diff --git a/docs/lib/default.nix b/docs/lib/default.nix
index fb37706f0d..3690b50dab 100644
--- a/docs/lib/default.nix
+++ b/docs/lib/default.nix
@@ -6,173 +6,137 @@
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 ]));
+ menuConfiguration = lib.evalModules {
+ modules = [
+ pageSpecs
+ ../modules/menu.nix
+ ];
+ };
+ cfg = menuConfiguration.config;
+ pages = cfg.functions;
# 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)
- ) (builtins.attrValues pages);
+ node:
+ let
+ children = builtins.removeAttrs node [ "_page" ];
+ in
+ lib.optional (node ? _page) node._page ++ lib.optionals (children != { }) (collectPages children)
+ ) (builtins.attrValues (builtins.removeAttrs pages [ "_category" ]));
# 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
+ pageList = collectPages pages;
+ pagesToRender = builtins.filter (page: page.hasContent) pageList;
-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 = 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 = menuConfiguration;
+
+ passthru.menu = cfg._menu.text;
+
+ 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"
+ 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 $category"
+ 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 "$category" \
+ --description "REMOVED BY TAIL" \
+ --prefix "lib" \
+ --anchor-prefix "" \
+ | tail --lines +2 \
+ > functions.md
+ fi
+
+ print_title=true
+ if [[ -f "$md_file" ]] && [[ "$(head --lines 1 "$md_file")" == '# '* ]]; then
+ if [[ -n "$title" ]]; then
+ >&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
+ fi
+
+ mkdir -p $(dirname "$out_file")
+ (
+ if [[ "$print_title" = true ]]; then
+ echo "# $title"
+ 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) # category
+ target # out_file
+ title # title
+ ]
+ ) pagesToRender}
+ '';
+in
+result
diff --git a/docs/lib/menu.nix b/docs/lib/menu.nix
deleted file mode 100644
index 87feb9c9cd..0000000000
--- a/docs/lib/menu.nix
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- lib,
- pageSpecs,
- indentSize ? " ",
-}:
-let
- pageToLines =
- indent: parentName:
- {
- name,
- outFile ? "",
- pages ? { },
- ...
- }:
- let
- menuName = lib.strings.removePrefix (parentName + ".") name;
- children = builtins.attrValues pages;
- # 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;
- in
- lib.optional useNodeInMenu "${indent}- [${menuName}](${outFile})"
- ++ lib.optionals (children != [ ]) (
- builtins.concatMap (pageToLines (indent + indentSize) parentOfChildren) children
- );
-in
-lib.pipe pageSpecs [
- builtins.attrValues
- (builtins.concatMap (pageToLines "" ""))
- lib.concatLines
-]
diff --git a/docs/lib/pages.nix b/docs/lib/pages.nix
index f519339e38..c9cc3625a4 100644
--- a/docs/lib/pages.nix
+++ b/docs/lib/pages.nix
@@ -4,20 +4,22 @@
# If there is an issue parsing the file, the resulting markdown will not contain any function docs.
{
- lib.pages = {
- nixvim = {
- title = "Nixvim's functions";
- markdown = ./index.md;
+ functions = {
+ _category.name = "Functions";
- pages = {
- utils = {
- file = ../../lib/utils.nix;
- title = "utility functions";
- };
- lua = {
- file = ../../lib/to-lua.nix;
- title = "lua functions";
- };
+ 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/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/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
new file mode 100644
index 0000000000..6300cc057c
--- /dev/null
+++ b/docs/modules/page-options.nix
@@ -0,0 +1,133 @@
+{
+ 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 = 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"
+ '';
+ 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`.
+ '';
+ };
+ 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 = ''
+ 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
+ ];
+
+ toMenu = import ./to-menu.nix {
+ inherit lib;
+ optionNames = builtins.attrNames 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
+ ];
+ };
+ };
+}
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)