{"id":16639851,"url":"https://github.com/dzoukr/fue","last_synced_at":"2025-05-16T03:02:03.364Z","repository":{"id":47851667,"uuid":"74512265","full_name":"Dzoukr/Fue","owner":"Dzoukr","description":"F# templating library with simple syntax designed for smooth work with F# types.","archived":false,"fork":false,"pushed_at":"2025-02-06T07:10:04.000Z","size":320,"stargazers_count":119,"open_issues_count":3,"forks_count":13,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-16T03:01:47.994Z","etag":null,"topics":["fsharp","html","netstandard20","template-engine","templating","viewmodel","web"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dzoukr.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null}},"created_at":"2016-11-22T20:53:40.000Z","updated_at":"2025-03-25T15:16:54.000Z","dependencies_parsed_at":"2025-05-16T03:01:27.118Z","dependency_job_id":null,"html_url":"https://github.com/Dzoukr/Fue","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dzoukr%2FFue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dzoukr%2FFue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dzoukr%2FFue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dzoukr%2FFue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dzoukr","download_url":"https://codeload.github.com/Dzoukr/Fue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254459078,"owners_count":22074604,"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":["fsharp","html","netstandard20","template-engine","templating","viewmodel","web"],"created_at":"2024-10-12T07:07:13.651Z","updated_at":"2025-05-16T03:02:03.230Z","avatar_url":"https://github.com/Dzoukr.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://dev.azure.com/dzoukrcz/Fue/_apis/build/status/Fue?branchName=master)](https://dev.azure.com/dzoukrcz/Fue/_build/latest?definitionId=2\u0026branchName=master)\n\n# Fue\n\nF# templating library with simple syntax designed for smooth work with F# types.\n\n## Why another templating library?\n\nWe have [Razor](https://github.com/Antaris/RazorEngine), we have [DotLiquid](http://dotliquidmarkup.org/) - why another templating library? I know, \"rendering on server side is so 2010\", but sometimes we just need (or want) to do it - for emails, for documents, even for HTML (yes, some oldschoolers still do it on server). And then pain starts: You need to have plenty of *ViewModels* to transform data from Discriminated Unions, Tuples, etc... \n\nWouldn\\`t be just great to have a library that allows you to use your original data without annoying only-for-template transformation? Good news! Fue was designed as *ViewModels |\u003e NoMore* library with focus on minimalistic API.\n\n\n## Installation\nFirst install NuGet package\n\n    Install-Package Fue\n\nor using [Paket](http://fsprojects.github.io/Paket/getting-started.html)\n\n    nuget Fue\n\n\n## Basic templating\n\nBefore we start, open these two modules:\n\n```fsharp\nopen Fue.Data\nopen Fue.Compiler\n```\n\nNow we need storage for our view data. Function `init` from `Fue.Data` module is here for you. Once initiated, you can store values in it.\n\n```fsharp\ninit |\u003e add \"name\" \"Roman\"\n```\n\nHaving data prepared, lets start rendering our values using simple `{{{myValue}}}` syntax.\n\n```fsharp\ninit |\u003e add \"name\" \"Roman\" |\u003e fromText \"{{{name}}}\" // compiles to value \"Roman\"\n```\n\nFull example:\n\n```fsharp\nlet html = \"\u003cdiv\u003e{{{name}}}\u003c/div\u003e\"\nlet compiledHtml = init |\u003e add \"name\" \"Roman\" |\u003e fromText html\n// compiledHtml now contains \"\u003cdiv\u003eRoman\u003c/div\u003e\"\n\n```\nTo help prevent cross site scripting (XSS) you can use `fromTextSafe`.\n\n```fsharp\nlet html = \"\u003cdiv\u003e{{{name}}}\u003c/div\u003e\"\nlet compiledHtml = init |\u003e add \"name\" \"\u003cscript\u003ealert('hi')\u003c/script\u003e\" |\u003e fromTextSafe html\n// compiledHtml is now \"\u003cdiv\u003e\u0026lt;script\u0026gt;alert(\u0026#39;hi\u0026#39;)\u0026lt;/script\u0026gt;\u003c/div\u003e\"\n```\n\nIf a variable is not defined, it gets rendered as is - including the braces.\n\n```fsharp\nlet html = \"\u003cdiv\u003e{{{name}}}\u003c/div\u003e\"\nlet compiledHtml = init |\u003e fromText html\n// compiledHtml now contains \"\u003cdiv\u003e{{{name}}}\u003c/div\u003e\"\n```\n\nHowever, if you want to have a default value in case the variable is not defined or `null`, you can do that with `??`:\n```fsharp\nlet html = \"\u003cdiv\u003e{{{name ?? \"Roman\"}}}\u003c/div\u003e\"\nlet compiledHtml = init |\u003e fromText html\n// compiledHtml now contains \"\u003cdiv\u003eRoman\u003c/div\u003e\"\n```\n\nWanna use functions? No problem!\n\n```fsharp\nlet html = \"\u003cdiv\u003e{{{getName()}}}\u003c/div\u003e\"\nlet compiledHtml = init |\u003e add \"getName\" (fun _ -\u003e \"Roman\") |\u003e fromText html\n// compiledHtml now contains \"\u003cdiv\u003eRoman\u003c/div\u003e\"\n```\n\nOr pipe forward operator?\n\n```fsharp\nlet html = \"\u003cdiv\u003e{{{myParam |\u003e multiply}}}\u003c/div\u003e\"\nlet compiledHtml =\n    init\n    |\u003e add \"myParam\" 10\n    |\u003e add \"multiply\" (fun x -\u003e x * 2)\n    |\u003e fromText html\n// compiledHtml now contains \"\u003cdiv\u003e20\u003c/div\u003e\"\n```\n\nAnd combine own functions with literals.\n\n```fsharp\nlet html = \"\"\"\u003cdiv\u003e{{{printHello(\"Roman\", myValue)}}}\u003c/div\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"printHello\" (fun name1 name2 -\u003e sprintf \"Hello %s and %s\" name1 name2)\n    |\u003e add \"myValue\" \"Jiri\"\n    |\u003e fromText html\n// compiledHtml now contains \"\u003cdiv\u003eHello Roman and Jiri\u003c/div\u003e\"\n```\n\nYou can also pass records to functions.\n\n```fsharp\nlet html = \"\"\"\u003cdiv\u003e{{{printHello({ name = \"Roman\" })}}}\u003c/div\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"printHello\" (fun name1 name2 -\u003e sprintf \"Hello %s and %s\" name1 name2)\n    |\u003e add \"myValue\" \"Jiri\"\n    |\u003e fromText html\n// compiledHtml now contains \"\u003cdiv\u003eHello Roman and Jiri\u003c/div\u003e\"\n```\n\n**Please note:** For better work with HTML templates, literals syntax can be marked with both 'single quotes' or \"double quotes\" and \"\"\"tripple quotes\"\"\" if you need multiline text\n\nRecord values can contain any other value including records.\n\n```fsharp\n{\n    a: \"bar\" // literal as value\n    b: abc   // variable as value\n    c: abc() // function call\n    d: \"abc\" |\u003e abc // pipes\n    c: {     // nested record\n       a: \"bar\" \n       ....\n    }\n}\n```\n\n\n\n## Supported types\n\nFue is designed to work with *classes, records, tuples, options, discriminated unions as well as anonymous functions*.\n\n```fsharp\ntype MyRecord = { Name : string }\nlet html = \"\"\"\u003cdiv id=\"{{{id}}}\"\u003e{{{fst myTuple}}} {{{myRec.Name}}}\u003c/div\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"myTuple\" (\"Hello\", 35)\n    |\u003e add \"fst\" fst\n    |\u003e add \"myRec\" { Name = \"John\"}\n    |\u003e add \"id\" \"someId\"\n    |\u003e fromText html\n// compiledHtml now contains \"\"\"\u003cdiv id=\"someId\"\u003eHello John\u003c/div\u003e\"\"\"\n```\n\n## Rendering from file\n\nCommon usage of template engines is to have templates separated as files. Function `fromFile` is here for you.\n\n```fsharp\nlet compiledHtml =\n    init\n    |\u003e add \"someValue\" \"myValue\"\n    |\u003e fromFile \"relative/or/absolute/path/to/file.html\"\n```\nThere is also `fromFileSafe` you can use if you need to prevent XSS.\n\n## Conditional rendering\n\nTrue power of Fue library is in custom attributes which can affect how will be template rendered.\n\n### fs-if\n\nSimple *if condition* attribute.\n\n```fsharp\nlet html = \"\"\"\u003cdiv fs-if=\"render\"\u003eThis DIV won`t be rendered at all\u003c/div\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"render\" false\n    |\u003e fromText html\n// compiledHtml is empty string\n```\n\n### fs-else\n\nSimple *else condition* attribute.\n\n```fsharp\nlet html = \"\"\"\u003cdiv fs-if=\"render\"\u003eNot rendered\u003c/div\u003e\u003cdiv fs-else\u003eThis will be rendered\u003c/div\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"render\" false\n    |\u003e fromText html\n// compiledHtml is \"\u003cdiv\u003eThis will be rendered\u003c/div\u003e\"\n```\n\n**Please note**: Else condition must immediately follow If condition.\n\n### fs-du \u0026 fs-case\n\nCondition based on value of *Discriminated Union*.\n\n```fsharp\ntype Access =\n    | Anonymous\n    | Admin of username:string\n\nlet html =\n    \"\"\"\n    \u003cdiv fs-du=\"access\" fs-case=\"Anonymous\"\u003eWelcome unknown!\u003c/div\u003e\n    \u003cdiv fs-du=\"access\" fs-case=\"Admin(user)\"\u003eWelcome {{{user}}}\u003c/div\u003e\n    \"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"access\" (Access.Admin(\"Mr. Boosh\"))\n    |\u003e fromText html\n// compiledHtml is \"\u003cdiv\u003eWelcome Mr. Boosh\u003c/div\u003e\"\n```\n\nOf course, if you do not need associated case values, you can ignore them.\n\n```fsharp\nlet html =\n    \"\"\"\n    \u003cdiv fs-du=\"access\" fs-case=\"Anonymous\"\u003eWelcome unknown!\u003c/div\u003e\n    \u003cdiv fs-du=\"access\" fs-case=\"Admin(_)\"\u003eWelcome admin\u003c/div\u003e\n    \"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"access\" (Access.Admin(\"Mr. Boosh\"))\n    |\u003e fromText html\n// compiledHtml is \"\u003cdiv\u003eWelcome admin\u003c/div\u003e\"\n```\n\nFue syntax allows you to do not extract any value (even if there is some associated).\n\n```fsharp\nlet html =\n    \"\"\"\n    \u003cdiv fs-du=\"access\" fs-case=\"Anonymous\"\u003eWelcome unknown!\u003c/div\u003e\n    \u003cdiv fs-du=\"access\" fs-case=\"Admin\"\u003eWelcome admin\u003c/div\u003e\n    \"\"\"\n```\n\n### fs-template\n\nNon-rendered placeholder for text. Use with combination of other Fue attributes.\n\n```fsharp\nlet html = \"\"\"\u003cfs-template fs-if=\"render\"\u003eRendered only inner Html\u003c/fs-template\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"render\" true\n    |\u003e fromText html\n// compiledHtml is \"Rendered only inner Html\"\n```\n\n## List rendering\n\n### fs-for\n\n*For-cycle* attribute\n\n```fsharp\nlet html = \"\"\"\u003cul\u003e\u003cli fs-for=\"item in items\"\u003e{{{item}}}\u003c/li\u003e\u003c/ul\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"items\" [\"A\";\"B\";\"C\"]\n    |\u003e fromText html\n// compiledHtml is \"\u003cul\u003e\u003cli\u003eA\u003c/li\u003e\u003cli\u003eB\u003c/li\u003e\u003cli\u003eC\u003c/li\u003e\u003c/ul\u003e\"\n```\n\nCommon task for rendering lists is to show row number, index or whole list length. Auto-created values `{{{$index}}}`, `{{{$iteration}}}`, `{{{$length}}}`, `{{{$is-last}}}` and `{{{$is-not-last}}}` are here to help.\n\n```fsharp\nlet html = \"\"\"\u003cli fs-for=\"i in items\"\u003e{{{i}}} is {{{$index}}}, {{{$iteration}}}, {{{$length}}}\u003c/li\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"items\" [\"A\";\"B\";\"C\"]\n    |\u003e fromText html\n// compiledHtml is \"\u003cli\u003eA is 0, 1, 3\u003c/li\u003e\u003cli\u003eB is 1, 2, 3\u003c/li\u003e\u003cli\u003eC is 2, 3, 3\u003c/li\u003e\"\n```\n\nSometimes you don't want to render all items of a list.\nYou can either prefilter the items `fs-for\"i in filter(items)\"` or alternatively you can combine `fs-for` with `fs-if`\n```fsharp\nlet html = \"\"\"\u003cli fs-if\"filter(i)\" fs-for=\"i in items\"\u003e{{{i}}} is {{{$index}}}, {{{$iteration}}}, {{{$length}}}\u003c/li\u003e\"\"\"\n```\n\nSince version 1.2.0 there is support for tuples destructuring:\n\n```fsharp\nlet html = \"\"\"\u003cli fs-for=\"greetings,target in items\"\u003eI say {{{greetings}}} to {{{target}}}\u003c/li\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"items\" [(\"Hi\",\"World\");(\"Hello\",\"Planet\")]\n    |\u003e fromText html\n// compiledHtml is \"\u003cli\u003eI say Hi to World\u003c/li\u003e\u003cli\u003eI say Hello to Planet\u003c/li\u003e\"\n```\n\n\n## Working with Option types\n\n*Option* types are fully supported and you can use them as you would directly from F# code.\n\n```fsharp\nlet html = \"\"\"\u003cdiv fs-if=\"myOptionValue.IsSome\"\u003eI got {{{myOptionValue.Value}}}\u003c/div\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"myOptionValue\" (Some \"abc\")\n    |\u003e fromText html\n// compiledHtml is \"\u003cdiv\u003eI got abc\u003c/div\u003e\"\n```\n\nor\n\n```fsharp\nlet html = \"\"\"\u003cdiv fs-if=\"myOptionValue.IsNone\"\u003eI got nothing\u003c/div\u003e\"\"\"\nlet compiledHtml =\n    init\n    |\u003e add \"myOptionValue\" None\n    |\u003e fromText html\n// compiledHtml is \"\u003cdiv\u003eI got nothing\u003c/div\u003e\"\n```\n\n## Cheatsheet\n\nSimple HTML snippet to show what can be achieved using Fue:\n\n```html\n\u003c!--Literals--\u003e\n{{{'single quoted literal'}}}\n{{{\"single line literal\"}}}\n{{{\"\"\"multi-line\nliteral\"\"\"}}}\n\n\u003c!--Template basics--\u003e\n{{{value}}} - Static value\n{{{value ?? \"default value\"}}} - Default Value\n{{{function()}}} - Function value\n{{{value1 |\u003e fun1}}}\n{{{ { key = value; key2 = value2 } }}}\n{{{ {\n    key = value\n    key2 = value2\n} }}}\n\n\u003c!--For-cycle--\u003e\n\u003cli fs-for=\"item in items\"\u003e\n    {{{item.Name}}} {{{$index}}} {{{$length}}} {{{$iteration}}}\n    \u003cdiv fs-if=\"$is-not-last\"\u003eShown if not last item of collection\u003c/div\u003e\n    \u003cdiv fs-if=\"$is-last\"\u003eShown only for the last item of collection\u003c/div\u003e\n\u003c/li\u003e\n\n\u003c!--For-cycle with direct tuple destructuring--\u003e\n\u003c!--let items = [(\"Hi\",\"World\");(\"Hello\";\"Planet\")]--\u003e\n\u003cli fs-for=\"greetings,target in items\"\u003e\n    I say {{{greetings}}} to {{{target}}}\n\u003c/li\u003e\n\n\u003c!--Condition--\u003e\n\u003cdiv fs-if=\"someCondition\" id=\"{{{id}}}\"\u003eValue\u003c/div\u003e\n\u003cdiv fs-else\u003e\u003c/div\u003e\n\n\u003c!--Option types--\u003e\n\u003cdiv fs-if=\"someOption.IsSome\"\u003e{{{someOption.Value}}}\u003c/div\u003e\n\u003cdiv fs-if=\"someOption.IsNone\"\u003eNothing\u003c/div\u003e\n\n\u003c!--Discriminated Union\ntype UserAccess =\n    | Anonymous\n    | Admin of section:string\n--\u003e\n    \u003cdiv fs-du=\"item\" fs-case=\"Anonymous\"\u003eAnonymous\u003c/div\u003e\n    \u003cdiv fs-du=\"item\" fs-case=\"Admin(section)\"\u003e{{{section}}}\u003c/div\u003e\n\n\u003c!--Placeholder--\u003e\n\u003cfs-template fs-if=\"someCondition\"\u003e\n    Some value\n\u003c/fs-template\u003e\n```\n\n## Used libraries\n\nFue is based on amazing [Html Agility Pack](http://htmlagilitypack.codeplex.com/) library.\n\n## Contribution\nDid you find any bug? Missing functionality? Please feel free to [Create issue](https://github.com/Dzoukr/Fue/issues) or [Pull request](https://github.com/Dzoukr/Fue/pulls).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzoukr%2Ffue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdzoukr%2Ffue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzoukr%2Ffue/lists"}