1
0
Fork 0
mirror of https://github.com/nix-community/nixvim.git synced 2025-11-09 03:56:05 +01:00

docs/modules: init

Modules to represent pages in the docs
This commit is contained in:
Matt Sturgeon 2025-05-20 04:16:40 +01:00
parent 9faa339d9e
commit 4414d8aa14
6 changed files with 309 additions and 187 deletions

View file

@ -6,65 +6,41 @@
writers, writers,
nixdoc, nixdoc,
nixvim, nixvim,
pageSpecs ? import ./pages.nix, pageSpecs ? ./pages.nix,
}: }:
let let
# Some pages are just menu entries, others have an actual markdown page that pageConfiguration = lib.evalModules {
# needs rendering. modules = [
shouldRenderPage = page: page ? file || page ? markdown; pageSpecs
# Normalise a page node, recursively normalise its children
elaboratePage =
loc:
{ {
title ? "", freeformType = lib.types.attrsOf (
markdown ? null, lib.types.submoduleWith {
file ? null, modules = [ ../modules/page.nix ];
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;
}; };
pages = pageConfiguration.config;
# Recursively normalise page nodes
elaboratePages = prefix: builtins.mapAttrs (name: elaboratePage (prefix ++ [ name ]));
# Collect all page nodes into a list of page entries # Collect all page nodes into a list of page entries
collectPages = collectPages =
pages: pages:
builtins.concatMap ( builtins.concatMap (
page: node:
[ (builtins.removeAttrs page [ "pages" ]) ] let
++ lib.optionals (page ? pages) (collectPages page.pages) children = builtins.removeAttrs node [ "_page" ];
in
lib.optional (node ? _page) node._page ++ lib.optionals (children != { }) (collectPages children)
) (builtins.attrValues pages); ) (builtins.attrValues pages);
# Normalised page specs # Normalised page specs
elaboratedPageSpecs = elaboratePages [ ] pageSpecs; pageList = collectPages pages;
pageList = collectPages elaboratedPageSpecs; pagesToRender = builtins.filter (page: page.hasContent) pageList;
pagesToRender = builtins.filter (page: page ? outFile) pageList;
pagesWithFunctions = builtins.filter (page: page.file or null != null) pageList;
in
runCommand "nixvim-lib-docs" result =
runCommand "nixvim-lib-docs"
{ {
nativeBuildInputs = [ nativeBuildInputs = [
nixdoc nixdoc
@ -75,25 +51,22 @@ runCommand "nixvim-lib-docs"
inherit lib; inherit lib;
rootPath = nixvim; rootPath = nixvim;
functionSet = lib.extend nixvim.lib.overlay; functionSet = lib.extend nixvim.lib.overlay;
pathsToScan = builtins.catAttrs "loc" pagesWithFunctions; pathsToScan = lib.pipe pageList [
(map (x: x.functions))
(builtins.filter (x: x.file != null))
(map (x: x.loc))
];
revision = nixvim.rev or "main"; revision = nixvim.rev or "main";
} }
); );
passthru.config = pageConfiguration;
passthru.menu = import ./menu.nix { passthru.menu = import ./menu.nix {
inherit lib; inherit lib pages;
pageSpecs = elaboratedPageSpecs;
}; };
passthru.pages = builtins.listToAttrs ( passthru.pages = map (page: "${result}/${page.target}") pagesToRender;
builtins.map (
{ name, outFile, ... }:
{
inherit name;
value = outFile;
}
) pagesToRender
);
} }
'' ''
function docgen { function docgen {
@ -121,7 +94,7 @@ runCommand "nixvim-lib-docs"
--locs "$locations" \ --locs "$locations" \
--category "$name" \ --category "$name" \
--description "REMOVED BY TAIL" \ --description "REMOVED BY TAIL" \
--prefix "" \ --prefix "lib" \
--anchor-prefix "" \ --anchor-prefix "" \
| tail --lines +2 \ | tail --lines +2 \
> functions.md > functions.md
@ -159,20 +132,21 @@ runCommand "nixvim-lib-docs"
${lib.concatMapStringsSep "\n" ( ${lib.concatMapStringsSep "\n" (
{ {
name, functions,
file, source,
markdown, target,
outFile,
title ? "", title ? "",
... ...
}: }:
lib.escapeShellArgs [ lib.escapeShellArgs [
"docgen" "docgen"
"${lib.optionalString (markdown != null) markdown}" # md_file "${lib.optionalString (source != null) source}" # md_file
"${lib.optionalString (file != null) file}" # in_file "${lib.optionalString (functions.file != null) functions.file}" # in_file
name # name (lib.showAttrPath functions.loc) # name
outFile # out_file target # out_file
title # title title # title
] ]
) pagesToRender} ) pagesToRender}
'' '';
in
result

View file

@ -1,31 +1,31 @@
{ {
lib, lib,
pageSpecs, pages,
indentSize ? " ", indentSize ? " ",
}: }:
let let
pageToLines = pageToLines =
indent: parentName: indent: parent: node:
{
name,
outFile ? "",
pages ? { },
...
}:
let 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 # Only add node to the menu if it has content or multiple children
useNodeInMenu = outFile != "" || builtins.length children > 1; useNodeInMenu = node._page.target != "" || node._page.children > 1;
parentOfChildren = if useNodeInMenu then name else parentName; 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 in
lib.optional useNodeInMenu "${indent}- [${menuName}](${outFile})" lib.optional useNodeInMenu "${indent}- [${menuName}](${node._page.target})"
++ lib.optionals (children != [ ]) ( ++ lib.optionals (children != [ ]) (
builtins.concatMap (pageToLines (indent + indentSize) parentOfChildren) children builtins.concatMap (pageToLines nextIndent nextParent) children
); );
in in
lib.pipe pageSpecs [ lib.pipe pages [
builtins.attrValues builtins.attrValues
(builtins.concatMap (pageToLines "" "")) (builtins.concatMap (pageToLines "" null))
lib.concatLines lib.concatLines
] ]

View file

@ -4,21 +4,19 @@
# If there is an issue parsing the file, the resulting markdown will not contain any function docs. # If there is an issue parsing the file, the resulting markdown will not contain any function docs.
{ {
lib.pages = { lib.nixvim = {
nixvim = { _page = {
title = "Nixvim's functions"; title = "Nixvim's functions";
markdown = ./index.md; source = ./index.md;
};
pages = { utils._page = {
utils = {
file = ../../lib/utils.nix;
title = "utility functions"; title = "utility functions";
functions.file = ../../lib/utils.nix;
}; };
lua = { lua._page = {
file = ../../lib/to-lua.nix;
title = "lua functions"; title = "lua functions";
}; functions.file = ../../lib/to-lua.nix;
};
}; };
}; };
} }

View file

@ -12,7 +12,8 @@ let
../user-guide/faq.md ../user-guide/faq.md
../user-guide/config-examples.md ../user-guide/config-examples.md
] ]
++ lib.mapAttrsToList (name: file: "${lib-docs}/${file}") lib-docs.pages; ++ lib-docs.pages;
manHeader = manHeader =
runCommand "nixvim-general-doc-manpage" runCommand "nixvim-general-doc-manpage"
{ {

View file

@ -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
];
};
}

45
docs/modules/page.nix Normal file
View file

@ -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
];
};
};
}