# adapted from: https://discourse.nixos.org/t/franken-script-to-generate-nixos-options-docs-with-custom-modules/1674/4 { pkgs, options }: let extraSources = [ ]; lib = pkgs.lib; optionsListVisible = lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList options); # Replace functions by the string substFunction = x: if builtins.isAttrs x then lib.mapAttrs (name: substFunction) x else if builtins.isList x then map substFunction x else if lib.isFunction x then "" else if isPath x then toString x else x; isPath = x: (builtins.typeOf x) == "path"; optionsListDesc = lib.flip map optionsListVisible (opt: opt // { description = let attempt = builtins.tryEval opt.description; in if attempt.success then attempt.value else "N/A"; declarations = map stripAnyPrefixes opt.declarations; } // lib.optionalAttrs (opt ? example) { example = substFunction opt.example; } // lib.optionalAttrs (opt ? default) { default = substFunction opt.default; } // lib.optionalAttrs (opt ? type) { type = substFunction opt.type; } // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != [ ]) { relatedPackages = genRelatedPackages opt.relatedPackages; } ); genRelatedPackages = packages: let unpack = p: if lib.isString p then { name = p; } else if lib.isList p then { path = p; } else p; describe = args: let title = args.title or null; name = args.name or (lib.concatStringsSep "." args.path); path = args.path or [ args.name ]; package = args.package or (lib.attrByPath path (throw "Invalid package attribute path '${toString path}'") pkgs); in "" + "${lib.optionalString (title != null) "${title} aka "}pkgs.${name} (${package.meta.name})" + lib.optionalString (!package.meta.available) " [UNAVAILABLE]" + ": ${package.meta.description or "???"}." + lib.optionalString (args ? comment) "\n${args.comment}" + lib.optionalString (package.meta ? longDescription) "\n${package.meta.longDescription}" + "" + ""; in "${lib.concatStringsSep "\n" (map (p: describe (unpack p)) packages)}"; optionLess = a: b: let ise = lib.hasPrefix "enable"; isp = lib.hasPrefix "package"; cmp = lib.splitByAndCompare ise lib.compare (lib.splitByAndCompare isp lib.compare lib.compare); in lib.compareLists cmp a.loc b.loc < 0; prefixesToStrip = map (p: "${toString p}/") ([ ../../.. ] ++ extraSources); stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) prefixesToStrip; ############################################################################### # This is the REAL meat of what we were after. # Output this however you want. optionsList = lib.sort optionLess optionsListDesc; optionsJSON = builtins.unsafeDiscardStringContext (builtins.toJSON (builtins.listToAttrs (map (o: { name = o.name; value = removeAttrs o [ # Select the fields you want to drop here: "name" "visible" "internal" "loc" "readOnly" ]; }) optionsList))); in pkgs.writeText "options.json" optionsJSON