diff --git a/TODOS.md b/TODOS.md index 5fc85f3..dfe8f9f 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,8 +1,4 @@ - [ ] add content-hash fingerprinting for tailwind cache busting. -- [ ] create template language - - Look at the source for 3 template languages that support mustache syntax, - and see how they implement the tempaltes, then pick the best one. One of them - should be Hugo / go. - [ ] Evaluate Tufte CSS — borrow sidenote CSS or replace TailwindCSS entirely - Option B: Steal Tufte's sidenote/margin-note CSS (adapt for dark theme), keep TailwindCSS - Option C: Full Tufte CSS — drop TailwindCSS, no build step, semantic HTML, customize for dark theme + Roboto @@ -16,3 +12,4 @@ - [ ] Content-hash fingerprinting for CSS and JS cache busting - [ ] OpenGraph meta tags - [ ] Search up for `thor.json` files. +- [ ] Partials inside sections still produce duplicate items — a fundamental issue with the mustache library's token handling. diff --git a/config.odin b/config.odin index 07e5597..b3363ee 100644 --- a/config.odin +++ b/config.odin @@ -17,6 +17,7 @@ Site_Config :: struct { base_url: string `args:"name=base-url"`, content_dir: string `args:"name=content"`, output_dir: string `args:"name=output"`, + layouts_dir: string, author: string, social: []Social_Link, drafts: bool `args:"name=drafts"`, diff --git a/main.odin b/main.odin index baa3665..48cef1c 100644 --- a/main.odin +++ b/main.odin @@ -33,6 +33,9 @@ main :: proc() { if config.output_dir == "" { config.output_dir = fmt.tprintf("%s/public", config_dir) } + if config.layouts_dir == "" { + config.layouts_dir = fmt.tprintf("%s/layouts", config_dir) + } if config.base_url == "" { config.base_url = "http://localhost:8080" } diff --git a/mustache/.gitignore b/mustache/.gitignore new file mode 100644 index 0000000..9a7cb7b --- /dev/null +++ b/mustache/.gitignore @@ -0,0 +1,8 @@ +*/**/.DS_Store +.DS_Store +tmp/ +bin/ +*/**/odin-mustache.dSYM/ +*/**/odin-mustache +*/**/odin-mustache-test.dSYM/ +*/**/odin-mustache-test diff --git a/mustache/.gitmodules b/mustache/.gitmodules new file mode 100644 index 0000000..e032690 --- /dev/null +++ b/mustache/.gitmodules @@ -0,0 +1,4 @@ +[submodule "spec"] + path = spec + url = git@github.com:mustache/spec.git + branch = v1.4.1 diff --git a/mustache/Justfile b/mustache/Justfile new file mode 100644 index 0000000..b045f40 --- /dev/null +++ b/mustache/Justfile @@ -0,0 +1,15 @@ +flags := "-vet -show-timings -strict-style -vet-cast -vet-tabs -vet-using-param -disallow-do -vet-semicolon" +name := "odin-mustache" + +build: + @mkdir -p bin + odin build . -out:bin/{{name}} -debug {{flags}} + +test: build + odin test . -out:bin/{{name}} + +run: build + bin/{{name}} test/template.txt test/data.json test/layout.txt + +check: + odin check . {{flags}} diff --git a/mustache/LICENSE b/mustache/LICENSE new file mode 100644 index 0000000..367a29a --- /dev/null +++ b/mustache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2025 Benjamin Block + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/mustache/README.md b/mustache/README.md new file mode 100644 index 0000000..335c3ea --- /dev/null +++ b/mustache/README.md @@ -0,0 +1,162 @@ +# odin-mustache +Native implementation of {{mustache}} templates in [Odin](https://odin-lang.org). + +https://github.com/benjamindblock/odin-mustache/assets/1155805/7d794861-e308-4132-aaa0-f800392208a4 + +All features are implemented, except for the ability to change delimiters. + +All in tests in the [official mustache spec](https://github.com/mustache/spec) pass successfully (except for the delimiters spec suite). + +## Documentation + +For more information about mustache, see the [mustache project page](https://mustache.github.io) or the mustache [man](https://mustache.github.io/mustache.5.html) [pages](https://mustache.github.io/mustache.1.html). + +View some [example mustache files](https://github.com/mustache/mustache/tree/master/examples) to get an overview. + +## Spec + +The [mustache-spec](https://github.com/mustache/spec) repo is added as a git submodule for testing purposes. To run tests, ensure that you run `git clone --recursive` and/or `git submodule update` as needed. + +## Usage + +### CLI Usage +``` +Usage: + odin-mustache [path to template] [path to JSON] [OPTIONAL - layout] + +Examples: + $ odin-mustache template.txt data.json +``` + +### Odin Usage +#### 1. `render(template: string, data: any, partials: any, allocator := context.allocator)` + +Renders a template, provided as a `string`. `data` and `partials` should be *either* a `map[string]...` or a `struct`. + +All `map` arguments passed **must** be keyed with `string` type data. When parsing a Mustache template, the text inside a tag (eg. `name` inside `{{name}}`) will be parsed as a `string`. + +#### 2. `render_from_filename(filename: string, data: any, partials: any, allocator := context.allocator)` + +Renders a template stored in a text file using `data` and `partials` provided. + +#### 3. `render_with_json(template: string, json_filename: string, allocator := context.allocator)` + +Renders a template `string` using data and partials stored inside a JSON file. `odin-mustache` will handle loading the JSON into a usable format for Mustache to work with. + +**NOTE**: The JSON file leverages the following top-level keys: +``` +"data": [required] +"partials": [optional] +``` + +#### 4. `render_from_filename_with_json(filename: string, json_filename: string, allocator := context.allocator)` + +Renders a template stored in a text file using data and partials stored inside a JSON file. `odin-mustache` will handle loading the JSON into a usable format for Mustache to work with. + +#### Example +```odin +input := "Hello, {{name}}!" +data: map[string]string = { + "name" = "St. Charles", +} +output, err := render(input, data, context.temp_allocator) +// => "Hello, St. Charles!" +``` + +## Escaping +`odin-mustache` follows the official mustache HTML escaping rules. That is, if you enclose a variable with two curly brackets, `{{var}}`, the contents are HTML-escaped. For instance, strings like `5 > 2` are converted to `5 > 2`. To use raw characters, use three curly brackets `{{{var}}}`. + +## Layouts +`odin-mustache` supports rendering templates with layouts. + +A layout is a regular template with a special function. It accepts **a `{{content}}` tag**. This is where the output of a child template will be inserted. Layouts have access to the same data provided to the regular template. + +Layouts can render content in both `{{normal}}` and `{{{literal}}}` tags. + +This is helpful for rendering scenarios like websites where the same elements (``, `