build.nix

{ pkgs, h, modulePath ? [ "services" "tailscale" ] }:

let
  moduleName = builtins.concatStringsSep "." modulePath;

  eval = import (pkgs.path + "/nixos") {
    configuration = { };
    system = pkgs.stdenv.hostPlatform.system;
  };

  options = import ./options.nix {
    inherit (pkgs) lib;
    inherit eval modulePath;
  };

  components = import ./components.nix { inherit h; };

  css = builtins.readFile ./style.css;

  page = h.renderPretty [ "html" { lang = "en"; }
    [ "head"
      [ "meta" { charset = "utf-8"; } ]
      [ "meta" { name = "viewport"; content = "width=device-width, initial-scale=1"; } ]
      [ "title" "${moduleName} - NixOS Options" ]
      [ "style" (h.raw css) ]
    ]
    [ "body"
      (components.header moduleName "NixOS module configuration options")
      [ "main" (map components.optionCard options) ]
      (components.footer "Generated from nixpkgs using niccup")
    ]
  ];

in pkgs.runCommand "docs-${builtins.replaceStrings ["."] ["-"] moduleName}" {} ''
  mkdir -p $out
  cp ${pkgs.writeText "index.html" page} $out/index.html
''

components.nix

{ h }:

let
  formatValue = v:
    if v == null then null
    else if builtins.isBool v then (if v then "true" else "false")
    else if builtins.isString v then ''"${v}"''
    else if builtins.isList v then "[${builtins.concatStringsSep ", " (map formatValue v)}]"
    else builtins.toJSON v;

in {
  header = title: subtitle: [ "header.header"
    [ "h1" title ]
    [ "p" subtitle ]
  ];

  optionCard = opt:
    let
      default = formatValue opt.default;
      example = formatValue opt.example;
    in [ "article.option"
      [ "div.option-header"
        [ "span.option-name" opt.name ]
        [ "span.option-type" opt.type ]
      ]
      (if opt.description != null then [ "p.option-desc" opt.description ] else [])
      (if default != null || example != null
        then [ "dl.option-meta"
          (if default != null then [ [ "dt" "Default:" ] [ "dd" [ "code" default ] ] ] else [])
          (if example != null then [ [ "dt" "Example:" ] [ "dd" [ "code" example ] ] ] else [])
        ]
        else [])
    ];

  footer = text: [ "footer.footer" [ "p" text ] ];
}

options.nix

{ lib, eval, modulePath }:

let
  allAttrs = lib.attrByPath modulePath {} eval.options;

  isOption = v: builtins.isAttrs v && v._type or null == "option";

  optionNames = builtins.filter (n: isOption allAttrs.${n}) (builtins.attrNames allAttrs);

  extractValue = attr:
    if builtins.isAttrs attr && attr ? value then attr.value
    else attr;

  simplifyValue = v:
    if v == null then null
    else if builtins.isBool v || builtins.isInt v || builtins.isFloat v then v
    else if builtins.isString v then v
    else if builtins.isList v then
      let simplified = map simplifyValue v;
      in if builtins.all (x: x != null) simplified then simplified else null
    else null;

  getInfo = name:
    let opt = allAttrs.${name}; in {
      inherit name;
      description = opt.description.text or opt.description or null;
      type = opt.type.name or opt.type.description or "unknown";
      default = if opt ? default then simplifyValue (extractValue opt.default) else null;
      example = if opt ? example then simplifyValue (extractValue opt.example) else null;
    };

in map getInfo optionNames