{ 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" ] ; # / toplevel = [ { route = [ "notes" ]; name = "Notes"; page = router; } { route = [ "projects" ]; name = "Projects"; # page = projects; } ]; # /notes/* notes = [ { route = [ "notes" "preventing-oom" ]; name = "Preventing out-of-memory (OOM) errors on Linux"; page = renderNote "preventing-oom" ./notes/preventing-oom.md; } { route = [ "notes" "rust-string-conversions" ]; name = "Converting between different String types in Rust"; page = renderNote "rust-string-conversions" ./notes/rust-string-conversions.md; } ]; 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"; } ]; # convert a note to html via lowdown renderNote = name: note: depot.nix.runExecline "${name}.html" {} [ "importas" "out" "out" bins.lowdown "-s" "-Thtml" "-o" "$out" note ]; # all notes with `route` converted to an absolute path notesFullRoute = lib.pipe notes [ (map (x@{route, ...}: x // { route = mkRoute route; })) ]; # a cdb from route to a netencoded version of data for each route router = lib.pipe notesFullRoute [ (map (x: { name = x.route; value = depot.users.Profpatsch.netencode.gen.dwim x; })) lib.listToAttrs (cdbMake "notes-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 + "/" + relativePath)) "depotCgitLink: path /${relativePath} does not exist in depot"); "https://code.tvl.fyi/tree/${relativePath}"; # look up a route by path ($1) router-lookup = depot.nix.writeExecline "router-lookup" { readNArgs = 1; } [ cdbLookup router "$1" ]; runExeclineStdout = name: args: cmd: depot.nix.runExecline name args ([ "importas" "-ui" "out" "out" "redirfd" "-w" "1" "$out" ] ++ cmd); notes-index-html = let o = notesFullRoute; in '' ''; notes-index = runExeclineStdout "notes-index" { stdin = depot.users.Profpatsch.netencode.gen.dwim notesFullRoute; } [ "withstdinas" "-in" "TEMPLATE_DATA" "pipeline" [ bins.printf '' '' ] depot.users.Profpatsch.netencode.netencode-mustache ]; # 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 = runExeclineStdout "projects-index" { stdin = depot.users.Profpatsch.netencode.gen.dwim projects; } [ "withstdinas" "-in" "TEMPLATE_DATA" "pipeline" [ bins.printf ''
{{#.}}
{{name}}
{{{description}}}
{{/.}}
'' ] depot.users.Profpatsch.netencode.netencode-mustache ]; 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 = { 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 ] # TODO: ignore potential query arguments. See 404 message "pipeline" [ router-lookup "$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.utils.drvTargets { inherit router depotCgitLink site-server notes-index notes-index-html projects-index projects-index-html router-lookup ; }