{ depot, pkgs, lib, ... }: let bins = depot.nix.getBins pkgs.lowdown [ "lowdown" ] // depot.nix.getBins pkgs.cdb [ "cdbget" "cdbmake" "cdbdump" ] // depot.nix.getBins pkgs.coreutils [ "mv" "cat" "printf" "test" ] // depot.nix.getBins pkgs.s6-networking [ "s6-tcpserver" ] // depot.nix.getBins pkgs.time [ "time" ] ; # / # TODO: use toplevel = [ { route = [ "notes" ]; name = "Notes"; page = { cssFile }: router cssFile; } { route = [ "projects" ]; name = "Projects"; # page = projects; } ]; # /notes/* notes = [ { route = [ "notes" "an-idealized-conflang" ]; name = "An Idealized Configuration Language"; page = { cssFile }: markdownToHtml { name = "an-idealized-conflang"; markdown = ./notes/an-idealized-conflang.md; inherit cssFile; }; } { route = [ "notes" "rust-string-conversions" ]; name = "Converting between different String types in Rust"; page = { cssFile }: markdownToHtml { name = "rust-string-conversions"; markdown = ./notes/rust-string-conversions.md; inherit cssFile; }; } { route = [ "notes" "preventing-oom" ]; name = "Preventing out-of-memory (OOM) errors on Linux"; page = { cssFile }: markdownToHtml { name = "preventing-oom"; markdown = ./notes/preventing-oom.md; inherit cssFile; }; } ]; projects = [ { name = "lorri"; description = "nix-shell replacement for projects"; link = "https://github.com/nix-community/lorri"; } { name = "netencode"; description = ''A human-readble nested data exchange format inspired by netstrings and bencode.''; link = depotCgitLink { relativePath = "users/Profpatsch/netencode/README.md"; }; } { name = "yarn2nix"; description = ''nix dependency generator for the yarn Javascript package manager''; link = "https://github.com/Profpatsch/yarn2nix"; } ]; posts = [ { date = "2017-05-04"; title = "Ligature Emulation in Emacs"; subtitle = "It’s not pretty, but the results are"; description = "How to set up ligatures using prettify-symbols-mode and the Hasklig/FiraCode fonts."; page = { cssFile }: markdownToHtml { name = "2017-05-04-ligature-emluation-in-emacs"; markdown = ./posts/2017-05-04-ligature-emulation-in-emacs.md; inherit cssFile; }; route = [ "posts" "2017-05-04-ligature-emluation-in-emacs" ]; tags = [ "emacs" ]; } ]; # convert a markdown file to html via lowdown markdownToHtml = { name , # the file to convert markdown , # css file to add to the final result, as { route } cssFile }: depot.nix.runExecline "${name}.html" { } ([ "importas" "out" "out" (depot.users.Profpatsch.lib.debugExec "") bins.lowdown "-s" "-Thtml" ] ++ (lib.optional (cssFile != null) ([ "-M" "css=${mkRoute cssFile.route}" ])) ++ [ "-o" "$out" markdown ]); # takes a { route … } attrset and converts the route lists to an absolute path fullRoute = attrs: lib.pipe attrs [ (map (x@{ route, ... }: x // { route = mkRoute route; })) ]; # a cdb from route to a netencoded version of data for each route router = cssFile: lib.pipe (notes ++ posts) [ (map (r: with depot.users.Profpatsch.lens; lib.pipe r [ (over (field "route") mkRoute) (over (field "page") (_ { inherit cssFile; })) ])) (map (x: { name = x.route; value = depot.users.Profpatsch.netencode.gen.dwim x; })) lib.listToAttrs (cdbMake "router") ]; # Create a link to the given source file/directory, given the relative path in the depot repo. # Checks that the file exists at evaluation time. depotCgitLink = { # relative path from the depot root (without leading /). relativePath }: assert (lib.assertMsg (builtins.pathExists (depot.path.origSrc + "/${relativePath}")) "depotCgitLink: path /${relativePath} does not exist in depot, and depot.path was ${toString depot.path}"); "https://code.tvl.fyi/tree/${relativePath}"; # look up a route by path ($1) router-lookup = cssFile: depot.nix.writeExecline "router-lookup" { readNArgs = 1; } [ cdbLookup (router cssFile) "$1" ]; runExeclineStdout = name: args: cmd: depot.nix.runExecline name args ([ "importas" "-ui" "out" "out" "redirfd" "-w" "1" "$out" ] ++ cmd); notes-index-html = let o = fullRoute notes; in '' ''; notes-index = pkgs.writeText "notes-index.html" notes-index-html; # A simple mustache-inspired string interpolation combinator # that takes an object and a template (a function from o to string) # and returns a string. scope = o: tpl: if builtins.typeOf o == "list" then lib.concatMapStringsSep "\n" tpl o else if builtins.typeOf o == "set" then tpl o else throw "${lib.generators.toPretty {} o} not allowed in template"; # string-escape html (TODO) str = s: s; # html-escape (TODO) esc = s: s; html = s: s; projects-index-html = let o = projects; in ''
${scope o (o: ''
${esc o.name}
${html o.description}
'')}
''; projects-index = pkgs.writeText "projects-index.html" projects-index-html; posts-index-html = let o = fullRoute posts; in ''
${scope o (o: ''
${str o.date} ${esc o.title}
${html o.description}
'')}
''; posts-index = pkgs.writeText "projects-index.html" posts-index-html; arglibNetencode = val: depot.nix.writeExecline "arglib-netencode" { } [ "export" "ARGLIB_NETENCODE" (depot.users.Profpatsch.netencode.gen.dwim val) "$@" ]; # A simple http server that serves the site. Yes, it’s horrible. site-server = { cssFile, port }: depot.nix.writeExecline "blog-server" { } [ (depot.users.Profpatsch.lib.runInEmptyEnv [ "PATH" ]) bins.s6-tcpserver "127.0.0.1" port bins.time "--format=time: %es" "--" runOr return400 "pipeline" [ (arglibNetencode { what = "request"; }) depot.users.Profpatsch.read-http ] depot.users.Profpatsch.netencode.record-splice-env runOr return500 "importas" "-i" "path" "path" "if" [ depot.tools.eprintf "GET \${path}\n" ] runOr return404 "backtick" "-ni" "TEMPLATE_DATA" [ # TODO: factor this out of here, this is routing not serving "ifelse" [ bins.test "$path" "=" "/notes" ] [ "export" "content-type" "text/html" "export" "serve-file" notes-index depot.users.Profpatsch.netencode.env-splice-record ] "ifelse" [ bins.test "$path" "=" "/projects" ] [ "export" "content-type" "text/html" "export" "serve-file" projects-index depot.users.Profpatsch.netencode.env-splice-record ] "ifelse" [ bins.test "$path" "=" "/posts" ] [ "export" "content-type" "text/html" "export" "serve-file" posts-index depot.users.Profpatsch.netencode.env-splice-record ] # TODO: ignore potential query arguments. See 404 message "pipeline" [ (router-lookup cssFile) "$path" ] depot.users.Profpatsch.netencode.record-splice-env "importas" "-ui" "page" "page" "export" "content-type" "text/html" "export" "serve-file" "$page" depot.users.Profpatsch.netencode.env-splice-record ] runOr return500 "if" [ "pipeline" [ bins.printf '' HTTP/1.1 200 OK Content-Type: {{{content-type}}}; charset=UTF-8 Connection: close '' ] depot.users.Profpatsch.netencode.netencode-mustache ] "pipeline" [ "importas" "t" "TEMPLATE_DATA" bins.printf "%s" "$t" ] depot.users.Profpatsch.netencode.record-splice-env "importas" "-ui" "serve-file" "serve-file" bins.cat "$serve-file" ]; # run argv or $1 if argv returns a failure status code. runOr = depot.nix.writeExecline "run-or" { readNArgs = 1; } [ "foreground" [ "$@" ] "importas" "?" "?" "ifelse" [ bins.test "$?" "-eq" "0" ] [ ] "if" [ depot.tools.eprintf "runOr: exited \${?}, running \${1}\n" ] "$1" ]; return400 = depot.nix.writeExecline "return400" { } [ bins.printf "%s" '' HTTP/1.1 400 Bad Request Content-Type: text/plain; charset=UTF-8 Connection: close '' ]; return404 = depot.nix.writeExecline "return404" { } [ bins.printf "%s" '' HTTP/1.1 404 Not Found Content-Type: text/plain; charset=UTF-8 Connection: close This page doesn’t exist! Query arguments are not handled at the moment. '' ]; return500 = depot.nix.writeExecline "return500" { } [ bins.printf "%s" '' HTTP/1.1 500 Internal Server Error Content-Type: text/plain; charset=UTF-8 Connection: close Encountered an internal server error. Please try again. '' ]; capture-stdin = depot.nix.writers.rustSimple { name = "capture-stdin"; dependencies = [ depot.users.Profpatsch.execline.exec-helpers ]; } '' extern crate exec_helpers; use std::io::Read; fn main() { let (args, prog) = exec_helpers::args_for_exec("capture-stdin", 1); let valname = &args[1]; let mut v : Vec = vec![]; std::io::stdin().lock().read_to_end(&mut v).unwrap(); exec_helpers::exec_into_args("capture-stdin", prog, vec![(valname, v)]); } ''; # go from a list of path elements to an absolute route string mkRoute = route: "/" + lib.concatMapStringsSep "/" urlencodeAscii route; # urlencodes, but only ASCII characters # https://en.wikipedia.org/wiki/Percent-encoding urlencodeAscii = urlPiece: let raw = [ "!" "#" "$" "%" "&" "'" "(" ")" "*" "+" "," "/" ":" ";" "=" "?" "@" "[" "]" ]; enc = [ "%21" "%23" "%24" "%25" "%26" "%27" "%28" "%29" "%2A" "%2B" "%2C" "%2F" "%3A" "%3B" "%3D" "%3F" "%40" "%5B" "%5D" ]; rest = [ "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_" "." "~" ]; in assert lib.assertMsg (lib.all (c: builtins.elem c (raw ++ rest)) (lib.stringToCharacters urlPiece)) "urlencodeAscii: the urlPiece must only contain valid url ASCII characters, was: ${urlPiece}"; builtins.replaceStrings raw enc urlPiece; # create a cdb record entry, as required by the cdbmake tool cdbRecord = key: val: "+${toString (builtins.stringLength key)},${toString (builtins.stringLength val)}:" + "${key}->${val}\n"; # create a full cdbmake input from an attribute set of keys to values (strings) cdbRecords = with depot.nix.yants; defun [ (attrs (either drv string)) string ] (attrs: (lib.concatStrings (lib.mapAttrsToList cdbRecord attrs)) + "\n"); # run cdbmake on a list of key/value pairs (strings cdbMake = name: attrs: depot.nix.runExecline "${name}.cdb" { stdin = cdbRecords attrs; } [ "importas" "out" "out" depot.users.Profpatsch.lib.eprint-stdin "if" [ bins.cdbmake "db" "tmp" ] bins.mv "db" "$out" ]; # look up a key ($2) in the given cdb ($1) cdbLookup = depot.nix.writeExecline "cdb-lookup" { readNArgs = 2; } [ # cdb ($1) on stdin "redirfd" "-r" "0" "$1" # key ($2) lookup bins.cdbget "$2" ]; in depot.nix.readTree.drvTargets { inherit router depotCgitLink site-server notes-index notes-index-html projects-index projects-index-html posts-index-html ; }