diff --git a/docs/default.nix b/docs/default.nix index 143cdd9c..06d4d729 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -1,8 +1,21 @@ { - lib, - modules, + rawModules, pkgs, }: let + pkgsDoc = + import (pkgs.applyPatches { + name = "nixpkgs-nixvim-doc"; + src = pkgs.path; + patches = [ + ./either_recursive.patch + ]; + }) { + inherit (pkgs) system; + config.allowUnfree = true; + }; + + inherit (pkgsDoc) lib; + nixvimPath = toString ./..; gitHubDeclaration = user: repo: subpath: { @@ -16,10 +29,10 @@ declarations = map ( decl: - if pkgs.lib.hasPrefix nixvimPath (toString decl) + if lib.hasPrefix nixvimPath (toString decl) then gitHubDeclaration "nix-community" "nixvim" - (pkgs.lib.removePrefix "/" (pkgs.lib.removePrefix nixvimPath (toString decl))) + (lib.removePrefix "/" (lib.removePrefix nixvimPath (toString decl))) else if decl == "lib/modules.nix" then gitHubDeclaration "NixOS" "nixpkgs" decl else decl @@ -27,222 +40,6 @@ opt.declarations; }; - getSubOptions' = type: loc: let - types = - # Composite types - { - either = - (getSubOptions' type.nestedTypes.left loc) - // (getSubOptions' type.nestedTypes.right loc); - nullOr = getSubOptions' type.nestedTypes.elemType loc; - lazyAttrsOf = getSubOptions' type.nestedTypes.elemType (loc ++ [""]); - attrsOf = getSubOptions' type.nestedTypes.elemType (loc ++ [""]); - listOf = getSubOptions' type.nestedTypes.elemType (loc ++ ["*"]); - functionTo = getSubOptions' type.nestedTypes.elemType (loc ++ [""]); - - # Taken from lib/types.nix - submodule = let - base = lib.evalModules { - modules = - [ - { - _module.args.name = lib.mkOptionDefault "‹name›"; - } - ] - ++ type.getSubModules; - }; - inherit (base._module) freeformType; - in - (base.extendModules - {prefix = loc;}) - .options - // lib.optionalAttrs (freeformType != null) { - _freeformOptions = getSubOptions' freeformType loc; - }; - } - # Leaf types - // lib.genAttrs [ - "raw" - "bool" - "optionType" - "unspecified" - "str" - "attrs" - "rawLua" - "int" - "package" - "numberBetween" - "enum" - "anything" - "separatedString" - "path" - "maintainer" - "unsignedInt" - "float" - "positiveInt" - "intBetween" - "nullType" - "nonEmptyStr" - "nixvim-configuration" - ] (_: {}); - in - # For recursive types avoid calculating sub options, else this - # will end up in an unbounded recursion - if loc == ["plugins" "packer" "plugins" "*" "requires"] - then {} - else if builtins.hasAttr type.name types - then types.${type.name} - else throw "unhandled type in documentation: ${type.name}"; - - mkOptionsJSON = options: let - # Mainly present to patch the type.getSubOptions of `either`, but we need to patch all - # the options in order to correctly handle other composite options - # The code that follows is taken almost exactly from nixpkgs, - # by changing type.getSubOptions to getSubOptions' - # lib/options.nix - optionAttrSetToDocList' = _: options: - lib.concatMap (opt: let - name = lib.showOption opt.loc; - docOption = - { - inherit (opt) loc; - inherit name; - description = opt.description or null; - declarations = builtins.filter (x: x != lib.unknownModule) opt.declarations; - internal = opt.internal or false; - visible = - if (opt ? visible && opt.visible == "shallow") - then true - else opt.visible or true; - readOnly = opt.readOnly or false; - type = opt.type.description or "unspecified"; - } - // lib.optionalAttrs (opt ? example) { - example = builtins.addErrorContext "while evaluating the example of option `${name}`" ( - lib.options.renderOptionValue opt.example - ); - } - // lib.optionalAttrs (opt ? defaultText || opt ? default) { - default = - builtins.addErrorContext "while evaluating the ${ - if opt ? defaultText - then "defaultText" - else "default value" - } of option `${name}`" ( - lib.options.renderOptionValue (opt.defaultText or opt.default) - ); - } - // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) {inherit (opt) relatedPackages;}; - - subOptions = let - ss = getSubOptions' opt.type opt.loc; - in - if ss != {} - then optionAttrSetToDocList' opt.loc ss - else []; - subOptionsVisible = docOption.visible && opt.visible or null != "shallow"; - in - # To find infinite recursion in NixOS option docs: - # builtins.trace opt.loc - [docOption] ++ lib.optionals subOptionsVisible subOptions) (lib.collect lib.isOption options); - - # Generate documentation template from the list of option declaration like - # the set generated with filterOptionSets. - optionAttrSetToDocList = optionAttrSetToDocList' []; - - # nixos/lib/make-options-doc/default.nix - - rawOpts = optionAttrSetToDocList options; - transformedOpts = map transformOptions rawOpts; - filteredOpts = lib.filter (opt: opt.visible && !opt.internal) transformedOpts; - - # Generate DocBook documentation for a list of packages. This is - # what `relatedPackages` option of `mkOption` from - # ../../../lib/options.nix influences. - # - # Each element of `relatedPackages` can be either - # - a string: that will be interpreted as an attribute name from `pkgs` and turned into a link - # to search.nixos.org, - # - a list: that will be interpreted as an attribute path from `pkgs` and turned into a link - # to search.nixos.org, - # - an attrset: that can specify `name`, `path`, `comment` - # (either of `name`, `path` is required, the rest are optional). - # - # NOTE: No checks against `pkgs` are made to ensure that the referenced package actually exists. - # Such checks are not compatible with option docs caching. - genRelatedPackages = packages: optName: 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); - in '' - - [${lib.optionalString (title != null) "${title} aka "}`pkgs.${name}`]( - https://search.nixos.org/packages?show=${name}&sort=relevance&query=${name} - )${ - lib.optionalString (args ? comment) "\n\n ${args.comment}" - } - ''; - in - lib.concatMapStrings (p: describe (unpack p)) packages; - - nixvimOptionsList = - lib.flip map filteredOpts - ( - opt: - opt - // lib.optionalAttrs (opt ? relatedPackages && opt.relatedPackages != []) { - relatedPackages = genRelatedPackages opt.relatedPackages opt.name; - } - ); - - nixvimOptionsNix = builtins.listToAttrs (map (o: { - inherit (o) name; - value = removeAttrs o ["name" "visible" "internal"]; - }) - nixvimOptionsList); - in - pkgs.runCommand "options.json" - { - meta.description = "List of NixOS options in JSON format"; - nativeBuildInputs = [ - pkgs.brotli - pkgs.python3Minimal - ]; - options = - builtins.toFile "options.json" - (builtins.unsafeDiscardStringContext (builtins.toJSON nixvimOptionsNix)); - baseJSON = builtins.toFile "base.json" "{}"; - } - '' - # Export list of options in different format. - dst=$out/share/doc/nixos - mkdir -p $dst - - TOUCH_IF_DB=$dst/.used-docbook \ - python ${pkgs.path}/nixos/lib/make-options-doc/mergeJSON.py \ - $baseJSON $options \ - > $dst/options.json - - if grep /nixpkgs/nixos/modules $dst/options.json; then - echo "The manual appears to depend on the location of Nixpkgs, which is bad" - echo "since this prevents sharing via the NixOS channel. This is typically" - echo "caused by an option default that refers to a relative path (see above" - echo "for hints about the offending path)." - exit 1 - fi - - brotli -9 < $dst/options.json > $dst/options.json.br - - mkdir -p $out/nix-support - echo "file json $dst/options.json" >> $out/nix-support/hydra-build-products - echo "file json-br $dst/options.json.br" >> $out/nix-support/hydra-build-products - ''; - nixvmConfigType = lib.mkOptionType { name = "nixvim-configuration"; description = "nixvim configuration options"; @@ -270,17 +67,24 @@ }; }) ] - ++ modules; + ++ (rawModules pkgsDoc); in rec { - options-json = mkOptionsJSON (lib.evalModules {modules = topLevelModules;}).options; - man-docs = pkgs.callPackage ./man {inherit options-json;}; + options-json = + (pkgsDoc.nixosOptionsDoc + { + inherit (lib.evalModules {modules = topLevelModules;}) options; + inherit transformOptions; + warningsAreErrors = false; + }) + .optionsJSON; + man-docs = pkgsDoc.callPackage ./man {inherit options-json;}; } # Do not check if documentation builds fine on darwin as it fails: # > sandbox-exec: pattern serialization length 69298 exceeds maximum (65535) - // lib.optionalAttrs (!pkgs.stdenv.isDarwin) { - docs = pkgs.callPackage ./mdbook { - inherit mkOptionsJSON getSubOptions'; + // lib.optionalAttrs (!pkgsDoc.stdenv.isDarwin) { + docs = pkgsDoc.callPackage ./mdbook { + inherit transformOptions; modules = topLevelModules; }; } diff --git a/docs/either_recursive.patch b/docs/either_recursive.patch new file mode 100644 index 00000000..ccb02dc6 --- /dev/null +++ b/docs/either_recursive.patch @@ -0,0 +1,49 @@ +commit e067eb1f6da7994150f10854e5fd635ca8bd0e92 +Author: traxys +Date: Sun Jan 14 17:54:55 2024 +0100 + + lib.types: Include the suboptions of both types for either + + This allows to correctly gather the sub options for types of the form + `either (submodule {...})`. + + This requires adding two new types: `eitherRecursive` and + `oneOfRecursive` that avoid infinite recursions for types like + configuration types that are often of the form `oneOf [atom (listOf + configType)]` + +diff --git a/lib/types.nix b/lib/types.nix +index cea63c598321..d26982db6cc0 100644 +--- a/lib/types.nix ++++ b/lib/types.nix +@@ -906,10 +906,16 @@ rec { + then functor.type mt1 mt2 + else null; + functor = (defaultFunctor name) // { wrapped = [ t1 t2 ]; }; ++ getSubOptions = prefix: (t1.getSubOptions prefix) // (t2.getSubOptions prefix); + nestedTypes.left = t1; + nestedTypes.right = t2; + }; + ++ # Handle recursive leaf types, avoiding an infinite recursion ++ eitherRecursive = t1: t2: (either t1 t2) // { ++ getSubOptions = _: {}; ++ }; ++ + # Any of the types in the given list + oneOf = ts: + let +@@ -917,6 +923,13 @@ rec { + tail' = tail ts; + in foldl' either head' tail'; + ++ # Handle recursive leaf types, avoiding an infinite recursion ++ oneOfRecursive = ts: ++ let ++ head' = if ts == [] then throw "types.oneOfRecursive needs to get at least one type in its argument" else head ts; ++ tail' = tail ts; ++ in foldl' eitherRecursive head' tail'; ++ + # Either value of type `coercedType` or `finalType`, the former is + # converted to `finalType` using `coerceFunc`. + coercedTo = coercedType: coerceFunc: finalType: diff --git a/docs/mdbook/default.nix b/docs/mdbook/default.nix index 2dd8ccb6..64e7677c 100644 --- a/docs/mdbook/default.nix +++ b/docs/mdbook/default.nix @@ -2,10 +2,8 @@ pkgs, lib, modules, - mkOptionsJSON, - runCommand, - nixos-render-docs, - getSubOptions', + nixosOptionsDoc, + transformOptions, }: with lib; let options = lib.evalModules { @@ -13,18 +11,12 @@ with lib; let specialArgs = {inherit pkgs lib;}; }; - mkMDDoc = options: let - optionsJson = mkOptionsJSON options; - in - runCommand "options.md" { - nativeBuildInputs = [nixos-render-docs]; - } '' - nixos-render-docs -j $NIX_BUILD_CORES options commonmark \ - --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \ - --revision "" \ - ${optionsJson}/share/doc/nixos/options.json \ - $out - ''; + mkMDDoc = options: + (nixosOptionsDoc { + inherit options transformOptions; + warningsAreErrors = false; + }) + .optionsCommonMark; removeUnwanted = attrs: builtins.removeAttrs attrs [ @@ -37,7 +29,7 @@ with lib; let removeWhitespace = builtins.replaceStrings [" "] [""]; - getSubOptions = opts: path: removeUnwanted (getSubOptions' opts.type path); + getSubOptions = opts: path: removeUnwanted (opts.type.getSubOptions path); isVisible = opts: if isOption opts diff --git a/flake-modules/modules.nix b/flake-modules/modules.nix index 6d393a66..873508f3 100644 --- a/flake-modules/modules.nix +++ b/flake-modules/modules.nix @@ -31,13 +31,12 @@ perSystem = { pkgs, - pkgsUnfree, config, ... }: { _module.args = { modules = modules pkgs; - modulesUnfree = modules pkgsUnfree; + rawModules = modules; }; }; } diff --git a/flake-modules/packages.nix b/flake-modules/packages.nix index 2111ced8..8de8b202 100644 --- a/flake-modules/packages.nix +++ b/flake-modules/packages.nix @@ -2,14 +2,11 @@ perSystem = { pkgs, config, - modulesUnfree, - pkgsUnfree, + rawModules, ... }: { packages = import ../docs { - modules = modulesUnfree; - pkgs = pkgsUnfree; - inherit (pkgs) lib; + inherit rawModules pkgs; }; # Test that all packages build fine when running `nix flake check`. diff --git a/lib/types.nix b/lib/types.nix index f4346ede..83221968 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -72,6 +72,9 @@ in strLua = strLikeType "lua code string"; strLuaFn = strLikeType "lua function string"; + + # Overridden when building the documentation + eitherRecursive = either; } # Allow to do `with nixvimTypes;` instead of `with types;` // lib.types diff --git a/plugins/pluginmanagers/packer.nix b/plugins/pluginmanagers/packer.nix index 2ad213c7..92345b7e 100644 --- a/plugins/pluginmanagers/packer.nix +++ b/plugins/pluginmanagers/packer.nix @@ -60,7 +60,9 @@ in { ]) "Post-install hook"; - requires = helpers.mkNullOrOption (either str listOfPlugins) "Plugin dependencies"; + requires = + helpers.mkNullOrOption (helpers.nixvimTypes.eitherRecursive str listOfPlugins) + "Plugin dependencies"; rocks = helpers.mkNullOrOption (either str (listOf (either str attrs)))