docs/lib: init

Generate reference docs for functions that have RFC145 style
doc-comments.

1. function locations

  `docs/lib/function-locations.nix` scans nixvim's extended lib,
  extracting "position entries" via `unsafeGetAttrPos`.

  This is then converted into a nixdoc `locations.json` map of
  "function name" → "markdown location string".

2. mdbook menu

  `docs/lib/menu.nix` renders a mdbook menu representing all page
  entries.

3. markdown pages

  `docs/lib/default.nix` expects a set of "page entries", which come
  from `docs/lib/pages.nix` by default. It passes this data to
  `function-locations.nix` and `menu.nix`, and uses it internally to
  render markdown pages.

  Page entries can contain a `file` to render using `nixdoc`, and also a
  `markdown` attribute which will be included at the top of the docs.

  Additionally, a `title` can be included. This forms the heading
  `$name: $title`, where `name` is derived from the page's attr-path.

  See https://github.com/nix-community/nixdoc
This commit is contained in:
Matt Sturgeon 2025-03-05 20:44:36 +00:00
parent 563fdaeef9
commit dfaea5982e
No known key found for this signature in database
GPG key ID: 4F91844CED1A8299
6 changed files with 299 additions and 0 deletions

View file

@ -1,6 +1,7 @@
{
helpers,
system,
nixvim,
nixpkgs,
nuschtosSearch,
}:
@ -108,6 +109,10 @@ lib.fix (
gfm-alerts-to-admonitions = pkgs.python3.pkgs.callPackage ./gfm-alerts-to-admonitions { };
man-docs = pkgs.callPackage ./man { inherit options-json; };
lib-docs = pkgs.callPackage ./lib {
inherit nixvim lib;
};
}
// lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
# NuschtOS/search does not seem to work on darwin

168
docs/lib/default.nix Normal file
View file

@ -0,0 +1,168 @@
# Generates the documentation for library functions using nixdoc.
# See https://github.com/nix-community/nixdoc
{
lib,
runCommand,
writers,
nixdoc,
nixvim,
pageSpecs ? import ./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 ]));
# 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);
# 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
];
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;
};
}
''
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" (
{
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}
''

View file

@ -0,0 +1,85 @@
# Generates an attrset of "function name" → "markdown location",
# for use with nixdoc's `--locs` option.
#
# {
# "lib.nixvim.foo.bar" = "[lib/foo.nix:123](https://github.com/nix-community/nixvim/blob/«rev»/lib/foo.nix#L123) in `<nixvim>`";
# }
{
rootPath,
lib,
functionSet,
pathsToScan,
revision,
functionSetName ? "lib",
url ? "https://github.com/nix-community/nixvim/blob",
}:
let
rootPathString = toString rootPath;
urlPrefix = "${url}/${revision}";
sanitizeId = builtins.replaceStrings [ "'" ] [ "-prime" ];
# Like `isAttrs`, but returns `false` if `v` throws
tryIsAttrs = v: (builtins.tryEval (builtins.isAttrs v)).value;
# Collect position entries from an attrset
# `prefix` is used in the human-readable name,
# and for determining whether to recurse into attrs
collectPositionEntriesInSet =
prefix: set:
builtins.concatMap (
name:
[
{
name = lib.showAttrPath (
builtins.concatMap lib.toList [
functionSetName
prefix
name
]
);
location = builtins.unsafeGetAttrPos name set;
}
]
++ lib.optionals (prefix == [ ] && tryIsAttrs set.${name}) (
collectPositionEntriesInSet (prefix ++ [ name ]) set.${name}
)
) (builtins.attrNames set);
# Collect position entries from each `pathsToScan` in `set`
collectPositionEntriesFromPaths =
set:
builtins.concatMap (loc: collectPositionEntriesInSet loc (lib.getAttrFromPath loc set)) pathsToScan;
# Remove the tree root (usually the top-level store path)
removeNixvimPrefix = lib.flip lib.pipe [
(lib.strings.removePrefix rootPathString)
(lib.strings.removePrefix "/")
];
# Create a name-value-pair for use with `listToAttrs`
entryToNameValuePair =
{ name, location }:
{
name = sanitizeId name;
value =
let
file = removeNixvimPrefix location.file;
line = builtins.toString location.line;
text = "${file}:${line}";
target = "${urlPrefix}/${file}#L${line}";
in
"[${text}](${target}) in `<nixvim>`";
};
in
lib.pipe functionSet [
# Get the entries
collectPositionEntriesFromPaths
# Only include entries that have a location
(builtins.filter (entry: entry.location != null))
# No need to include out-of-tree entries
(builtins.filter (entry: lib.strings.hasPrefix rootPathString entry.location.file))
# Convert entries to attrset
(builtins.map entryToNameValuePair)
builtins.listToAttrs
]

31
docs/lib/menu.nix Normal file
View file

@ -0,0 +1,31 @@
{
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
]

8
docs/lib/pages.nix Normal file
View file

@ -0,0 +1,8 @@
# This file contains a list of function reference docs pages that should be generated by `nixdoc`
#
# Note: `nixdoc` uses `rnix` to parse the input file, so the file must return a fairly simple attrset.
# If there is an issue parsing the file, the resulting markdown will not contain any function docs.
{
# TODO
}

View file

@ -1,4 +1,5 @@
{
self,
inputs,
helpers,
...
@ -12,6 +13,7 @@
}:
{
packages = import ../docs {
nixvim = self;
inherit helpers;
inherit system;
inherit (inputs) nixpkgs;