{"id":13805842,"url":"https://github.com/hungrybluedev/whisker","last_synced_at":"2025-04-08T23:44:33.779Z","repository":{"id":167695251,"uuid":"584148809","full_name":"hungrybluedev/whisker","owner":"hungrybluedev","description":"V project for supporting whisker, a descendent of the Mustache template language.","archived":false,"fork":false,"pushed_at":"2024-06-16T10:52:41.000Z","size":168,"stargazers_count":39,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-14T18:36:24.774Z","etag":null,"topics":["template-engine","vlang","vlang-package"],"latest_commit_sha":null,"homepage":"https://trywhisker.com","language":"V","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/hungrybluedev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"hungrybluedev","patreon":null,"open_collective":null,"ko_fi":"hungrybluedev","tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2023-01-01T15:30:48.000Z","updated_at":"2024-11-16T00:00:21.000Z","dependencies_parsed_at":"2023-06-06T23:30:18.871Z","dependency_job_id":"c75caa0b-02e8-4417-8cc5-6919d995a93d","html_url":"https://github.com/hungrybluedev/whisker","commit_stats":null,"previous_names":["hungrybluedev/whisker"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hungrybluedev%2Fwhisker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hungrybluedev%2Fwhisker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hungrybluedev%2Fwhisker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hungrybluedev%2Fwhisker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hungrybluedev","download_url":"https://codeload.github.com/hungrybluedev/whisker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247947825,"owners_count":21023058,"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":["template-engine","vlang","vlang-package"],"created_at":"2024-08-04T01:01:05.465Z","updated_at":"2025-04-08T23:44:33.765Z","avatar_url":"https://github.com/hungrybluedev.png","language":"V","funding_links":["https://github.com/sponsors/hungrybluedev","https://ko-fi.com/hungrybluedev"],"categories":["Libraries"],"sub_categories":["Text processing"],"readme":"\u003c!--suppress HtmlDeprecatedAttribute --\u003e\n\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"https://raw.githubusercontent.com/hungrybluedev/whisker/main/docs/img/whisker%20logo.svg\" width=\"300\" alt=\"whisker logo\"/\u003e\n\n[vlang.io](https://vlang.io) | [hungrybluedev](https://hungrybluedev.in/)\n\n[![CI][workflow_badge]][workflow_url]\n[![License: MIT][license_badge]][license_url]\n[![Git Latest Tag][git_tag_badge]][git_tag_url]\n\n\u003c/div\u003e\n\n_whisker_ is inspired by [Mustache](https://mustache.github.io/) but is more\nstable, robust and predictable. It is a fast template engine\nfor [V](https://vlang.io/) with a simple syntax.\n\n## Features\n\n1. **Logic-less**: Different but expressive and powerful.\n2. **Four Data Types**: Booleans, Strings, Lists, and Maps.\n3. **Composable**: Supports nested iteration and partial recursion.\n4. **Simple Data Model**: Template data be constructed in V source code or\n   imported and exported using JSON.\n5. **Partials**: External, partial templates can be plugged into the primary\n   template.\n6. **Safe by Default**: Tag contents are HTML escaped by default.\n7. **Customisable**: The delimiters can be changed from the default `{{...}}`.\n\n## Motivation\n\nThe following blog posts provide more context:\n\n1. [Announcing\n   _whisker_ - easier way to do templates in V](https://hungrybluedev.tech/whisker-easier-way-to-do-templates-in-v/):\n   We take a look at current template engines available in V and announce a new\n   template engine.\n2. [Writing whisker’s tokeniser using the Theory of Computation](https://hungrybluedev.tech/writing-whiskers-tokeniser-using-the-theory-of-computation/):\n   We show how we use fundamental CS principles to implement an FSM-based\n   tokeniser for whisker.\n\n## Prerequisites\n\nYou must have V installed. Refer to\nthe [official instructions](https://github.com/vlang/v/#installing-v-from-source)\nfor help with installation.\n\nIf you already have V installed, use `v up` to update the toolchain and standard\nlibrary.\n\n## Installation\n\n### From VPM\n\n```\nv install hungrybluedev.whisker\n```\n\nThis should install the package as the `hungrybluedev.whisker` module.\n\nTo use it, use `import hungrybluedev.whisker` and proceed as normal.\n\n### From GitHub\n\nRun the following to install _whisker_ from **GitHub** using V's package manager:\n\n```\nv install --git https://github.com/hungrybluedev/whisker\n```\n\nThis should install in `hungrybluedev.whisker` first and then relocate it\nto `whisker`. Now, in your project, you can `import whisker` and use _whisker_\nright away!\n\n## Usage\n\nThe main struct is `whisker.template.Template` which can be generated either\ndirectly from template strings or be loaded from disk from template files. A\nsingle template should be reused for different data models to produce outputs\nwhich differ in content but not semantic structure.\n\n\u003e **Note**\n\u003e There might be slight white-space consistencies between the generated\n\u003e and expected results. For machine-verification, it is recommended to compare\n\u003e the parsed and reconstructed outputs for your particular file format.\n\n### Direct String Templates\n\n1. **Load a template**:\n   Use `template.from_strings(input: input_str, partials: partial_map)`\n   to generate a template from direct string inputs. Here, `input_str` is\n   a `string` and `partial_map` is a `map[string]string`. The map's keys are the\n   names of the template that are replaced by the direct template strings. Leave\n   the partials field empty if there are none required.\n2. **Run with Data Model**: Use `run(data)` to generate the output string. The\n   data can be represented in V source code directly (refer to the spec for\n   examples), or it can be loaded from JSON (using\n   `datamodel.from_json(data_string)`).\n\nThis is a copy-paste-able example to get started immediately:\n\n```v\nmodule main\n\n// Imports if you install from GitHub:\nimport whisker.datamodel\nimport whisker.template\n\n// Imports if you install from VPM:\nimport hungrybluedev.whisker.template\nimport hungrybluedev.whisker.datamodel\n\nfn main() {\n\tsimple_template := template.from_strings(input: 'Hello, {{name}}!')!\n\tdata := datamodel.from_json('{\"name\": \"World\"}')!\n\n\tprintln(simple_template.run(data)!) // prints \"Hello, World!\"\n}\n```\n\n### Template Files\n\n1. **Load a template**:\n   Use `template.load_file(input: input_str, partials: partial_map)` to\n   generate a template from file names. The difference here is that instead of\n   providing content, you provide the relative file paths. The names of the\n   partials need to be exact though, so keep an eye on that.\n2. **Run with Data Model**: Same as before. You can\n   use `os.read_file(path_to_json)` to read the JSON contents and then plug this\n   into the `datamodel.from_json` function.\n\nIt is not necessary, but it is recommended to use filenames that\ncontain `*.wskr.*` somewhere in the file name.\nCheck [json_test.v](spec/json_test.v) and [html_test.v](spec/html_test.v) for\nexamples with template files.\n\n## The CLI\n\n_whisker_ may also be used as a standalone command-line program to process\ntemplate files. It does not support direct template string input for the sake of\nsimplicity.\n\nBuild `whisker` with `v cmd/whisker` and run `cmd/whisker/whisker --help` for\nusage instructions. You can specify a `bin` subdirectory as output folder and\nadd it to path as well:\n\n```bash\n# Create an output directory\nmkdir cmd/bin\n\n# Build the executable\nv cmd/whisker -o cmd/bin/whisker\n\n# Run the executable\ncmd/bin/whisker --help\n```\n\nCheck [whisker_cli_test.v](cmd/whisker/whisker_cli_test.v) for a concrete\ndemonstration.\n\n## Syntax\n\n### Normal Text Is Unaffected\n\n#### Input\n\n```\nSample text\n```\n\n#### Output\n\n```\nSample text\n```\n\n### Double Curly Braces Indicate Sections\n\n#### Input\n\n```\nHello, {{name}}!\n```\n\n#### Data\n\n```json\n{\n  \"name\": \"world\"\n}\n```\n\n#### Output\n\n```\nHello, world!\n```\n\n### Changing Delimiters\n\n#### Input\n\n```v\n{{=[ ]=}}\nmodule main\n\nfn main() {\n    println('[greeting]')\n}\n```\n\n#### Data\n\n```json\n{\n  \"greeting\": \"Have a nice day!\"\n}\n```\n\n#### Output\n\n```v\nmodule main\n\nfn main() {\n    println('Have a nice day!')\n}\n```\n\n### Booleans, Positive, and Negative Sections\n\n#### Input\n\n```html\n\u003cnav\u003e\n  \u003cul\u003e\n    \u003cli\u003eHome\u003c/li\u003e\n    \u003cli\u003eAbout\u003c/li\u003e\n    {{-logged_in}}\n    \u003cli\u003eLog In\u003c/li\u003e\n    {{/logged_in}} {{+logged_in}}\n    \u003cli\u003eAccount: {{user.name}}\u003c/li\u003e\n    {{/logged_in}}\n  \u003c/ul\u003e\n\u003c/nav\u003e\n```\n\n#### Data 1\n\n```json\n{\n  \"logged_in\": false\n}\n```\n\n#### Output 1\n\n```html\n\u003cnav\u003e\n  \u003cul\u003e\n    \u003cli\u003eHome\u003c/li\u003e\n    \u003cli\u003eAbout\u003c/li\u003e\n    \u003cli\u003eLog In\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/nav\u003e\n```\n\n#### Data 2\n\n```json\n{\n  \"logged_in\": true,\n  \"user\": {\n    \"name\": \"whisker\"\n  }\n}\n```\n\n#### Output 2\n\n```html\n\u003cnav\u003e\n  \u003cul\u003e\n    \u003cli\u003eHome\u003c/li\u003e\n    \u003cli\u003eAbout\u003c/li\u003e\n\n    \u003cli\u003eAccount: whisker\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/nav\u003e\n```\n\n\u003e Positive and negative sections also apply to lists and maps. An empty list or map means a negative section and a non-empty one represents a positive section.\n\n**List**:\n\n#### Input\n\n```html\n{{+vacation}}\n\u003ch1\u003eCurrently on vacation\u003c/h1\u003e\n\u003cul\u003e\n  {{*.}}\n  \u003cli\u003e{{.}}\u003c/li\u003e\n  {{/.}}\n\u003c/ul\u003e\n{{/vacation}} {{-vacation}}\n\u003cp\u003eNobody is on vacation currently\u003c/p\u003e\n{{/vacation}}\n```\n\n#### Data 1\n\n```json\n{\n  \"vacation\": []\n}\n```\n\n#### Output 1\n\n```html\n\u003cp\u003eNobody is on vacation currently\u003c/p\u003e\n```\n\n#### Data 2\n\n```json\n{\n  \"vacation\": [\"Homer\", \"Marge\"]\n}\n```\n\n#### Output 2\n\n```html\n\u003ch1\u003eCurrently on vacation\u003c/h1\u003e\n\u003cul\u003e\n  \u003cli\u003eHomer\u003c/li\u003e\n  \u003cli\u003eMarge\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n**Map**:\n\n#### Input\n\n```html\n\n{{+user}}\n\u003cp\u003eWelcome {{last_name}}, {{first_name}}\u003c/h1\u003e\n{{/user}}\n{{-user}}\n\u003cp\u003eCreate account?\u003c/p\u003e\n{{/user}}\n```\n\n#### Data 1\n\n```json\n{\n  \"user\": {}\n}\n```\n\n#### Output 1\n\n```html\n\u003cp\u003eCreate account?\u003c/p\u003e\n```\n\n#### Data 2\n\n```json\n{\n  \"user\": {\n    \"last_name\": \"Simpson\",\n    \"first_name\": \"Homer\"\n  }\n}\n```\n\n#### Output 2\n\n```html\n\u003cp\u003eWelcome Simpson, Homer\u003c/h1\u003e\n```\n\n**Map Iteration:**\n\n#### Input\n\n```html\n\u003cul\u003e\n  {{*user}}\n  \u003cli\u003e{{key}}: {{value}}\u003c/li\u003e\n  {{/user}}\n\u003c/ul\u003e\n```\n\n#### Data\n\n```json\n{\n  \"user\": {\n    \"First Name\": \"Homer\",\n    \"Last Name\": \"Simpson\"\n  }\n}\n```\n\n#### Output\n\n```html\n\u003cul\u003e\n\u003cli\u003eFirst Name: Homer\u003c/li\u003e\n\u003cli\u003eLast Name: Simpson\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n### Maps, Lists, and Partials\n\n#### Input\n\n```html\n\u003col\u003e\n  {{*items}} {{\u003eitem}} {{/items}}\n\u003c/ol\u003e\n```\n\n#### Partial: item\n\n```html\n\u003cli\u003e{{name}}: {{description}}\u003c/li\u003e\n```\n\n#### Data\n\n```json\n{\n  \"items\": [\n    {\n      \"name\": \"Banana\",\n      \"description\": \"Rich in potassium and naturally sweet.\"\n    },\n    {\n      \"name\": \"Orange\",\n      \"description\": \"High in Vitamin C and very refreshing.\"\n    }\n  ]\n}\n```\n\n#### Output\n\n```html\n\u003col\u003e\n  \u003cli\u003eBanana: Rich in potassium and naturally sweet.\u003c/li\u003e\n  \u003cli\u003eOrange: High in Vitamin C and very refreshing.\u003c/li\u003e\n\u003c/ol\u003e\n```\n\nAll the examples shown here are tested in CI in\nthe [readme_test.v](spec/readme_test.v) file.\n\nFor the full specification, refer to the unit tests and test cases in\nthe [`spec`](spec) directory.\n\n## License\n\nThis project is distributed under the [MIT License](LICENSE).\n\n[workflow_badge]: https://github.com/hungrybluedev/whisker/actions/workflows/main.yml/badge.svg\n[license_badge]: https://img.shields.io/badge/License-MIT-blue.svg\n[workflow_url]: https://github.com/hungrybluedev/whisker/actions/workflows/main.yml\n[license_url]: https://github.com/hungrybluedev/whisker/blob/main/LICENSE\n[git_tag_url]: https://github.com/hungrybluedev/whisker/tags\n[git_tag_badge]: https://img.shields.io/github/v/tag/hungrybluedev/whisker?color=purple\u0026include_prereleases\u0026sort=semver\n\n## Acknowledgements\n\nThanks to the original Mustache project for inspiration and the specification.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhungrybluedev%2Fwhisker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhungrybluedev%2Fwhisker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhungrybluedev%2Fwhisker/lists"}