mirror of
https://github.com/nix-community/nixvim.git
synced 2025-11-08 19:46:06 +01:00
docs/modules: init
Modules to represent pages in the docs
This commit is contained in:
parent
9faa339d9e
commit
4414d8aa14
6 changed files with 309 additions and 187 deletions
|
|
@ -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
|
||||
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";
|
||||
}
|
||||
);
|
||||
|
||||
passthru.menu = import ./menu.nix {
|
||||
inherit lib;
|
||||
pageSpecs = elaboratedPageSpecs;
|
||||
};
|
||||
|
||||
passthru.pages = builtins.listToAttrs (
|
||||
builtins.map (
|
||||
{ name, outFile, ... }:
|
||||
{
|
||||
inherit name;
|
||||
value = outFile;
|
||||
}
|
||||
) 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 <h1> 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" (
|
||||
result =
|
||||
runCommand "nixvim-lib-docs"
|
||||
{
|
||||
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}
|
||||
''
|
||||
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;
|
||||
}
|
||||
''
|
||||
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 <h1> 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" (
|
||||
{
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
{
|
||||
|
|
|
|||
104
docs/modules/page-options.nix
Normal file
104
docs/modules/page-options.nix
Normal 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
45
docs/modules/page.nix
Normal 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
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue