├── .gitignore ├── LICENSE ├── README.md ├── branding ├── nte-colors.svg ├── nte-monochrome.svg └── powered-by-nte.png ├── changelog.md ├── engine.nix ├── example ├── base.css ├── default.nix ├── index.nix ├── posts │ ├── index.css │ ├── index.nix │ ├── post.css │ └── test.nix └── templates │ ├── base.nix │ └── post.nix ├── flake.lock ├── flake.nix ├── nte-drv.nix └── stdlib.nix /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | */result 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024-2025 poz 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

nte

5 |
6 | 7 | nix template engine - takes some templates, entries and applies the templates to the entries 8 | 9 | nte's main repository is on [my forgejo](https://git.poz.pet/poz/nte) instance 10 | 11 | mirrors are available on [github](https://github.com/imnotpoz/nte) and [codeberg](https://codeberg.org/poz/nte), I accept contributions from anywhere 12 | 13 | # sites written in nte 14 | https://poz.pet 15 | 16 | https://nixwebr.ing 17 | 18 | https://mewoocat.github.io/ 19 | 20 | if your site (or anything else) is written in nte, let me know and I'll add you to this list 21 | 22 | you can also use this button on your site and link to one of the repos 23 | 24 | [](https://git.poz.pet/poz/nte) 25 | 26 | # examples 27 | 28 | check `example/` for a static website written in nte 29 | 30 | build and run it using 31 | ```sh 32 | nix shell nixpkgs#darkhttpd --command sh -c "nix build -L .#examples.x86_64-linux.default && darkhttpd ./result" 33 | ``` 34 | the site will be available at http://localhost:8080 35 | 36 | the example is a cut down version of my own website 37 | 38 | # usage 39 | 40 | first add nte as an input in your project's flake 41 | 42 | ```nix 43 | nte = { 44 | url = "git+https://git.poz.pet/poz/nte"; 45 | # or one of the mirrors 46 | #url = "git+https://codeberg.org/poz/nte"; 47 | #url = "github:imnotpoz/nte"; 48 | inputs.nixpkgs.follows = "nixpkgs"; 49 | }; 50 | ``` 51 | 52 | then use the `mkNteDerivation` wrapper over `stdenv.mkDerivation` available under 53 | ```nix 54 | inputs.nte.functions.${system}.mkNteDerivation 55 | ``` 56 | it accepts an attrset of: 57 | - `name`, `version`, `src` - passthrough to `stdenv.mkDerivation` 58 | - `extraArgs` - an attrset of additional arguments passed to all entries and templates 59 | - `entries` - a list of all entry files to be processed 60 | - `templates` - a list of all template files to be applied 61 | - `extraFiles` - a list of either: 62 | - a string containing the path to the source file - will be copied to `$out` in the `installPhase` 63 | - attrset of `source` and `destination`: 64 | - `source` - a string containing a path, if relative `$PWD` is `$src` in the `installPhase` 65 | - `destination` - a string containing a path, never absolute, appended to `$out` in the `installPhase` 66 | - `preBuild` - passthrough to `stdenv.mkDerivation` 67 | - `postBuild` - passthrough to `stdenv.mkDerivation` 68 | - `preInstall` - passthrough to `stdenv.mkDerivation` 69 | - `postInstall` - passthrough to `stdenv.mkDerivation` 70 | 71 | make sure not to use nix paths in `extraFiles` if you want the names of the files to match up 72 | 73 | example usage of the wrapper function: 74 | ```nix 75 | mkNteDerivation { 76 | name = "nte-example"; 77 | version = "0.1"; 78 | src = ./.; 79 | 80 | extraArgs = { 81 | foo = 2137; 82 | bar = "dupa"; 83 | baz = arg1: arg2: '' 84 | here's arg1: ${arg1} 85 | and here's arg2: ${arg2} 86 | ''; 87 | }; 88 | 89 | entries = [ 90 | ./entry1.nix 91 | ./foo/entry2.nix 92 | ./foo/entry3.nix 93 | ./bar/entry4.nix 94 | ./bar/entry5.nix 95 | ./bar/entry6.nix 96 | ]; 97 | 98 | templates = [ 99 | ./template1.nix 100 | ./template2.nix 101 | ]; 102 | 103 | extraFiles = [ 104 | "./data.txt" # equivalent to { source = ./data.txt; destination = "/"; } 105 | { source = "./image.png"; destination = "/assets/"; } 106 | { source = "./image2.png"; destination = "/assets/dupa.png"; } 107 | { source = "./data/*"; destination = "/assets/data/"; } 108 | { source = fetchurl { ... }; destination = "/"; } 109 | ]; 110 | } 111 | ``` 112 | 113 | nte will handle creating directories if your source file structure isn't flat 114 | 115 | if the `mkNteDerivation` wrapper isn't enough for you, you can do things the old way - by putting the output of `inputs.nte.functions.${system}.engine` in a derivation's `buildPhase`: 116 | ```nix 117 | mkDerivation { 118 | # ... 119 | 120 | buildPhase = '' 121 | runHook preBuild 122 | 123 | ${engine {inherit extraArgs entries templates;}} 124 | 125 | runHook postBuild 126 | ''; 127 | } 128 | ``` 129 | 130 | in that case if you wish to replicate the functionality of `extraFiles` you can use the derivation's `installPhase`, manually `mkdir` the needed directories and `cp` your files into `$out` 131 | 132 | nte offers a standard library that contains: 133 | - `nixpkgs` 134 | - `getEntry` - a function that gives you access to the entry's attributes 135 | - `applyTemplate` - a function that allows you to manually apply a template to an entry 136 | - utility functions found in [stdlib.nix](./stdlib.nix) 137 | 138 | ## templates 139 | 140 | a template can take an arbitrary number of arguments and returns `{ name, format, output }`: 141 | 142 | - `name` - used as a template ID for the entries 143 | - `format` - the extension of the output file (ignored if an entry defines `file`) 144 | - `output` - string if in a base template, entry to another template otherwise 145 | 146 | example template: 147 | ```nix 148 | { 149 | name, 150 | location, 151 | info, 152 | ... 153 | }: { 154 | name = "greeting"; 155 | format = "txt"; 156 | 157 | output = '' 158 | Hello ${name}! Welcome to ${location}. 159 | 160 | Here's some more information: 161 | ${info} 162 | ''; 163 | } 164 | ``` 165 | 166 | a template's output can also be an entry to another template: 167 | ```nix 168 | { 169 | name, 170 | location, 171 | date, 172 | time, 173 | ... 174 | }: { 175 | name = "greeting-with-date"; 176 | output = { 177 | template = "greeting"; 178 | 179 | inherit name location; 180 | 181 | info = '' 182 | You're visiting ${location} on ${date} at ${time}! 183 | ''; 184 | }; 185 | } 186 | ``` 187 | a template that's inherited from a different template also inherits its format - no need to define it again 188 | 189 | ## entries 190 | 191 | an entry can take an arbitrary number of arguments and returns `{ template, ... }`, the `...` being the desired template's arguments (sans `extraArgs`, those are passed either way) 192 | 193 | there's a built-in `passthrough` template, which (as the name might suggest) takes in a `format` and `output` and passes them through to the template with no changes 194 | 195 | this is useful if you're using nte to create a single file - you won't have to create a boilerplate template 196 | 197 | example entries (using the previous example templates): 198 | ```nix 199 | _: { 200 | template = "greeting"; 201 | 202 | name = "Jacek"; 203 | location = "Wrocław"; 204 | info = '' 205 | As of 2023, the official population of Wrocław is 674132 making it the third largest city in Poland. 206 | ''; 207 | } 208 | ``` 209 | an entry using the stdlib: 210 | ```nix 211 | { 212 | run, 213 | ... 214 | }: { 215 | template = "greeting-with-date"; 216 | 217 | name = "Rafał"; 218 | location = "Osieck"; 219 | date = run "date +%F"; 220 | time = run "date +%T"; 221 | } 222 | ``` 223 | if a binary isn't in `$PATH`, remember that each entry gets `pkgs`: 224 | ```nix 225 | { 226 | pkgs, 227 | run, 228 | ... 229 | }: let 230 | date = "${pkgs.coreutils-full}/bin/date"; 231 | in { 232 | # ... 233 | date = run "${date} +%F"; 234 | time = run "${date} +%T"; 235 | } 236 | ``` 237 | 238 | nte by default will follow your source file structure, if you want to specify the output location yourself use `file`: 239 | ```nix 240 | _: { 241 | # ... 242 | file = "foo/bar.txt"; 243 | } 244 | ``` 245 | in this example the output of this entry will end up at `$out/foo/bar.txt` instead of the default location - a base template's `format` will also be ignored 246 | 247 | # thanks 248 | 249 | [raf](https://notashelf.dev/) for helping me out with some of the nix and setting up mirrors 250 | 251 | # license 252 | MIT 253 | -------------------------------------------------------------------------------- /branding/nte-colors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 314 | -------------------------------------------------------------------------------- /branding/nte-monochrome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 306 | -------------------------------------------------------------------------------- /branding/powered-by-nte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imnotpoz/nte/986ab4440f3c55ba3f4e673932b39d9b5a06864d/branding/powered-by-nte.png -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | - made this 3 | 4 | # 0.2.0 5 | - made `file` in entries optional 6 | 7 | # 0.3.0 8 | - set nixpkgs input as unstable by default 9 | - added `github:nix-systems/default` as an input 10 | - moved the following outputs: 11 | - `engines.${system}.default` -> `functions.${system}.engine` 12 | - introduced `mkNteDerivation` helper function (`functions.${system}.mkNteDerivation`) 13 | 14 | # 0.3.1 15 | - improved engine code readability a bit 16 | - exposed `applyTemplate` so that users can do templating inside a single file 17 | 18 | # 0.3.2 19 | - allowed the use of raw paths alongside attrsets in `extraFiles` 20 | 21 | # 0.3.3 22 | - made `applyTemplate` work on a raw entry and return a string 23 | and added an internal `applyTemplateFile` that works as the former did before 24 | - added built-in `passthrough` template 25 | 26 | # 0.3.4 27 | - fixed an issue where `file` wasn't available to the base template if another one inherited from it 28 | 29 | # 0.3.5 30 | - added `{pre,post}{Build,Install}` as passthrough parameters to `mkNteDerivation` 31 | 32 | # 0.3.6 33 | - fixed the above mentioned hooks not having any effect 34 | 35 | # 0.3.7 36 | - added passthrough `meta` parameter to `mkNteDerivation` 37 | -------------------------------------------------------------------------------- /engine.nix: -------------------------------------------------------------------------------- 1 | pkgs: src: {extraArgs, entries, templates}: let 2 | inherit (pkgs) lib; 3 | 4 | inherit (builtins) abort baseNameOf dirOf toString; 5 | inherit (lib.attrsets) hasAttr; 6 | inherit (lib.lists) forEach findFirst; 7 | inherit (lib.path) removePrefix; 8 | inherit (lib.strings) concatMapStrings concatStrings hasSuffix isString removeSuffix; 9 | inherit (lib.trivial) functionArgs; 10 | 11 | inherit (pkgs) writeText; 12 | 13 | templates' = templates ++ [ 14 | (pkgs.writeText "passthrough.nix" /*nix*/'' 15 | { format, output, ... }: { 16 | name = "passthrough"; 17 | inherit format output; 18 | } 19 | '') 20 | ]; 21 | 22 | args = {inherit pkgs getEntry applyTemplate;} 23 | // (import ./stdlib.nix pkgs) 24 | // extraArgs; 25 | 26 | isBaseTemplate = template: 27 | isString template.output; 28 | 29 | findTemplateFn = entry: let 30 | template = findFirst (templateFile: let 31 | templateFn = import templateFile; 32 | template' = templateFn (functionArgs templateFn); 33 | in 34 | template'.name == entry.template) 35 | null 36 | templates'; 37 | in 38 | if template == null then 39 | abort "unknown template `${entry.template}`" 40 | else 41 | (import template); 42 | 43 | applyTemplate = templateFn: entry: let 44 | template = templateFn (args // entry); 45 | in 46 | if isBaseTemplate template then 47 | template.output 48 | else let 49 | newEntry = template.output // {inherit (entry) file;}; 50 | foundTemplateFn = findTemplateFn newEntry; 51 | in 52 | applyTemplate foundTemplateFn newEntry; 53 | 54 | applyTemplateFn = templateFn: entry: { 55 | inherit (entry) file; 56 | output = applyTemplate templateFn entry; 57 | }; 58 | 59 | replaceSuffix = from: to: string: 60 | if !(hasSuffix from string) then 61 | abort "invalid suffix `${from}` for string `${string}`" 62 | else 63 | concatStrings [ (removeSuffix from string) to ]; 64 | 65 | getTemplateFormat = entry: templateFn: let 66 | # getEntry needs to go down to the base template for the format 67 | # but any template through the way can ask for a file 68 | # so we just give it a placeholder - an empty string here 69 | # since the output of this thing doesn't matter 70 | template = templateFn (args // entry // { file = ""; }); 71 | in 72 | if isBaseTemplate template then 73 | template.format 74 | else let 75 | newEntry = template.output; 76 | foundTemplateFn = findTemplateFn newEntry; 77 | in 78 | getTemplateFormat newEntry foundTemplateFn; 79 | 80 | getEntry = entryFile: let 81 | sourceFile = toString (removePrefix src entryFile); 82 | entry = (import entryFile) args; 83 | foundTemplateFn = findTemplateFn entry; 84 | entryFormat = getTemplateFormat entry foundTemplateFn; 85 | in 86 | if !(hasAttr "file" entry) then 87 | entry // { 88 | file = replaceSuffix ".nix" ".${entryFormat}" sourceFile; 89 | } 90 | else 91 | entry; 92 | 93 | processEntryFile = entryFile: let 94 | foundTemplateFn = findTemplateFn entry; 95 | entry = getEntry entryFile; 96 | in 97 | applyTemplateFn foundTemplateFn entry; 98 | 99 | in /*sh*/'' 100 | ${concatMapStrings 101 | (result: /*sh*/'' 102 | mkdir -p $out/${dirOf result.file} 103 | cat ${writeText (baseNameOf result.file) result.output} > $out/${result.file} 104 | '') 105 | (forEach entries processEntryFile) 106 | } 107 | '' 108 | -------------------------------------------------------------------------------- /example/base.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | background-color: #EEEEEE; 8 | color: #222222; 9 | font-size: 1.15rem; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | margin: 0; 14 | height: 100%; 15 | } 16 | 17 | hr { 18 | margin: 1em auto 1em; 19 | width: 90%; 20 | } 21 | 22 | #navigation { 23 | padding-top: 1em; 24 | display: flex; 25 | flex-direction: row; 26 | font-size: 1.5rem; 27 | } 28 | 29 | #navigation:has(hr) hr { 30 | margin: auto 1em auto; 31 | height: 75%; 32 | width: auto; 33 | } 34 | -------------------------------------------------------------------------------- /example/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | mkNteDerivation, 4 | ... 5 | }: let 6 | inherit (lib.attrsets) listToAttrs; 7 | inherit (lib.lists) map; 8 | inherit (lib.strings) replaceStrings toLower; 9 | 10 | extraArgs' = { 11 | h = n: content: let 12 | id = replaceStrings [" " ";"] ["-" "-"] (toLower content); 13 | in /*html*/'' 14 | # ${content} 15 | ''; 16 | }; 17 | in mkNteDerivation { 18 | name = "nte-example"; 19 | version = "0.1.0"; 20 | src = ./.; 21 | 22 | extraArgs = extraArgs' 23 | // listToAttrs (map (n: { 24 | name = "h${toString n}"; 25 | value = text: extraArgs'.h n text; 26 | }) [ 1 2 3 4 5 6 ] 27 | ); 28 | 29 | entries = [ 30 | ./index.nix 31 | ./posts/index.nix 32 | ./posts/test.nix 33 | ]; 34 | 35 | templates = [ 36 | ./templates/base.nix 37 | ./templates/post.nix 38 | ]; 39 | 40 | extraFiles = [ 41 | { source = "./*.css"; destination = "/"; } 42 | { source = "./posts/*.css"; destination = "/posts"; } 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /example/index.nix: -------------------------------------------------------------------------------- 1 | _: { 2 | template = "base"; 3 | 4 | head = /*html*/'' 5 | nte example 6 | ''; 7 | 8 | body = /*html*/'' 9 |

nte example site

10 |

this site was written in nte

11 |

check the sources here

12 | ''; 13 | } 14 | -------------------------------------------------------------------------------- /example/posts/index.css: -------------------------------------------------------------------------------- 1 | #posts { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: flex-start; 5 | width: 65%; 6 | } 7 | 8 | .post-item { 9 | display: flex; 10 | flex-direction: row; 11 | align-items: center; 12 | justify-content: space-between; 13 | width: 100%; 14 | padding: 1em; 15 | } 16 | 17 | .post-item:has(hr) hr { 18 | margin: auto 1em auto; 19 | height: 95%; 20 | width: auto; 21 | } 22 | 23 | .post-title { 24 | display: flex; 25 | flex-direction: row; 26 | align-items: center; 27 | } 28 | -------------------------------------------------------------------------------- /example/posts/index.nix: -------------------------------------------------------------------------------- 1 | { 2 | getEntry, 3 | pkgs, 4 | ... 5 | }: let 6 | inherit (pkgs) lib; 7 | inherit (lib.lists) map; 8 | inherit (lib.strings) concatStrings; 9 | in { 10 | template = "base"; 11 | 12 | head = /*html*/'' 13 | nte posts 14 | 15 | ''; 16 | 17 | body = let 18 | postItem = post: /*html*/'' 19 |
20 |
21 |

${post.title}

22 |
23 |

${post.created}

24 |
25 | ''; 26 | in /*html*/'' 27 |
28 | ${concatStrings (map (postFile: postItem (getEntry postFile)) 29 | [ 30 | ./test.nix 31 | ] 32 | )} 33 |
34 | ''; 35 | } 36 | -------------------------------------------------------------------------------- /example/posts/post.css: -------------------------------------------------------------------------------- 1 | #metadata { 2 | width: 90%; 3 | margin: 2em auto 1em; 4 | text-align: center; 5 | } 6 | 7 | #less-metadata { 8 | color: #555555; 9 | margin: 1em auto 0em; 10 | } 11 | 12 | hr { 13 | width: 75%; 14 | margin: 1em auto 1em; 15 | } 16 | 17 | #content { 18 | max-width: 80ch; 19 | padding-bottom: 2em; 20 | } 21 | 22 | p { 23 | margin: 1em auto 1em; 24 | } 25 | -------------------------------------------------------------------------------- /example/posts/test.nix: -------------------------------------------------------------------------------- 1 | { 2 | h2, 3 | ... 4 | }: { 5 | template = "post"; 6 | 7 | title = "Test post (using nte)"; 8 | author = "poz"; 9 | created = "2024-05-22"; 10 | content = /*html*/'' 11 |

12 | This is a test post on an example nte site. Feel free to read the lorem ipsum below. Its heading was generated using an additional helper function added to extraArgs 13 |

14 | ${h2 "Lorem ipsum"} 15 |

16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a lobortis augue. Suspendisse finibus turpis massa, eget aliquam justo ultricies eu. Aliquam augue elit, vulputate ut elit a, porta imperdiet nisi. Sed suscipit sed eros a maximus. Nullam congue sit amet metus non porta. Nunc iaculis euismod orci, sed cursus eros pellentesque sed. Sed lacinia purus nec magna mattis ullamcorper. In aliquet neque sed est pharetra posuere. Donec mauris turpis, tempus at tempor eget, pellentesque nec nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed volutpat orci eget justo auctor bibendum. Sed elementum odio et odio vulputate congue at eu tortor. 17 |

18 | 19 |

20 | Morbi eget volutpat justo. Maecenas erat ante, interdum non odio nec, sagittis tristique orci. Ut pulvinar sem vitae odio dictum facilisis. Vestibulum sit amet tristique eros. Phasellus ac tortor arcu. In hac habitasse platea dictumst. Fusce laoreet fringilla facilisis. Vestibulum feugiat luctus elit sit amet rhoncus. Vivamus sed orci quis elit pharetra pretium eu a libero. In sed pretium ante, eget dictum augue. 21 |

