nix-community.nixvim/docs/mdbook/default.nix

334 lines
9.4 KiB
Nix
Raw Normal View History

{
pkgs,
lib,
2024-08-02 01:45:45 +01:00
evaledModules,
nixosOptionsDoc,
transformOptions,
search,
}:
2024-05-05 19:39:35 +02:00
let
2024-08-02 01:45:45 +01:00
inherit (evaledModules.config.meta) nixvimInfo;
2024-05-05 19:39:35 +02:00
mkMDDoc =
options:
(nixosOptionsDoc {
inherit options transformOptions;
warningsAreErrors = false;
2024-05-05 19:39:35 +02:00
}).optionsCommonMark;
2024-05-05 19:39:35 +02:00
removeUnwanted =
attrs:
builtins.removeAttrs attrs [
"_module"
"_freeformOptions"
"warnings"
"assertions"
"content"
];
2024-05-05 19:39:35 +02:00
removeWhitespace = builtins.replaceStrings [ " " ] [ "" ];
getSubOptions =
2024-08-30 14:37:32 -05:00
opts: path: lib.optionalAttrs (isVisible opts) (removeUnwanted (opts.type.getSubOptions path));
2024-05-05 19:39:35 +02:00
isVisible =
opts:
2024-08-30 14:37:32 -05:00
if lib.isOption opts then
2024-06-25 17:41:42 +01:00
opts.visible or true
2024-05-05 19:39:35 +02:00
else if opts.isOption then
2024-06-25 17:41:42 +01:00
opts.index.options.visible or true
2024-05-05 19:39:35 +02:00
else
let
2024-08-30 14:37:32 -05:00
filterFunc = lib.filterAttrs (_: v: if lib.isAttrs v then isVisible v else true);
2024-05-05 19:39:35 +02:00
hasEmptyIndex = (filterFunc opts.index.options) == { };
hasEmptyComponents = (filterFunc opts.components) == { };
in
!hasEmptyIndex || !hasEmptyComponents;
wrapModule = path: opts: isOpt: rec {
index = {
options =
2024-05-05 19:39:35 +02:00
if isOpt then
opts
else
2024-08-30 14:37:32 -05:00
lib.filterAttrs (_: component: component.isOption && (isVisible component)) opts;
path = removeWhitespace (lib.concatStringsSep "/" path);
moduleDoc =
let
2024-08-30 14:37:32 -05:00
info = lib.attrByPath path { } nixvimInfo;
2024-08-02 01:45:45 +01:00
maintainers = lib.unique (evaledModules.config.meta.maintainers.${info.file} or [ ]);
maintainersNames = builtins.map maintToMD maintainers;
maintToMD = m: if m ? github then "[${m.name}](https://github.com/${m.github})" else m.name;
in
# Make sure this path has a valid info attrset
if info ? file && info ? description && info ? url then
"# ${lib.last path}\n\n"
2024-06-13 05:46:16 +01:00
+ (lib.optionalString (info.url != null) "**URL:** [${info.url}](${info.url})\n\n")
2024-05-05 19:39:35 +02:00
+ (lib.optionalString (
maintainers != [ ]
2024-05-05 19:39:35 +02:00
) "**Maintainers:** ${lib.concatStringsSep ", " maintainersNames}\n\n")
+ lib.optionalString (info.description != null) ''
---
${info.description}
''
2024-05-05 19:39:35 +02:00
else
null;
};
components =
2024-05-05 19:39:35 +02:00
if isOpt then
{ }
else
2024-08-30 14:37:32 -05:00
lib.filterAttrs (_: component: !component.isOption && (isVisible component)) opts;
2024-05-05 19:39:35 +02:00
hasComponents = components != { };
isOption = isOpt;
};
2024-05-05 19:39:35 +02:00
processModulesRec =
modules:
let
recurse =
path: mods:
let
g =
name: opts:
2024-08-30 14:37:32 -05:00
if !lib.isOption opts then
2024-05-05 19:39:35 +02:00
wrapModule (path ++ [ name ]) (recurse (path ++ [ name ]) opts) false
else
let
subOpts = getSubOptions opts (path ++ [ name ]);
in
if subOpts != { } then
wrapModule (path ++ [ name ]) (
(recurse (path ++ [ name ]) subOpts)
// {
# This is necessary to include the submodule option's definition in the docs (description, type, etc.)
# For instance, this helps submodules like "autoCmd" to include their base definitions and examples in the docs
# Though there might be a better, less "hacky" solution than this.
2024-08-30 14:37:32 -05:00
${name} = lib.recursiveUpdate opts {
2024-05-05 19:39:35 +02:00
isOption = true;
type.getSubOptions = _: _: { }; # Used to exclude suboptions from the submodule definition itself
};
}
) false
else
wrapModule (path ++ [ name ]) opts true;
in
2024-08-30 14:37:32 -05:00
lib.mapAttrs g mods;
in
2024-08-30 14:37:32 -05:00
lib.foldlAttrs (
2024-05-05 19:39:35 +02:00
acc: name: opts:
let
group = if !opts.hasComponents then "Neovim Options" else "none";
last =
2024-05-05 19:39:35 +02:00
acc.${group} or {
index = {
2024-05-05 19:39:35 +02:00
options = { };
path = removeWhitespace "${group}";
moduleDoc = null;
};
2024-05-05 19:39:35 +02:00
components = { };
isGroup = true;
hasComponents = false;
};
2024-08-30 14:37:32 -05:00
isOpt = !opts.hasComponents && (lib.isOption opts.index.options);
in
2024-05-05 19:39:35 +02:00
acc
// {
2024-08-30 14:37:32 -05:00
${group} = lib.recursiveUpdate last {
index.options = lib.optionalAttrs isOpt { ${name} = opts.index.options; };
2024-08-30 14:37:32 -05:00
components = lib.optionalAttrs (!isOpt) {
${name} = lib.recursiveUpdate opts {
2024-05-05 19:39:35 +02:00
index.path = removeWhitespace (
2024-08-30 14:37:32 -05:00
lib.concatStringsSep "/" ((lib.optional (group != "none") group) ++ [ opts.index.path ])
2024-05-05 19:39:35 +02:00
);
hasComponents = true;
};
};
2024-05-05 19:39:35 +02:00
hasComponents = last.components != { };
};
}
) { } (recurse [ ] modules);
mapModulesToString =
f: modules:
let
recurse =
mods:
let
g =
name: opts:
if (opts ? "isGroup") then
if name != "none" then (f name opts) + ("\n" + recurse opts.components) else recurse opts.components
else if opts.hasComponents then
(f name opts) + ("\n" + recurse opts.components)
else
f name opts;
in
2024-08-30 14:37:32 -05:00
lib.concatStringsSep "\n" (lib.mapAttrsToList g mods);
in
recurse modules;
docs = rec {
2024-08-02 01:45:45 +01:00
modules = processModulesRec (removeUnwanted evaledModules.options);
2024-05-05 19:39:35 +02:00
commands = mapModulesToString (
name: opts:
let
2024-08-30 14:37:32 -05:00
isBranch = if (lib.hasSuffix "index" opts.index.path) then true else opts.hasComponents;
2024-05-05 19:39:35 +02:00
path = if isBranch then "${opts.index.path}/index.md" else "${opts.index.path}.md";
in
2024-08-30 14:37:32 -05:00
(lib.optionalString isBranch "mkdir -p ${opts.index.path}\n")
2024-05-05 19:39:35 +02:00
+ (
if opts.index.moduleDoc == null then
"cp ${mkMDDoc opts.index.options} ${path}"
else
# Including moduleDoc's text directly will result in bash interpreting special chars,
# write it to the nix store and `cat` the file instead.
''
{
cat ${pkgs.writeText "module-doc" opts.index.moduleDoc}
cat ${mkMDDoc opts.index.options}
} > ${path}
''
)
2024-05-05 19:39:35 +02:00
) modules;
};
mdbook = {
2024-10-23 18:16:03 +01:00
nixvimOptionsSummary = mapModulesToString (
2024-05-05 19:39:35 +02:00
name: opts:
let
isBranch = name == "index" || (opts.hasComponents && opts.index.options != { });
2024-05-05 19:39:35 +02:00
path =
if isBranch then
"${opts.index.path}/index.md"
else if !opts.hasComponents then
"${opts.index.path}.md"
else
"";
indentLevel = lib.count (c: c == "/") (lib.stringToCharacters opts.index.path);
padding = lib.strings.replicate indentLevel "\t";
2024-05-05 19:39:35 +02:00
in
"${padding}- [${name}](${path})"
) docs.modules;
wrapperOptionFiles = lib.mapAttrs' (name: options: {
name = options.meta.wrapper.name.value;
# TODO:
# value.path = "./modules/${name}.md";
value.file = mkMDDoc options;
}) wrapperOptions;
wrapperOptionDocs =
pkgs.runCommandNoCCLocal "wrapper-option-doc"
{
__structuredAttrs = true;
files = lib.mapAttrs (name: value: value.file) mdbook.wrapperOptionFiles;
# TODO:
# paths = lib.mapAttrs (name: value: value.path) mdbook.wrapperOptionFiles;
}
''
for name in "''${!files[@]}"
do
# $file contains the docs built by mkMDDoc
file="''${files[$name]}"
# TODO: consider putting wrapper docs in separate files:
# path="''${paths[$name]}"
echo >> "$out"
echo "## $name" >> "$out"
echo >> "$out"
cat "$file" >> $out
done
'';
};
2024-10-21 17:17:15 +01:00
wrapperOptions =
lib.genAttrs
[
"hm"
"nixos"
"darwin"
]
(
name:
let
configuration = lib.evalModules {
modules = [
../../wrappers/modules/${name}.nix
{
# Ignore definitions for missing options
_module.check = false;
}
];
};
in
removeUnwanted configuration.options
);
in
2024-10-23 18:16:03 +01:00
pkgs.stdenv.mkDerivation (finalAttrs: {
2024-05-05 19:39:35 +02:00
name = "nixvim-docs";
2024-10-23 18:16:03 +01:00
# Use structured attrs to avoid "bash: argument list too long" errors
__structuredAttrs = true;
2024-05-05 19:39:35 +02:00
phases = [ "buildPhase" ];
buildInputs = [
pkgs.mdbook
pkgs.mdbook-alerts
];
2024-10-23 18:16:03 +01:00
2024-08-30 14:37:32 -05:00
inputs = lib.sourceFilesBySuffices ./. [
2024-05-05 19:39:35 +02:00
".md"
".toml"
".js"
];
2024-05-05 19:39:35 +02:00
buildPhase = ''
dest=$out/share/doc/nixvim
2024-05-05 19:39:35 +02:00
mkdir -p $dest
2024-10-23 18:16:03 +01:00
# Copy inputs into the build directory
cp -r --no-preserve=all $inputs/* ./
cp ${../../CONTRIBUTING.md} ./CONTRIBUTING.md
cp -r ${../user-guide} ./user-guide
cp -r ${../modules} ./modules
# Copy the generated MD docs into the build directory
bash -e ${finalAttrs.passthru.copy-docs}
# Patch SUMMARY.md - which defiens mdBook's table of contents
substituteInPlace ./SUMMARY.md \
--replace-fail "@NIXVIM_OPTIONS@" "$nixvimOptionsSummary"
substituteInPlace ./modules/wrapper-options.md \
--replace-fail "@WRAPPER_OPTIONS@" "$(cat ${finalAttrs.passthru.wrapperOptionDocs})"
2024-05-05 19:39:35 +02:00
mdbook build
cp -r ./book/* $dest
mkdir -p $dest/search
cp -r ${search}/* $dest/search
2024-05-05 19:39:35 +02:00
'';
2024-10-23 18:16:03 +01:00
inherit (mdbook) nixvimOptionsSummary;
passthru = {
inherit (mdbook) wrapperOptionDocs;
copy-docs = pkgs.writeShellScript "copy-docs" docs.commands;
};
})