about summary refs log tree commit diff
path: root/users/sterni/nix/html/README.md
diff options
context:
space:
mode:
authorsterni <sternenseemann@systemli.org>2021-08-25T00·45+0200
committersterni <sternenseemann@systemli.org>2021-08-26T15·34+0000
commit9ed439bfbddee7915c4011f8a6ba7562b3375ac8 (patch)
tree6343c44bb40676d684aceec57bab7b26388f7914 /users/sterni/nix/html/README.md
parent17d78867bbef8a3df1271137a2db18b3584cdc39 (diff)
feat(users/sterni/nix): cursed nix html DSL r/2784
Couldn't sleep, so I made a surprisingly neat way to render HTML
documents in Nix using our favorite feature __findFile:

  let
    inherit (depot.users.sterni.nix.html) __findFile esc;

  in

  <html> {} [
    (<head> {} [
      (<meta> { charset = "utf-8"; } null)
      (<title> {} (esc "hello"))
    ])
    (<body> {} [
      (<h1> {} (esc "hello world"))
    ])
  ]

=> "<html><head><meta charset=\"utf-8\"/><title>hello</title></head><body><h1>hello world</h1></body></html>"

Change-Id: Id36808a56ae3da3b5263c06f29342fc22d105c21
Reviewed-on: https://cl.tvl.fyi/c/depot/+/3410
Tested-by: BuildkiteCI
Reviewed-by: tazjin <mail@tazj.in>
Diffstat (limited to 'users/sterni/nix/html/README.md')
-rw-r--r--users/sterni/nix/html/README.md148
1 files changed, 148 insertions, 0 deletions
diff --git a/users/sterni/nix/html/README.md b/users/sterni/nix/html/README.md
new file mode 100644
index 000000000000..0349e466a166
--- /dev/null
+++ b/users/sterni/nix/html/README.md
@@ -0,0 +1,148 @@
+# html.nix — _the_ most cursed Nix HTML DSL
+
+A quick example to show you what it looks like:
+
+```nix
+# Note: this example is for standalone usage out of depot
+{ pkgs ? import <nixpkgs> {} }:
+
+let
+  # zero dependency, one file implementation
+  htmlNix = import ./path/to/html.nix { };
+
+  # make the magic work
+  inherit (htmlNix) __findFile esc withDoctype;
+in
+
+pkgs.writeText "example.html" (withDoctype (<html> {} [
+  (<head> {} [
+    (<meta> { charset = "utf-8"; } null)
+    (<title> {} (esc "hello world"))
+  ])
+  (<body> {} [
+    (<h1> {} (esc "hello world"))
+    (<p> { class = "intro"; } (esc ''
+      welcome to the land of sillyness!
+    ''))
+    (<ul> {} [
+      (<li> {} [
+        (esc "check out ")
+        (<a> { href = "https://code.tvl.fyi"; } "depot")
+      ])
+      (<li> {} [
+        (esc "find ")
+        (<a> { href = "https://cl.tvl.fyi/q/hashtag:cursed"; } "cursed things")
+      ])
+    ])
+  ])
+]))
+```
+
+Convince yourself it works:
+
+```console
+$ $BROWSER $(nix-build example.nix)
+```
+
+Alternatively, in depot:
+
+```console
+$ $BROWSER $(nix-build -A users.sterni.nix.html.tests)
+```
+
+## Creating tags
+
+An empty tag is passed `null` as its content argument:
+
+```nix
+<link> {
+  rel = "stylesheet";
+  href = "/main.css";
+  type = "text/css";
+} null
+
+# => "<link href=\"/main.css\" rel=\"stylesheet\" type=\"text/css\"/>"
+```
+
+Content is expected to be HTML:
+
+```nix
+<div> { class = "foo"; } "<strong>hi</strong>"
+
+# => "<div class=\"foo\"><strong>hi</strong></div>"
+```
+
+If it's not, be sure to escape it:
+
+```nix
+<p> {} (esc "A => B")
+
+# => "<p>A =&gt; B</p>"
+```
+
+Nesting tags works of course:
+
+```nix
+<div> {} (<strong> {} (<em> {} "hi"))
+
+# => "<div><strong><em>hi</em></strong></div>"
+```
+
+If the content of a tag is a list, it's concatenated:
+
+```nix
+<h1> {} [
+  (esc "The ")
+  (<strong> {} "Nix")
+  (esc " ")
+  (<em> {} "Expression")
+  (esc " Language")
+]
+
+# => "<h1>The <strong>Nix</strong> <em>Expression</em> Language</h1>"
+```
+
+More detailed documentation can be found in `nixdoc`-compatible
+comments in the source file (`default.nix` in this directory).
+
+## How does this work?
+
+*Theoretically* expressions like `<nixpkgs>` are just ordinary paths —
+their actual value is determined from `NIX_PATH`. `html.nix` works
+because of how this is actually implemented: At [parse time][spath-parsing]
+Nix transparently translates an expression like `<foo>` into
+`__findFile __nixPath "foo"`:
+
+```
+nix-repl> <nixpkgs>
+/nix/var/nix/profiles/per-user/root/channels/vuizvui/nixpkgs
+
+nix-repl> __findFile __nixPath "nixpkgs"
+/nix/var/nix/profiles/per-user/root/channels/vuizvui/nixpkgs
+```
+
+This translation doesn't take any scoping issues into account --
+so we can just shadow `__findFile` and make it return anything,
+even a function:
+
+```
+nix-repl> __findFile = nixPath: str:
+            /**/ if str == "double" then x: x * 2
+            else if str == "triple" then x: x * 3
+            else throw "what?"
+
+nix-repl> <double> 2
+4
+
+nix-repl> <triple> 3
+9
+
+nix-repl> <quadruple> 4
+error: what?
+```
+
+Exactly this is what we are doing in `html.nix`:
+Using `let inherit (htmlNix) __findFile; in` we shadow the builtin `__findFile`
+with a function which returns a function rendering a particular HTML tag.
+
+[spath-parsing]: https://github.com/NixOS/nix/blob/293220bed5a75efc963e33c183787e87e55e28d9/src/libexpr/parser.y#L410-L416