{"id":21343901,"url":"https://github.com/benjamin-hodgson/eighty","last_synced_at":"2025-04-06T01:07:00.325Z","repository":{"id":52542736,"uuid":"121961895","full_name":"benjamin-hodgson/Eighty","owner":"benjamin-hodgson","description":"An HTML generation library","archived":false,"fork":false,"pushed_at":"2025-03-24T14:17:55.000Z","size":3678,"stargazers_count":41,"open_issues_count":4,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-30T00:05:43.717Z","etag":null,"topics":["csharp","dotnet","html","html-generation","web"],"latest_commit_sha":null,"homepage":"https://www.benjamin.pizza/Eighty/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benjamin-hodgson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-18T15:04:13.000Z","updated_at":"2025-03-24T14:07:17.000Z","dependencies_parsed_at":"2023-11-28T21:40:36.253Z","dependency_job_id":"b26115e6-d388-46a0-8d56-84c3e04ddf11","html_url":"https://github.com/benjamin-hodgson/Eighty","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamin-hodgson%2FEighty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamin-hodgson%2FEighty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamin-hodgson%2FEighty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamin-hodgson%2FEighty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benjamin-hodgson","download_url":"https://codeload.github.com/benjamin-hodgson/Eighty/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247419859,"owners_count":20936012,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","html","html-generation","web"],"created_at":"2024-11-22T01:16:13.838Z","updated_at":"2025-04-06T01:07:00.297Z","avatar_url":"https://github.com/benjamin-hodgson.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"Eighty\n======\n\nEighty (as in _eigh-ty-M-L_) is a simple HTML generation library. It's an embedded domain-specific language, allowing you to write C# code which roughly resembles HTML. Programming with Eighty is _just programming_: HTML is represented as ordinary immutable C# values which can be passed around as usual. This makes for an approachable alternative to Razor when you don't need the complexity of a whole templating language. It's a great fit for simple web apps or command-line tools like report generators.\n\n```csharp\nvar html = article(@class: \"readme\")._(\n    h1(id: \"Eighty\")._(\"Eighty\"),\n    p_(\n        \"Eighty (as in \",\n        i_(\"eigh-ty-M-L\"),\n        \") is a simple HTML generation library.\"\n    )\n);\n```\n\nInstallation\n------------\n\nEighty is available from [Nuget](https://www.nuget.org/packages/Eighty). API docs are hosted [on my website](https://www.benjamin.pizza/Eighty).\n\nGuide\n-----\n\nYou can read an intro to Eighty's programming model [on my personal website](https://www.benjamin.pizza/posts/2018-03-10-eighty.html).\n\nAlmost all of Eighty's API lives in the `Html` class, which is designed to be imported with `using static`. (I like to additionally import it under an alias, to minimise line noise when you need to disambiguate something.)\n\n```csharp\nusing Eighty;\nusing static Eighty.Html;\nusing H = Eighty.Html;\n```\n\nEighty's domain-specific language adopts the following conventions:\n\n* Tags are created using (lower-case) methods like `p()` and `i()`.\n* Attributes are passed to tags as named arguments: `h1(@class: \"foo\")`\n    * Boolean attributes like `disabled` take a `bool` as their argument, not a `string`. `button(disabled: true)` produces `\u003cbutton disabled\u003e\u003c/button\u003e`\n    * Attribute names and values are HTML-encoded by default. Use the `Attr.Raw` factory method if you don't want this.\n    * You can add custom attributes using tuples, or by creating an `Attr` yourself: `div((\"data-example\", \"example\"), new Attr(\"class\", \"bar\"))`.\n    * You can add custom Boolean attributes using the one-parameter overloads of `new Attr` and `Attr.Raw`.\n* The `_` symbol means _here are the children_. `_` can appear in a few places:\n    * At the end of the tag method's name. This creates a tag with no attributes. `div_(span(), img())` creates a `div` containing a `span` and an `img`.\n    * As a method on a tag with attributes. `div(@class: \"container\")._(span(), img())` creates a `div` with a class `container`, with a `span` and an `img` inside it. Note that this means `p()._(...)` is equivalent to `p_(...)`.\n    * As a top-level method name (imported statically from `Html`). This just groups together siblings. `_(span(), img())` creates a `span` and an `img` next to each other at the top level.\n* You can't use `_` with self-closing tags like `img`. It doesn't have a `_` method or an `img_` variant.\n* Strings are interpreted as text and are HTML-encoded: `p_(\"text\u003c\u003e\")` will produce `\u003cp\u003etext\u0026lt;\u0026gt;\u003c/p\u003e`.\n    * To render a string containing raw HTML use the `Raw` method.\n* All `Html` values are immutable and can be passed around as normal C# values, including being shared between different HTML documents. (Sharing as much of a tree as you can is often important for efficiency!)\n\nTo render your `Html`, use its `Write` or `WriteAsync` methods. (If you just want it as a string, you can call `ToString()`.)\n\n```csharp\nusing (var writer = new StringWriter())\n{\n    html.Write(writer);\n    Console.WriteLine(writer.ToString());\n}\n```\n\n\nHow it works\n------------\n\nSome simple tricks make this DSL's syntax work.\n\n1. Methods like `p()` return a `TagBuilder` object, which defines the `_` method. A `TagBuilder` represents an HTML tag which is waiting for its children. Calling `_` assembles the tag and its children into an `Html` value.\n    * Methods like `p_()` return an `Html` value directly. Same goes for self-closing tags like `img`\n2. You can tersely combine `Html` values with other objects using implicit conversions. In particular:\n    * An implicit conversion from `string` to `Html`, so you can easily mix text with markup\n    * An implicit conversion from `TagBuilder` to `Html`, so you don't have to call `TagBuilder._()` when a tag doesn't have any children.\n    * An implicit conversion from `(string, string)` to `Attr` to save keystrokes when using custom attributes.\n3. `_` methods are declared to take `params Html[] children`. You can give a tag an arbitrary number of children, and the implicit conversions coerce each child to `Html` automatically.\n4. All of the tag methods were written using code generation. There are around 25,000 lines of generated code in `Html`!\n\n\nTwenty\n------\n\nEighty comes bundled with a second HTML generation library called Twenty. Twenty is very fast (at least 5-10x faster than both Eighty and Razor in most cases) and generates no garbage, but it has a noisier syntax and is easier to misuse than Eighty. (It also can't be used with `async`.) Twenty is a good fit when you need to render HTML really fast, but you don't want to template strings or build tags by hand. I recommend starting with Eighty, and switching to Twenty if you're sure you need the extra speed.\n\nTwenty takes an imperative view of HTML generation, wherein HTML is written directly to the output stream. (That's why it's fast.) The balancing of tags is managed by `using` statements.\n\nTo use Twenty, subclass `HtmlBuilder`. This brings the various tag methods into scope, for use in `Build`. You _must_ put the (non-self-closing) tags in `using` statements:\n\n```csharp\nusing Eighty.Twenty;\n\nclass MyHtmlBuilder : HtmlBuilder\n{\n    protected override void Build()\n    {\n        using (article(@class: \"readme\"))\n        {\n            using (h1(id: \"Eighty\"))\n                Text(\"Eighty\");\n            using (p())\n            {\n                Text(\"Eighty (as in \");\n                using (i())\n                    Text(\"eigh-ty-M-L\");\n                Text(\") is a simple HTML generation library.\");\n            }\n        }\n    }\n}\n```\n\nThe tag methods like `div` write the opening tag to the output stream and then return an `IDisposable` which writes the closing tag in its `Dispose` method. It's therefore imperative (ha ha) that you don't accidentally miss out a `using` statement, as that would result in a half-closed tag! You should also avoid calling `Dispose` more than once.\n\nIt also means that `HtmlBuilder` is not thread-safe; instances of `HtmlBuilder` can only be safely accessed from one thread at a time. And it doesn't support `async`, because there's no way to implement `Dispose` asynchronously.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenjamin-hodgson%2Feighty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenjamin-hodgson%2Feighty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenjamin-hodgson%2Feighty/lists"}