about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsterni <sternenseemann@systemli.org>2021-03-10T14·37+0100
committersterni <sternenseemann@systemli.org>2021-03-11T13·12+0000
commit2cd2b58a04cd86e8bf1d72e9c0a67ad8c8e9c8dd (patch)
tree3f31e27a700d93ec79bd2e130dc98ebc1eda024e
parente41bd6a82de9f9cba1783fc26bb71b4c97b1953a (diff)
feat(users/sterni/htmlman): static site generator for manual pages r/2276
htmlman is a very simple nix based static site generator which is
intended for rendering HTML representations for man pages plus an index
page listing all available pages. For the sake of simplicity (and unlike
previous iterations of this piece of code) other documentation artifacts
and formats are not supported.

Usually web services like GitHub and depot's web interface are pretty
good at displaying "normal" documentation artifacts like markdown files,
but man pages are usually not rendered — with the additional problem
that it's source is virtually unreadable. htmlman should provide a
simple static site generator which can be plugged into GitHub actions or
the like to automatically generate rendered version of man pages tracked
in version control.

Change-Id: Ib53292964b3ff84c32d70c5fde257a2edb8c2122
Reviewed-on: https://cl.tvl.fyi/c/depot/+/2596
Tested-by: BuildkiteCI
Reviewed-by: tazjin <mail@tazj.in>
Reviewed-by: Profpatsch <mail@profpatsch.de>
Reviewed-by: sterni <sternenseemann@systemli.org>
-rw-r--r--third_party/nixpkgs-exposed/OWNERS1
-rw-r--r--third_party/nixpkgs-exposed/exposed/default.nix1
-rw-r--r--users/sterni/htmlman/README.md36
-rw-r--r--users/sterni/htmlman/default.nix204
-rw-r--r--users/sterni/htmlman/defaultStyle.nix49
5 files changed, 291 insertions, 0 deletions
diff --git a/third_party/nixpkgs-exposed/OWNERS b/third_party/nixpkgs-exposed/OWNERS
index 32f9ac2499..a5ea8a2ae8 100644
--- a/third_party/nixpkgs-exposed/OWNERS
+++ b/third_party/nixpkgs-exposed/OWNERS
@@ -4,3 +4,4 @@
 inherited: true
 owners:
   - Profpatsch
+  - sterni
diff --git a/third_party/nixpkgs-exposed/exposed/default.nix b/third_party/nixpkgs-exposed/exposed/default.nix
index 6f4441879a..61ae3ff677 100644
--- a/third_party/nixpkgs-exposed/exposed/default.nix
+++ b/third_party/nixpkgs-exposed/exposed/default.nix
@@ -84,6 +84,7 @@
     lutris
     makeFontsConf
     makeWrapper
+    mandoc
     mdbook
     meson
     mime-types