22 | 23 |

24 | Maecenas odio ligula, vehicula nec eros id, rhoncus auctor ante. Sed sodales est eu sapien sollicitudin placerat. Maecenas consequat est ac condimentum faucibus. Aenean vel augue sed nunc dapibus sollicitudin. Phasellus sed nisi vel nulla aliquet pellentesque. Etiam euismod euismod felis a cursus. Morbi consequat aliquet ex, nec auctor odio varius non. Etiam finibus sem at orci semper, in volutpat odio sagittis. Etiam id mauris sit amet orci sodales tristique eget ut mauris. 25 |

26 | 27 |

28 | Vestibulum magna sem, tempor et ultricies vitae, cursus ut nunc. Ut eleifend dignissim augue, eu feugiat urna maximus nec. Praesent eget pulvinar velit, a condimentum purus. Nunc dapibus, ligula sed efficitur mattis, velit justo pharetra ipsum, eu malesuada erat erat sed orci. Integer orci orci, ultrices in ipsum et, interdum vestibulum dolor. Mauris varius tellus eu erat lobortis, et viverra ante volutpat. Nam nunc est, posuere eget bibendum tincidunt, posuere et sem. Integer egestas aliquam nibh eu viverra. 29 |

30 | 31 |

32 | Morbi in dignissim erat. Aenean non diam at libero eleifend gravida nec et lorem. Ut nibh sapien, blandit sit amet dui id, tincidunt tempor velit. Aliquam cursus libero lacus, eget pretium ligula scelerisque eget. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer tristique metus massa, nec auctor purus imperdiet a. Proin eu turpis quis augue mattis tempus at at dolor. Vestibulum eu justo tincidunt nibh eleifend sollicitudin at ac massa. Praesent sit amet ultricies lorem. Sed eleifend laoreet elit, ac auctor diam finibus vel. Nam iaculis felis neque, eu hendrerit ligula pellentesque in. Sed gravida lacus tempus dictum scelerisque. Ut commodo nec tellus sit amet consequat. 33 |

