about summary refs log tree commit diff
path: root/users/sterni/htmlman/default.nix
{ depot, lib, pkgs, ... }:

let
  inherit (depot.nix)
    getBins
    runExecline
    yants
    ;

  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
    , linkXr ? "all"
    # How to handle cross references in the html output:
    #
    # * none:     don't convert cross references into hyperlinks
    # * all:      link all cross references as if they were
    #             rendered into $out by htmlman
    # * inManDir: link to all man pages which have their source
    #             in `manDir` and use the format string defined
    #             in linkXrFallback for all other cross references.
    , linkXrFallback ? "https://manpages.debian.org/unstable/%N.%S.en.html"
    # fallback link to use if linkXr == "inManDir" and the man
    # page is not in ${manDir}. Placeholders %N (name of page)
    # and %S (section of page) can be used. See mandoc(1) for
    # more information.
    }:

    let
      linkXrEnum = yants.enum "linkXr" [ "all" "inManDir" "none" ];

      index = indexTemplate {
        inherit title description pages;
      };

      resolvePath = { path ? null, name, section }:
        if path != null
        then path
        else "${manDir}/${name}.${toString section}";

      mandocOpts = lib.concatStringsSep "," ([
        "style=style.css"
      ] ++ linkXrEnum.match linkXr {
        all      = [ "man=./%N.%S.html" ];
        inManDir = [ "man=./%N.%S.html;${linkXrFallback}" ];
        none     = [ ];
      });

      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"
            ])
          ])
          # let mandoc check for available man pages
          "execline-cd" "${manDir}"
        ] ++ lib.concatMap ({ name, section, ... }@p:
          execlineStdoutInto "\${out}/${name}.${toString section}.html" [
          "if" [
            bins.mandoc
            "-mdoc"
            "-T" "html"
            "-O" mandocOpts
            (resolvePath p)
          ]
        ]) pages);
    in html // {
      deploy = deployScript title html;
    };
in
  htmlman