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