From 6684d793845f0e89cfc44babaca98e49d80ee518 Mon Sep 17 00:00:00 2001 From: Matt Sturgeon Date: Tue, 20 May 2025 04:16:40 +0100 Subject: [PATCH 1/4] docs/modules: init Modules to represent pages in the docs --- docs/lib/default.nix | 284 +++++++++++++++------------------- docs/lib/menu.nix | 32 ++-- docs/lib/pages.nix | 24 ++- docs/man/default.nix | 3 +- docs/modules/page-options.nix | 104 +++++++++++++ docs/modules/page.nix | 45 ++++++ 6 files changed, 307 insertions(+), 185 deletions(-) create mode 100644 docs/modules/page-options.nix create mode 100644 docs/modules/page.nix diff --git a/docs/lib/default.nix b/docs/lib/default.nix index fb37706f0d..641e5e0e31 100644 --- a/docs/lib/default.nix +++ b/docs/lib/default.nix @@ -6,173 +6,147 @@ writers, nixdoc, nixvim, - pageSpecs ? import ./pages.nix, + pageSpecs ? ./pages.nix, }: let - # Some pages are just menu entries, others have an actual markdown page that - # needs rendering. - shouldRenderPage = page: page ? file || page ? markdown; - - # Normalise a page node, recursively normalise its children - elaboratePage = - loc: - { - title ? "", - markdown ? null, - file ? null, - pages ? { }, - }@page: - { - name = lib.attrsets.showAttrPath loc; - loc = lib.throwIfNot ( - builtins.head loc == "lib" - ) "All pages must be within `lib`, unexpected root `${builtins.head loc}`" (builtins.tail loc); - } - // lib.optionalAttrs (shouldRenderPage page) { - inherit - file - title - ; - markdown = - if builtins.isString markdown then - builtins.toFile "${lib.strings.replaceStrings [ "/" "-" ] (lib.lists.last loc)}.md" markdown - else - markdown; - outFile = lib.strings.concatStringsSep "/" (loc ++ [ "index.md" ]); - } - // lib.optionalAttrs (page ? pages) { - pages = elaboratePages loc pages; - }; - - # Recursively normalise page nodes - elaboratePages = prefix: builtins.mapAttrs (name: elaboratePage (prefix ++ [ name ])); + pageConfiguration = lib.evalModules { + modules = [ + pageSpecs + { + freeformType = lib.types.attrsOf ( + lib.types.submoduleWith { + modules = [ ../modules/page.nix ]; + } + ); + } + ]; + }; + pages = pageConfiguration.config; # Collect all page nodes into a list of page entries collectPages = pages: builtins.concatMap ( - page: - [ (builtins.removeAttrs page [ "pages" ]) ] - ++ lib.optionals (page ? pages) (collectPages page.pages) + node: + let + children = builtins.removeAttrs node [ "_page" ]; + in + lib.optional (node ? _page) node._page ++ lib.optionals (children != { }) (collectPages children) ) (builtins.attrValues pages); # Normalised page specs - elaboratedPageSpecs = elaboratePages [ ] pageSpecs; - pageList = collectPages elaboratedPageSpecs; - pagesToRender = builtins.filter (page: page ? outFile) pageList; - pagesWithFunctions = builtins.filter (page: page.file or null != null) pageList; -in - -runCommand "nixvim-lib-docs" - { - nativeBuildInputs = [ - nixdoc - ]; + pageList = collectPages pages; + pagesToRender = builtins.filter (page: page.hasContent) pageList; - locations = writers.writeJSON "locations.json" ( - import ./function-locations.nix { - inherit lib; - rootPath = nixvim; - functionSet = lib.extend nixvim.lib.overlay; - pathsToScan = builtins.catAttrs "loc" pagesWithFunctions; - revision = nixvim.rev or "main"; + result = + runCommand "nixvim-lib-docs" + { + nativeBuildInputs = [ + nixdoc + ]; + + locations = writers.writeJSON "locations.json" ( + import ./function-locations.nix { + inherit lib; + rootPath = nixvim; + functionSet = lib.extend nixvim.lib.overlay; + pathsToScan = lib.pipe pageList [ + (map (x: x.functions)) + (builtins.filter (x: x.file != null)) + (map (x: x.loc)) + ]; + revision = nixvim.rev or "main"; + } + ); + + passthru.config = pageConfiguration; + + passthru.menu = import ./menu.nix { + inherit lib pages; + }; + + passthru.pages = map (page: "${result}/${page.target}") pagesToRender; } - ); - - passthru.menu = import ./menu.nix { - inherit lib; - pageSpecs = elaboratedPageSpecs; - }; - - passthru.pages = builtins.listToAttrs ( - builtins.map ( - { name, outFile, ... }: - { - inherit name; - value = outFile; + '' + function docgen { + md_file="$1" + in_file="$2" + name="$3" + out_file="$out/$4" + title="$5" + + if [[ -z "$in_file" ]]; then + if [[ -z "$md_file" ]]; then + >&2 echo "No markdown or nix file for $name" + exit 1 + fi + elif [[ -f "$in_file/default.nix" ]]; then + in_file+="/default.nix" + elif [[ ! -f "$in_file" ]]; then + >&2 echo "File not found: $in_file" + exit 1 + fi + + if [[ -n "$in_file" ]]; then + nixdoc \ + --file "$in_file" \ + --locs "$locations" \ + --category "$name" \ + --description "REMOVED BY TAIL" \ + --prefix "lib" \ + --anchor-prefix "" \ + | tail --lines +2 \ + > functions.md + fi + + default_heading="# $name" + if [[ -n "$title" ]]; then + default_heading+=": $title" + fi + + print_heading=true + if [[ -f "$md_file" ]] && [[ "$(head --lines 1 "$md_file")" == '# '* ]]; then + >&2 echo "NOTE: markdown file for $name starts with a