diff --git a/users/sterni/htmlman/README.md b/users/sterni/htmlman/README.md
new file mode 100644
index 0000000000..258233d4c4
--- /dev/null
+++ b/users/sterni/htmlman/README.md
@@ -0,0 +1,36 @@
+# htmlman
+
+static site generator for man pages intended for
+rendering man page documentation viewable using
+a web browser.
+
+## usage
+
+If you have a nix expression, `doc.nix`, like this:
+
+```nix
+{ depot, ... }:
+
+depot.users.sterni.htmlman {
+  title = "foo project";
+  pages = [
+    {
+      name = "foo";
+      section = 1;
+    }
+    {
+      name = "foo";
+      section = 3;
+      path = ../devman/foo.3;
+    }
+  ];
+  manDir = ../man;
+}
+```
+
+You can run the following to directly deploy the resulting
+documentation output to a specific target directory:
+
+```sh
+nix-build -A deploy doc.nix && ./result target_directory
+```
diff --git a/users/sterni/htmlman/default.nix b/users/sterni/htmlman/default.nix
new file mode 100644
index 0000000000..c528a29006
--- /dev/null
+++ b/users/sterni/htmlman/default.nix
@@ -0,0 +1,204 @@
+{ depot, lib, pkgs, ... }:
+
+let
+  inherit (depot.nix)
+    getBins
+    runExecline
+    ;
+
+  inherit (depot.tools)
+    cheddar
+    ;
+
+  inherit (pkgs)
+    mandoc
+    coreutils
+    fetchurl
+    writers
+    ;
+
+  bins = getBins cheddar [ "cheddar" ]
+      // getBins mandoc [ "mandoc" ]
+      // getBins coreutils [ "cat" "mv" "mkdir" ]
+      ;
+
+  normalizeDrv = fetchurl {
+    url = "https://necolas.github.io/normalize.css/8.0.1/normalize.css";
+    sha256 = "04jmvybwh2ks4dlnfa70sb3a3z3ig4cv0ya9rizjvm140xq1h22q";
+  };
+
+  execlineStdoutInto = target: line: [
+    "redirfd" "-w" "1" target
+  ] ++ line;
+
+  # I will not write a pure nix markdown renderer
+  # I will not write a pure nix markdown renderer
+  # I will not write a pure nix markdown renderer
+  # I will not write a pure nix markdown renderer
+  # I will not write a pure nix markdown renderer
+  markdown = md:
+    let
+      html = runExecline.local "rendered-markdown" {
+        stdin = md;
+      } ([
+        "importas" "-iu" "out" "out"
+      ] ++ execlineStdoutInto "$out" [
+        bins.cheddar "--about-filter" "description.md"
+      ]);
+    in builtins.readFile html;
+
+  indexTemplate = { title, description, pages ? [] }: ''
+    <!doctype html>
+    <html>
+      <head>
+        <meta charset="utf-8">
+        <title>${title}</title>
+        <link rel="stylesheet" type="text/css" href="style.css"/>
+      </head>
+      <body>
+        <div class="index-text">
+          <h1>${title}</h1>
+          ${markdown description}
+          <h2>man pages</h2>
+          <ul>
+            ${lib.concatMapStrings ({ name, section, ... }: ''
+              <li><a href="${name}.${toString section}.html">${name}(${toString section})</a></li>
+            '') pages}
+          </ul>
+        </div>
+      </body>
+    </html>
+  '';
+
+  defaultStyle = import ./defaultStyle.nix { };
+
+  # This deploy script automatically copies the build result into
+  # a TARGET directory and marks it as writeable optionally.
+  # It is exposed as the deploy attribute of the result of
+  # htmlman, so an htmlman expression can be used like this:
+  # nix-build -A deploy htmlman.nix && ./result target_dir
+  deployScript = title: drv: writers.writeDash "deploy-${title}" ''
+    usage() {
+      printf 'Usage: %s [-w] TARGET\n\n' "$0"
+      printf 'Deploy htmlman documentation to TARGET directory.\n\n'
+      printf '  -h    Display this help message\n'
+      printf '  -w    Make TARGET directory writeable\n'
+    }
+
+    if test "$#" -lt 1; then
+      usage
+      exit 100
+    fi
+
+    writeable=false
+
+    while test "$#" -gt 0; do
+      case "$1" in
+        -h)
+          usage
+          exit 0
+          ;;
+        -w)
+          writeable=true
+          ;;
+        -*)
+          usage
+          exit 100
+          ;;
+        *)
+          if test -z "$target"; then
+            target="$1"
+          else
+            echo "Too many arguments"
+            exit 100
+          fi
+          ;;
+      esac
+
+      shift
+    done
+
+    if test -z "$target"; then
+      echo "Missing TARGET"
+      usage
+      exit 100
+    fi
+
+    set -ex
+
+    mkdir -p "$target"
+    cp -RTL --reflink=auto "${drv}" "$target"
+
+    if $writeable; then
+      chmod -R +w "$target"
+    fi
+  '';
+
+  htmlman =
+    { title
+    # title of the index page
+    , description ? ""
+    # description which is displayed after
+    # the main heading on the index page
+    , pages ? []
+    # man pages of the following structure:
+    # {
+    #   name : string;
+    #   section : int;
+    #   path : either path string;
+    # }
+    # path is optional, if it is not given,
+    # the man page source must be located at
+    # "${manDir}/${name}.${toString section}"
+    , manDir ? null
+    # directory in which man page sources are located
+    , style ? defaultStyle
+    # CSS to use as a string
+    , normalizeCss ? true
+    # whether to include normalize.css before the custom CSS
+    }:
+
+    let
+      index = indexTemplate {
+        inherit title description pages;
+      };
+      resolvePath = { path ? null, name, section }:
+        if path != null
+        then path
+        else "${manDir}/${name}.${toString section}";
+      html =
+        runExecline.local "htmlman-${title}" {
+          derivationArgs = {
+            inherit index style;
+            passAsFile = [ "index" "style" ];
+          };
+        } ([
+          "multisubstitute" [
+            "importas" "-iu" "out" "out"
+            "importas" "-iu" "index" "indexPath"
+            "importas" "-iu" "style" "stylePath"
+          ]
+          "if" [ bins.mkdir "-p" "$out" ]
+          "if" [ bins.mv "$index" "\${out}/index.html" ]
+          "if" (execlineStdoutInto "\${out}/style.css" [
+            "if" ([
+              bins.cat
+            ] ++ lib.optional normalizeCss normalizeDrv
+              ++ [
+              "$style"
+            ])
+          ])
+        ] ++ lib.concatMap ({ name, section, ... }@p:
+          execlineStdoutInto "\${out}/${name}.${toString section}.html" [
+          "if" [
+            bins.mandoc
+            "-T" "html" "-mdoc"
+            "-O" "style=style.css"
+            (resolvePath p)
+          ]
+        ]) pages);
+    in html // {
+      deploy = deployScript title html;
+    };
+in
+  htmlman
diff --git a/users/sterni/htmlman/defaultStyle.nix b/users/sterni/htmlman/defaultStyle.nix
new file mode 100644
index 0000000000..a44b5ef069
--- /dev/null
+++ b/users/sterni/htmlman/defaultStyle.nix
@@ -0,0 +1,49 @@
+{ ... }:
+
+''
+  body {
+    font-size: 1em;
+    line-height: 1.5;
+    font-family: serif;
+    background-color: #efefef;
+  }
+
+  h1, h2, h3, h4, h5, h6 {
+    font-family: sans-serif;
+    font-size: 1em;
+    margin: 5px 0;
+  }
+
+  h1 {
+    margin-top: 0;
+  }
+
+  a:link, a:visited {
+    color: #3e7eff;
+  }
+
+  h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
+    text-decoration: none;
+  }
+
+  .manual-text, .index-text {
+    padding: 20px;
+    max-width: 800px;
+    background-color: white;
+    margin: 0 auto;
+  }
+
+  table.head, table.foot {
+    display: none;
+  }
+
+  .Nd {
+    display: inline;
+  }
+
+  /* use same as cheddar for man pages */
+  pre {
+    padding: 16px;
+    background-color: #f6f8fa;
+  }
+''