34 | 35 |

36 | Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras eleifend sit amet arcu id euismod. Suspendisse vel bibendum diam, ut posuere nunc. Cras eget purus sit amet ex consequat laoreet quis eget sapien. Pellentesque vel lacus mollis, fringilla lectus tristique, condimentum justo. Donec orci augue, efficitur eget dolor vitae, venenatis malesuada orci. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc eros enim, viverra sed semper ac, aliquam et odio. Curabitur a venenatis est. Praesent lacinia non enim quis gravida. 37 |

38 | 39 |

40 | Nunc porta risus a nisl commodo, in tempor est tempor. Quisque facilisis varius nunc, ac ullamcorper magna pharetra et. Aenean lectus nulla, blandit quis mi ac, bibendum convallis nunc. Proin scelerisque porttitor turpis, sed pharetra velit ullamcorper quis. Vivamus hendrerit diam ac neque commodo ornare. Curabitur hendrerit a nulla ut auctor. Nulla sed laoreet lorem. Morbi ullamcorper ipsum mollis, lobortis lectus ut, tempus lacus. Proin lobortis dui sapien, eu facilisis dui molestie at. Fusce ut suscipit nisl, nec mollis ex. Curabitur ut cursus mauris. 41 |

42 | 43 |

44 | Nulla vulputate eget sapien eget congue. Proin et gravida urna. In hendrerit posuere dolor, eget efficitur ex aliquam a. Sed tristique lacus sit amet pulvinar dignissim. Quisque volutpat mi ac posuere feugiat. Etiam elementum molestie tortor ut rhoncus. Integer at orci enim. Pellentesque malesuada, neque faucibus aliquet vehicula, ligula neque maximus mauris, non scelerisque nisl libero sit amet nibh. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. 45 |