heading. Skipping default heading \"$default_heading\"." + >&2 echo " Found \"$(head --lines 1 "$md_file")\" in: $md_file" + print_heading=false + fi + + mkdir -p $(dirname "$out_file") + ( + if [[ "$print_heading" = true ]]; then + echo "$default_heading" + echo + fi + if [[ -f "$md_file" ]]; then + cat "$md_file" + echo + fi + if [[ -f functions.md ]]; then + cat functions.md + fi + ) > "$out_file" } - ) pagesToRender - ); - } - '' - function docgen { - md_file="$1" - in_file="$2" - name="$3" - out_file="$out/$4" - title="$5" - - if [[ -z "$in_file" ]]; then - if [[ -z "$md_file" ]]; then - >&2 echo "No markdown or nix file for $name" - exit 1 - fi - elif [[ -f "$in_file/default.nix" ]]; then - in_file+="/default.nix" - elif [[ ! -f "$in_file" ]]; then - >&2 echo "File not found: $in_file" - exit 1 - fi - - if [[ -n "$in_file" ]]; then - nixdoc \ - --file "$in_file" \ - --locs "$locations" \ - --category "$name" \ - --description "REMOVED BY TAIL" \ - --prefix "" \ - --anchor-prefix "" \ - | tail --lines +2 \ - > functions.md - fi - - default_heading="# $name" - if [[ -n "$title" ]]; then - default_heading+=": $title" - fi - - print_heading=true - if [[ -f "$md_file" ]] && [[ "$(head --lines 1 "$md_file")" == '# '* ]]; then - >&2 echo "NOTE: markdown file for $name starts with a

heading. Skipping default heading \"$default_heading\"." - >&2 echo " Found \"$(head --lines 1 "$md_file")\" in: $md_file" - print_heading=false - fi - mkdir -p $(dirname "$out_file") - ( - if [[ "$print_heading" = true ]]; then - echo "$default_heading" - echo - fi - if [[ -f "$md_file" ]]; then - cat "$md_file" - echo - fi - if [[ -f functions.md ]]; then - cat functions.md - fi - ) > "$out_file" - } - - mkdir -p "$out" - - ${lib.concatMapStringsSep "\n" ( - { - name, - file, - markdown, - outFile, - title ? "", - ... - }: - lib.escapeShellArgs [ - "docgen" - "${lib.optionalString (markdown != null) markdown}" # md_file - "${lib.optionalString (file != null) file}" # in_file - name # name - outFile # out_file - title # title - ] - ) pagesToRender} - '' + mkdir -p "$out" + + ${lib.concatMapStringsSep "\n" ( + { + functions, + source, + target, + title ? "", + ... + }: + lib.escapeShellArgs [ + "docgen" + "${lib.optionalString (source != null) source}" # md_file + "${lib.optionalString (functions.file != null) functions.file}" # in_file + (lib.showAttrPath functions.loc) # name + target # out_file + title # title + ] + ) pagesToRender} + ''; +in +result diff --git a/docs/lib/menu.nix b/docs/lib/menu.nix index 87feb9c9cd..cd6eb954f2 100644 --- a/docs/lib/menu.nix +++ b/docs/lib/menu.nix @@ -1,31 +1,31 @@ { lib, - pageSpecs, + pages, indentSize ? " ", }: let pageToLines = - indent: parentName: - { - name, - outFile ? "", - pages ? { }, - ... - }: + indent: parent: node: let - menuName = lib.strings.removePrefix (parentName + ".") name; - children = builtins.attrValues pages; + + children = lib.pipe node [ + (lib.flip builtins.removeAttrs [ "_page" ]) + builtins.attrValues + ]; # Only add node to the menu if it has content or multiple children - useNodeInMenu = outFile != "" || builtins.length children > 1; - parentOfChildren = if useNodeInMenu then name else parentName; + useNodeInMenu = node._page.target != "" || node._page.children > 1; + nextParent = if useNodeInMenu then node else parent; + nextIndent = if useNodeInMenu then indent + indentSize else indent; + loc = lib.lists.removePrefix (parent._page.loc or [ ]) node._page.loc; + menuName = lib.attrsets.showAttrPath loc; in - lib.optional useNodeInMenu "${indent}- [${menuName}](${outFile})" + lib.optional useNodeInMenu "${indent}- [${menuName}](${node._page.target})" ++ lib.optionals (children != [ ]) ( - builtins.concatMap (pageToLines (indent + indentSize) parentOfChildren) children + builtins.concatMap (pageToLines nextIndent nextParent) children ); in -lib.pipe pageSpecs [ +lib.pipe pages [ builtins.attrValues - (builtins.concatMap (pageToLines "" "")) + (builtins.concatMap (pageToLines "" null)) lib.concatLines ] diff --git a/docs/lib/pages.nix b/docs/lib/pages.nix index f519339e38..b5e42f8298 100644 --- a/docs/lib/pages.nix +++ b/docs/lib/pages.nix @@ -4,21 +4,19 @@ # If there is an issue parsing the file, the resulting markdown will not contain any function docs. { - lib.pages = { - nixvim = { + lib.nixvim = { + _page = { title = "Nixvim's functions"; - markdown = ./index.md; + source = ./index.md; + }; - pages = { - utils = { - file = ../../lib/utils.nix; - title = "utility functions"; - }; - lua = { - file = ../../lib/to-lua.nix; - title = "lua functions"; - }; - }; + utils._page = { + title = "utility functions"; + functions.file = ../../lib/utils.nix; + }; + lua._page = { + title = "lua functions"; + functions.file = ../../lib/to-lua.nix; }; }; } diff --git a/docs/man/default.nix b/docs/man/default.nix index 92e69cde94..96f19312d1 100644 --- a/docs/man/default.nix +++ b/docs/man/default.nix @@ -12,7 +12,8 @@ let ../user-guide/faq.md ../user-guide/config-examples.md ] - ++ lib.mapAttrsToList (name: file: "${lib-docs}/${file}") lib-docs.pages; + ++ lib-docs.pages; + manHeader = runCommand "nixvim-general-doc-manpage" { diff --git a/docs/modules/page-options.nix b/docs/modules/page-options.nix new file mode 100644 index 0000000000..942c9be93b --- /dev/null +++ b/docs/modules/page-options.nix @@ -0,0 +1,104 @@ +{ + lib, + prefix, + name, + config, + options, + ... +}: +let + cfg = config._page; + opts = options._page; +in +{ + options._page = { + loc = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Page's location in the menu."; + default = prefix ++ [ name ]; + defaultText = lib.literalExpression "prefix ++ [ name ]"; + readOnly = true; + }; + target = lib.mkOption { + type = lib.types.str; + default = lib.optionalString cfg.hasContent (lib.concatStringsSep "/" (cfg.loc ++ [ "index.md" ])); + defaultText = lib.literalMD '' + `""` if page has no content, otherwise a filepath derived from the page's `loc`. + ''; + description = "Where to render content and link menu entries."; + }; + title = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Page's heading title."; + }; + text = lib.mkOption { + type = lib.types.nullOr lib.types.lines; + default = null; + description = "Optional markdown text to include after the title."; + }; + source = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Optional markdown file to include after the title."; + }; + functions.file = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Optional nix file to scan for RFC145 doc comments."; + }; + functions.loc = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = lib.lists.removePrefix [ "lib" ] cfg.loc; + defaultText = lib.literalMD '' + `loc`'s attrpath, without any leading "lib" + ''; + description = '' + Optional attrpath where functions are defined. + Provided to `nixdoc` as `--category`. + + Will scan `lib` for attribute locations in the functions set at this attrpath. + + Used in conjunction with `nix`. + ''; + }; + options = lib.mkOption { + type = lib.types.nullOr lib.types.raw; + default = null; + apply = opts: if builtins.isAttrs opts then lib.options.optionAttrSetToDocList opts else opts; + description = '' + Optional set of options or list of option docs-templates. + + If an attrset is provided, it will be coerced using `lib.options.optionAttrSetToDocList`. + ''; + }; + children = lib.mkOption { + type = lib.types.ints.unsigned; + description = '' + The number of child pages. + ''; + readOnly = true; + }; + hasContent = lib.mkOption { + type = lib.types.bool; + description = '' + Whether this page has any docs content. + + When `false`, this page represents an _empty_ menu entry. + ''; + readOnly = true; + }; + }; + + config._page = { + source = lib.mkIf (cfg.text != null) ( + lib.mkDerivedConfig opts.text (builtins.toFile "docs-${lib.attrsets.showAttrPath cfg.loc}-text.md") + ); + + hasContent = builtins.any (x: x != null) [ + cfg.source # markdown + cfg.functions.file # doc-comments + cfg.options # module options + ]; + }; +} diff --git a/docs/modules/page.nix b/docs/modules/page.nix new file mode 100644 index 0000000000..1224eaa3e3 --- /dev/null +++ b/docs/modules/page.nix @@ -0,0 +1,45 @@ +# This module represents a node in a tree of pages. +# Its freeformType is is recursive: attrs of another node submodule. +{ + lib, + prefix, + name, + config, + options, + ... +}: +{ + freeformType = lib.types.attrsOf ( + lib.types.submoduleWith { + specialArgs.prefix = prefix ++ [ name ]; + modules = [ ./page.nix ]; + } + // { + description = "page submodule"; + descriptionClass = "noun"; + # Alternative to `visible = "shallow"`, avoid inf-recursion when collecting options for docs + getSubOptions = _: { }; + } + ); + + # The _page option contains options for this page node + imports = [ + ./page-options.nix + ]; + + config = { + # Ensure the `prefix` arg exists + # Usually shadowed by `specialArgs.prefix` + _module.args.prefix = [ ]; + + _page = { + # Freeform definitions are children; count definitions without a + # corresponding option + children = lib.pipe config [ + builtins.attrNames + (lib.count (name: !(options ? ${name}))) + lib.mkForce + ]; + }; + }; +} From 2c407201847e675174613a4b1e43cfe1ef08b8dc Mon Sep 17 00:00:00 2001 From: Matt Sturgeon Date: Tue, 23 Sep 2025 14:50:15 +0100 Subject: [PATCH 2/4] docs/lib: simplify default title-heading --- docs/lib/default.nix | 19 ++++++++----------- docs/lib/pages.nix | 6 +++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/docs/lib/default.nix b/docs/lib/default.nix index 641e5e0e31..e105ac2391 100644 --- a/docs/lib/default.nix +++ b/docs/lib/default.nix @@ -100,22 +100,19 @@ let > functions.md fi - default_heading="# $name" - if [[ -n "$title" ]]; then - default_heading+=": $title" - fi - - print_heading=true + print_title=true if [[ -f "$md_file" ]] && [[ "$(head --lines 1 "$md_file")" == '# '* ]]; then - >&2 echo "NOTE: markdown file for $name starts with a

heading. Skipping default heading \"$default_heading\"." - >&2 echo " Found \"$(head --lines 1 "$md_file")\" in: $md_file" - print_heading=false + if [[ -n "$title" ]]; then + >&2 echo "NOTE: markdown file for $name starts with a

heading. Skipping title \"$title\"." + >&2 echo " Found \"$(head --lines 1 "$md_file")\" in: $md_file" + fi + print_title=false fi mkdir -p $(dirname "$out_file") ( - if [[ "$print_heading" = true ]]; then - echo "$default_heading" + if [[ "$print_title" = true ]]; then + echo "# $title" echo fi if [[ -f "$md_file" ]]; then diff --git a/docs/lib/pages.nix b/docs/lib/pages.nix index b5e42f8298..deeb905171 100644 --- a/docs/lib/pages.nix +++ b/docs/lib/pages.nix @@ -6,16 +6,16 @@ { lib.nixvim = { _page = { - title = "Nixvim's functions"; + title = "lib.nixvim: Nixvim's functions"; source = ./index.md; }; utils._page = { - title = "utility functions"; + title = "lib.nixvim.utils: utility functions"; functions.file = ../../lib/utils.nix; }; lua._page = { - title = "lua functions"; + title = "lib.nixvim.lua: lua functions"; functions.file = ../../lib/to-lua.nix; }; }; From 01631a363f8658d3d1c73fd1a598a974c2c830c2 Mon Sep 17 00:00:00 2001 From: Matt Sturgeon Date: Tue, 23 Sep 2025 15:02:24 +0100 Subject: [PATCH 3/4] =?UTF-8?q?docs/lib:=20rename=20'name'=20=E2=86=92=20'?= =?UTF-8?q?category'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/lib/default.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/lib/default.nix b/docs/lib/default.nix index e105ac2391..8659cad603 100644 --- a/docs/lib/default.nix +++ b/docs/lib/default.nix @@ -72,13 +72,13 @@ let function docgen { md_file="$1" in_file="$2" - name="$3" + category="$3" out_file="$out/$4" title="$5" if [[ -z "$in_file" ]]; then if [[ -z "$md_file" ]]; then - >&2 echo "No markdown or nix file for $name" + >&2 echo "No markdown or nix file for $category" exit 1 fi elif [[ -f "$in_file/default.nix" ]]; then @@ -92,7 +92,7 @@ let nixdoc \ --file "$in_file" \ --locs "$locations" \ - --category "$name" \ + --category "$category" \ --description "REMOVED BY TAIL" \ --prefix "lib" \ --anchor-prefix "" \ @@ -103,7 +103,7 @@ let print_title=true if [[ -f "$md_file" ]] && [[ "$(head --lines 1 "$md_file")" == '# '* ]]; then if [[ -n "$title" ]]; then - >&2 echo "NOTE: markdown file for $name starts with a

heading. Skipping title \"$title\"." + >&2 echo "NOTE: markdown file for $category starts with a

heading. Skipping title \"$title\"." >&2 echo " Found \"$(head --lines 1 "$md_file")\" in: $md_file" fi print_title=false @@ -139,7 +139,7 @@ let "docgen" "${lib.optionalString (source != null) source}" # md_file "${lib.optionalString (functions.file != null) functions.file}" # in_file - (lib.showAttrPath functions.loc) # name + (lib.showAttrPath functions.loc) # category target # out_file title # title ] From c99d6f2ff1a2bfa15d99be4a96f22cbfd9a2aa85 Mon Sep 17 00:00:00 2001 From: Matt Sturgeon Date: Tue, 23 Sep 2025 18:25:39 +0100 Subject: [PATCH 4/4] docs/lib: generalise `menu` impl using module system Move the mdbook menu rendering code into the module system and generalise it to apply to multiple "categories" (mdbook parts) and "types" of category (prefix, suffix, etc). --- docs/lib/default.nix | 21 +++------ docs/lib/menu.nix | 31 ------------ docs/lib/pages.nix | 28 ++++++----- docs/mdbook/SUMMARY.md | 2 - docs/modules/category.nix | 89 +++++++++++++++++++++++++++++++++++ docs/modules/menu.nix | 43 +++++++++++++++++ docs/modules/page-options.nix | 31 +++++++++++- docs/modules/to-menu.nix | 43 +++++++++++++++++ 8 files changed, 228 insertions(+), 60 deletions(-) delete mode 100644 docs/lib/menu.nix create mode 100644 docs/modules/category.nix create mode 100644 docs/modules/menu.nix create mode 100644 docs/modules/to-menu.nix diff --git a/docs/lib/default.nix b/docs/lib/default.nix index 8659cad603..3690b50dab 100644 --- a/docs/lib/default.nix +++ b/docs/lib/default.nix @@ -10,19 +10,14 @@ }: let - pageConfiguration = lib.evalModules { + menuConfiguration = lib.evalModules { modules = [ pageSpecs - { - freeformType = lib.types.attrsOf ( - lib.types.submoduleWith { - modules = [ ../modules/page.nix ]; - } - ); - } + ../modules/menu.nix ]; }; - pages = pageConfiguration.config; + cfg = menuConfiguration.config; + pages = cfg.functions; # Collect all page nodes into a list of page entries collectPages = @@ -33,7 +28,7 @@ let children = builtins.removeAttrs node [ "_page" ]; in lib.optional (node ? _page) node._page ++ lib.optionals (children != { }) (collectPages children) - ) (builtins.attrValues pages); + ) (builtins.attrValues (builtins.removeAttrs pages [ "_category" ])); # Normalised page specs pageList = collectPages pages; @@ -60,11 +55,9 @@ let } ); - passthru.config = pageConfiguration; + passthru.config = menuConfiguration; - passthru.menu = import ./menu.nix { - inherit lib pages; - }; + passthru.menu = cfg._menu.text; passthru.pages = map (page: "${result}/${page.target}") pagesToRender; } diff --git a/docs/lib/menu.nix b/docs/lib/menu.nix deleted file mode 100644 index cd6eb954f2..0000000000 --- a/docs/lib/menu.nix +++ /dev/null @@ -1,31 +0,0 @@ -{ - lib, - pages, - indentSize ? " ", -}: -let - pageToLines = - indent: parent: node: - let - - children = lib.pipe node [ - (lib.flip builtins.removeAttrs [ "_page" ]) - builtins.attrValues - ]; - # Only add node to the menu if it has content or multiple children - useNodeInMenu = node._page.target != "" || node._page.children > 1; - nextParent = if useNodeInMenu then node else parent; - nextIndent = if useNodeInMenu then indent + indentSize else indent; - loc = lib.lists.removePrefix (parent._page.loc or [ ]) node._page.loc; - menuName = lib.attrsets.showAttrPath loc; - in - lib.optional useNodeInMenu "${indent}- [${menuName}](${node._page.target})" - ++ lib.optionals (children != [ ]) ( - builtins.concatMap (pageToLines nextIndent nextParent) children - ); -in -lib.pipe pages [ - builtins.attrValues - (builtins.concatMap (pageToLines "" null)) - lib.concatLines -] diff --git a/docs/lib/pages.nix b/docs/lib/pages.nix index deeb905171..c9cc3625a4 100644 --- a/docs/lib/pages.nix +++ b/docs/lib/pages.nix @@ -4,19 +4,23 @@ # If there is an issue parsing the file, the resulting markdown will not contain any function docs. { - lib.nixvim = { - _page = { - title = "lib.nixvim: Nixvim's functions"; - source = ./index.md; - }; + functions = { + _category.name = "Functions"; - utils._page = { - title = "lib.nixvim.utils: utility functions"; - functions.file = ../../lib/utils.nix; - }; - lua._page = { - title = "lib.nixvim.lua: lua functions"; - functions.file = ../../lib/to-lua.nix; + lib.nixvim = { + _page = { + title = "lib.nixvim: Nixvim's functions"; + source = ./index.md; + }; + + utils._page = { + title = "lib.nixvim.utils: utility functions"; + functions.file = ../../lib/utils.nix; + }; + lua._page = { + title = "lib.nixvim.lua: lua functions"; + functions.file = ../../lib/to-lua.nix; + }; }; }; } diff --git a/docs/mdbook/SUMMARY.md b/docs/mdbook/SUMMARY.md index 77ecb37b4e..0b1223c198 100644 --- a/docs/mdbook/SUMMARY.md +++ b/docs/mdbook/SUMMARY.md @@ -9,8 +9,6 @@ - [Configuration examples](./user-guide/config-examples.md) - [Lazy Loading](./user-guide/lazy-loading.md) -# Functions - @FUNCTIONS_MENU@ # Platforms diff --git a/docs/modules/category.nix b/docs/modules/category.nix new file mode 100644 index 0000000000..749a29afef --- /dev/null +++ b/docs/modules/category.nix @@ -0,0 +1,89 @@ +{ + lib, + name, + config, + options, + ... +}: +let + cfg = config._category; + + pageType = lib.types.submoduleWith { + modules = [ ./page.nix ]; + }; + + pages = builtins.removeAttrs config (builtins.attrNames options); +in +{ + freeformType = lib.types.attrsOf pageType; + + options._category = { + name = lib.mkOption { + type = lib.types.str; + default = name; + defaultText = lib.literalMD "attribute name"; + }; + + order = lib.mkOption { + type = lib.types.int; + default = 100; + description = "Priority for where this category will appear in the menu."; + }; + + type = lib.mkOption { + type = lib.types.enum [ + "prefix" + "normal" + "suffix" + ]; + default = "normal"; + description = '' + The kind of mdbook chapters this category contains. + + **Prefix Chapter** + : Before the main numbered chapters, prefix chapters can be added that + will not be numbered. This is useful for forewords, introductions, etc. + There are, however, some constraints. + Prefix chapters cannot be nested; they should all be on the root level. + And you cannot add prefix chapters once you have added numbered chapters. + + **Normal Chapter** + : Called a "Numbered Chapter" in the MDBook docs. + Numbered chapters outline the main content of the book and can be + nested, resulting in a nice hierarchy (chapters, sub-chapters, etc.). + + **Suffix Chapter** + : Like prefix chapters, suffix chapters are unnumbered, but they come + after numbered chapters. + + See . + ''; + }; + + text = lib.mkOption { + type = lib.types.str; + description = "The rendered menu."; + readOnly = true; + }; + }; + + config._category = { + text = lib.optionalString (pages != { }) '' + # ${cfg.name} + + ${lib.pipe pages [ + builtins.attrValues + (map ( + page: + page._page.toMenu { + nested = cfg.type == "normal"; + indent = ""; + prefix = [ ]; + inherit page; + } + )) + (builtins.concatStringsSep "\n") + ]} + ''; + }; +} diff --git a/docs/modules/menu.nix b/docs/modules/menu.nix new file mode 100644 index 0000000000..eaa90d6be6 --- /dev/null +++ b/docs/modules/menu.nix @@ -0,0 +1,43 @@ +{ + lib, + config, + options, + ... +}: +let + categoryType = lib.types.submoduleWith { + modules = [ ./category.nix ]; + }; + + categories = builtins.removeAttrs config (builtins.attrNames options); +in +{ + freeformType = lib.types.attrsOf categoryType; + + options._menu = { + text = lib.mkOption { + type = lib.types.str; + description = "The rendered menu."; + readOnly = true; + }; + }; + + config._menu = { + text = lib.pipe categories [ + builtins.attrValues + (map (x: x._category)) + (lib.sortOn (x: x.order)) + (builtins.groupBy (x: x.type)) + ( + { + prefix ? [ ], + normal ? [ ], + suffix ? [ ], + }: + prefix ++ normal ++ suffix + ) + (map (x: x.text)) + (builtins.concatStringsSep "\n\n") + ]; + }; +} diff --git a/docs/modules/page-options.nix b/docs/modules/page-options.nix index 942c9be93b..6300cc057c 100644 --- a/docs/modules/page-options.nix +++ b/docs/modules/page-options.nix @@ -49,7 +49,7 @@ in }; functions.loc = lib.mkOption { type = lib.types.listOf lib.types.str; - default = lib.lists.removePrefix [ "lib" ] cfg.loc; + default = if lib.lists.hasPrefix [ "lib" ] cfg.loc then builtins.tail cfg.loc else cfg.loc; defaultText = lib.literalMD '' `loc`'s attrpath, without any leading "lib" ''; @@ -72,6 +72,30 @@ in If an attrset is provided, it will be coerced using `lib.options.optionAttrSetToDocList`. ''; }; + toMenu = lib.mkOption { + type = lib.types.functionTo lib.types.str; + description = '' + A function to render the menu for this sub-tree. + + Typically, this involves invoking `_page.toMenu` for all children. + + **Inputs** + + `settings` + : `nested` + : Whether this menu category supports nesting. + + `indent` + : The indentation to use before non-empty lines. + + `page` + : This page node. + + `prefix` + : The menu loc prefix, to be omitted from menu entry text. + Usually the `loc` of the parent page node. + ''; + }; children = lib.mkOption { type = lib.types.ints.unsigned; description = '' @@ -100,5 +124,10 @@ in cfg.functions.file # doc-comments cfg.options # module options ]; + + toMenu = import ./to-menu.nix { + inherit lib; + optionNames = builtins.attrNames options; + }; }; } diff --git a/docs/modules/to-menu.nix b/docs/modules/to-menu.nix new file mode 100644 index 0000000000..f0cab7f74a --- /dev/null +++ b/docs/modules/to-menu.nix @@ -0,0 +1,43 @@ +{ + lib, + optionNames, +}: +/** + The default `toMenu` function renders a page node into a menu subtree. +*/ +{ + page, + prefix ? [ ], + indent ? "", + nested ? true, +}: +let + inherit (page._page) loc target; + count = page._page.children; + + # Only add node to the menu if it has content or multiple children + showInMenu = target != "" || count > 1; + nextPrefix = if showInMenu then loc else prefix; + nextIndent = if showInMenu && nested then indent + " " else indent; + + children = builtins.removeAttrs page optionNames; + submenu = lib.pipe children [ + builtins.attrValues + (map ( + subpage: + page._page.toMenu { + inherit nested; + page = subpage; + indent = nextIndent; + prefix = nextPrefix; + } + )) + ]; + + loc' = if lib.lists.hasPrefix prefix loc then lib.lists.drop (builtins.length prefix) loc else loc; + menuText = lib.attrsets.showAttrPath loc'; + menuitem = lib.optionals showInMenu [ + (indent + lib.optionalString nested "- " + "[${menuText}](${target})") + ]; +in +builtins.concatStringsSep "\n" (menuitem ++ submenu)