46 | 47 |

48 | Vivamus at mattis augue, non ullamcorper diam. Ut euismod sed erat ut efficitur. Sed mollis dui sit amet ultricies rutrum. Cras sit amet justo nisl. Aliquam at orci velit. Vestibulum vel dolor ut lectus fermentum scelerisque ut ullamcorper risus. Sed diam nibh, tempor id arcu at, eleifend placerat turpis. 49 |

50 | 51 |

52 | Morbi ut bibendum mi, id finibus odio. Suspendisse commodo leo a nisi porta, sed imperdiet turpis posuere. Mauris interdum neque sit amet metus tristique semper vitae tincidunt ipsum. Phasellus bibendum nulla venenatis nisi aliquam condimentum ac eget metus. Quisque dignissim viverra interdum. Mauris non semper magna. Nam ultricies dui at lacinia fermentum. Quisque sed diam vestibulum, mollis libero eu, sollicitudin elit. Nam quis hendrerit urna, a interdum tortor. Mauris ac augue non neque malesuada eleifend. Etiam accumsan ligula hendrerit, accumsan ante id, faucibus mi. 53 |

54 | ''; 55 | } 56 | -------------------------------------------------------------------------------- /example/templates/base.nix: -------------------------------------------------------------------------------- 1 | { 2 | body, 3 | head, 4 | ... 5 | }: { 6 | name = "base"; 7 | format = "html"; 8 | 9 | output = /*html*/'' 10 | 11 | 12 | 13 | ${head} 14 | 15 | 16 | 21 |
22 | ${body} 23 | 24 | 25 | ''; 26 | } 27 | -------------------------------------------------------------------------------- /example/templates/post.nix: -------------------------------------------------------------------------------- 1 | { 2 | title, 3 | author, 4 | created, 5 | content, 6 | ... 7 | }: { 8 | name = "post"; 9 | 10 | output = { 11 | template = "base"; 12 | 13 | head = /*html*/'' 14 | ${title} 15 | 16 | ''; 17 | body = /*html*/'' 18 |
19 |

${title}

20 |
21 |

${author} | published: ${created}

22 |
23 |
24 | 25 |
26 | 27 |
28 | ${content} 29 |
30 | ''; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1741246872, 6 | "narHash": "sha256-Q6pMP4a9ed636qilcYX8XUguvKl/0/LGXhHcRI91p0U=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "10069ef4cf863633f57238f179a0297de84bd8d3", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "systems": "systems" 23 | } 24 | }, 25 | "systems": { 26 | "locked": { 27 | "lastModified": 1681028828, 28 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 | "owner": "nix-systems", 30 | "repo": "default", 31 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "nix-systems", 36 | "repo": "default", 37 | "type": "github" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Nix Template Engine"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | systems.url = "github:nix-systems/default"; 7 | }; 8 | 9 | outputs = { nixpkgs, systems, ... }: let 10 | forEachSystem = nixpkgs.lib.genAttrs (import systems); 11 | pkgsForEach = nixpkgs.legacyPackages; 12 | enginesForEach = import ./engine.nix; 13 | mkNteDerivationsForEach = import ./nte-drv.nix; 14 | in { 15 | functions = forEachSystem ( 16 | system: let 17 | pkgs = pkgsForEach.${system}; 18 | engine = enginesForEach pkgs; 19 | in { 20 | inherit engine; 21 | mkNteDerivation = mkNteDerivationsForEach pkgs engine; 22 | } 23 | ); 24 | 25 | libs = forEachSystem ( 26 | system: let 27 | pkgs = pkgsForEach.${system}; 28 | in { 29 | default = import ./stdlib.nix pkgs; 30 | } 31 | ); 32 | 33 | examples = forEachSystem ( 34 | system: let 35 | pkgs = pkgsForEach.${system}; 36 | engine = enginesForEach pkgs; 37 | in { 38 | default = import ./example/default.nix { 39 | inherit (pkgs) lib; 40 | mkNteDerivation = mkNteDerivationsForEach pkgs engine; 41 | }; 42 | } 43 | ); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /nte-drv.nix: -------------------------------------------------------------------------------- 1 | pkgs: engine: { 2 | name, 3 | version, 4 | src, 5 | extraArgs ? {}, 6 | entries ? [], 7 | templates ? [], 8 | extraFiles ? [], 9 | preBuild ? "", 10 | postBuild ? "", 11 | preInstall ? "", 12 | postInstall ? "", 13 | meta ? {}, 14 | }: let 15 | inherit (pkgs) lib; 16 | inherit (lib.attrsets) isAttrs; 17 | inherit (lib.lists) forEach init; 18 | inherit (lib.strings) concatStrings concatStringsSep match normalizePath optionalString splitString; 19 | in pkgs.stdenv.mkDerivation { 20 | inherit name version src; 21 | 22 | inherit preBuild; 23 | 24 | buildPhase = /*sh*/'' 25 | runHook preBuild 26 | 27 | ${engine src {inherit extraArgs entries templates;}} 28 | 29 | runHook postBuild 30 | ''; 31 | 32 | inherit postBuild; 33 | 34 | inherit preInstall; 35 | 36 | installPhase = optionalString (extraFiles != []) /*sh*/'' 37 | runHook preInstall 38 | 39 | mkdir -p $out 40 | 41 | ${concatStrings (forEach extraFiles 42 | (extraFile: let 43 | fileAttrs = if isAttrs extraFile 44 | then extraFile 45 | else { source = extraFile; destination = "/"; }; 46 | 47 | isInSubdir = (match ".+/.*" fileAttrs.destination) != null; 48 | outDir = normalizePath "$out/${concatStringsSep "/" (init (splitString "/" fileAttrs.destination))}"; 49 | outPath = normalizePath "$out/${fileAttrs.destination}"; 50 | in /*sh*/'' 51 | ${optionalString isInSubdir /*sh*/"mkdir -p ${outDir}"} 52 | cp -r ${fileAttrs.source} ${outPath} 53 | '')) 54 | } 55 | 56 | runHook postInstall 57 | ''; 58 | 59 | inherit postInstall; 60 | 61 | inherit meta; 62 | } 63 | -------------------------------------------------------------------------------- /stdlib.nix: -------------------------------------------------------------------------------- 1 | pkgs: let 2 | inherit (pkgs) lib runCommand; 3 | inherit (lib.strings) readFile; 4 | in { 5 | run = script: readFile (runCommand "run" {} '' 6 | echo $(${script}) > $out 7 | ''); 8 | } 9 | --------------------------------------------------------------------------------