1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-19 16:59:35 +01:00

Add 'nix doc' command

This command generates HTML docs (using mdbook) for a flake.
This commit is contained in:
Eelco Dolstra 2020-09-18 13:40:29 +02:00
parent f731443384
commit 0b337fca34
8 changed files with 251 additions and 44 deletions

View file

@ -12,14 +12,14 @@ let
in
{ description ? null, extends ? [], options ? {}, config ? ({ config }: {}) } @ inArgs:
{ doc ? null, extends ? [], options ? {}, config ? ({ config }: {}) } @ inArgs:
let thisModule = rec {
type = "module";
_module = {
inherit description extends options config;
};
inherit extends options config;
} // (if doc != null then { inherit doc; } else {});
_allModules = [thisModule] ++ builtins.concatLists (map (mod: assert mod.type or "<untyped>" == "module"; mod._allModules) extends);

View file

@ -98,14 +98,14 @@ struct CmdBundle : InstallableCommand
if (!evalState->isDerivation(*vRes))
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
auto attr1 = vRes->attrs->find(evalState->sDrvPath);
auto attr1 = vRes->attrs->get(evalState->sDrvPath);
if (!attr1)
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
PathSet context2;
StorePath drvPath = store->parseStorePath(evalState->coerceToPath(*attr1->pos, *attr1->value, context2));
auto attr2 = vRes->attrs->find(evalState->sOutPath);
auto attr2 = vRes->attrs->get(evalState->sOutPath);
if (!attr2)
throw Error("the bundler '%s' does not produce a derivation", bundler.what());

View file

@ -58,6 +58,23 @@ struct MixFlakeOptions : virtual Args, EvalCommand
{ return {}; }
};
class FlakeCommand : virtual Args, public MixFlakeOptions
{
std::string flakeUrl = ".";
public:
FlakeCommand();
FlakeRef getFlakeRef();
flake::Flake getFlake();
flake::LockedFlake lockFlake();
std::optional<FlakeRef> getFlakeRefForCompletion() override;
};
/* How to handle derivations in commands that operate on store paths. */
enum class OperateOn {
/* Operate on the output path. */

101
src/nix/doc.cc Normal file
View file

@ -0,0 +1,101 @@
#include "command.hh"
#include "common-args.hh"
#include "eval-cache.hh"
#include "eval-inline.hh"
using namespace nix;
using namespace nix::flake;
// FIXME: move
StorePath buildDerivation(EvalState & state, Value & vDerivation)
{
state.forceValue(vDerivation);
if (!state.isDerivation(vDerivation))
throw Error("value did not evaluate to a derivation");
auto aDrvPath = vDerivation.attrs->get(state.sDrvPath);
assert(aDrvPath);
PathSet context;
auto drvPath = state.store->parseStorePath(state.coerceToPath(*aDrvPath->pos, *aDrvPath->value, context));
state.store->buildPaths({{drvPath}});
auto aOutPath = vDerivation.attrs->get(state.sOutPath);
assert(aOutPath);
auto outPath = state.store->parseStorePath(state.coerceToPath(*aOutPath->pos, *aOutPath->value, context));
assert(state.store->isValidPath(outPath));
return outPath;
}
struct CmdDoc : FlakeCommand
{
std::optional<Path> outLink = "flake-doc";
bool printMarkdown = false;
CmdDoc()
{
// FIXME: cut&paste from 'nix build'.
addFlag({
.longName = "out-link",
.shortName = 'o',
.description = "path of the symlink to the build result",
.labels = {"path"},
.handler = {&outLink},
.completer = completePath
});
addFlag({
.longName = "no-link",
.description = "do not create a symlink to the build result",
.handler = {&outLink, {}},
});
addFlag({
.longName = "print-markdown",
.description = "show markdown, don't generate an HTML book",
.handler = {&this->printMarkdown, true},
});
}
void run(nix::ref<nix::Store> store) override
{
auto state = getEvalState();
auto flake = std::make_shared<LockedFlake>(lockFlake());
auto vFlake = state->allocValue();
flake::callFlake(*state, *flake, *vFlake);
auto vFun = state->allocValue();
state->eval(state->parseExprFromString(
#include "doc.nix.gen.hh"
, "/"), *vFun);
auto vRes = state->allocValue();
state->callFunction(*vFun, *vFlake, *vRes, noPos);
state->forceAttrs(*vRes, noPos);
auto markdown = vRes->attrs->get(state->symbols.create("markdown"));
assert(markdown);
if (printMarkdown) {
logger->stdout(state->forceString(*markdown->value));
return;
}
auto mdbook = vRes->attrs->get(state->symbols.create("mdbook"));
assert(mdbook);
// FIXME: ugly, needed for getFlake.
evalSettings.pureEval = false;
auto path = buildDerivation(*state, *mdbook->value);
if (outLink)
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
store2->addPermRoot(path, absPath(*outLink));
}
};
static auto r1 = registerCommand<CmdDoc>("doc");

94
src/nix/doc.nix Normal file
View file

@ -0,0 +1,94 @@
with builtins;
flake:
let
splitLines = s: filter (x: !isList x) (split "\n" s);
concatStrings = concatStringsSep "";
modules = flake.modules or {};
in
rec {
markdown =
# FIXME: split into multiple files.
"# Outputs\n\n"
+ concatStrings (map
(outputName:
" - `${outputName}` \n\n")
(attrNames flake.outputs))
+ (if modules != {} then
"# Modules\n\n"
+ concatStrings (map
(moduleName:
let
module = modules.${moduleName};
in
"## `${moduleName}`\n\n"
+ (if module._module.doc or "" != ""
then "### Synopsis\n\n${module._module.doc}\n\n"
else "")
+ (if module._module.options != {}
then
"### Options\n\n"
+ concatStrings (map
(optionName:
let option = module._module.options.${optionName}; in
" - `${optionName}` \n\n"
+ concatStrings (map (l: " ${l}\n") (splitLines option.doc))
+ "\n"
)
(attrNames module._module.options))
else "")
)
(attrNames modules))
else "");
mdbook =
let
nixpkgs = getFlake "nixpkgs";
pkgs = nixpkgs.legacyPackages.x86_64-linux; # FIXME
in
pkgs.runCommand "flake-doc"
{ buildInputs = [ pkgs.mdbook ];
}
''
mkdir $out
mkdir -p book/src
cat > book/book.toml <<EOF
[output.html]
additional-css = ["custom.css"]
EOF
cat > book/custom.css <<EOF
h1:not(:first-of-type) {
margin-top: 1.3em;
}
h2 {
margin-top: 1em;
}
h3 {
margin-top: 0.7em;
}
EOF
cat > book/src/SUMMARY.md <<EOF
# Table of Contents
- [Overview](overview.md)
EOF
cp ${toFile "flake.md" markdown} book/src/overview.md
mdbook build -d $out book
'';
}

View file

@ -20,13 +20,7 @@
using namespace nix;
using namespace nix::flake;
class FlakeCommand : virtual Args, public MixFlakeOptions
{
std::string flakeUrl = ".";
public:
FlakeCommand()
FlakeCommand::FlakeCommand()
{
expectArgs({
.label = "flake-url",
@ -38,27 +32,26 @@ public:
});
}
FlakeRef getFlakeRef()
FlakeRef FlakeCommand::getFlakeRef()
{
return parseFlakeRef(flakeUrl, absPath(".")); //FIXME
}
Flake getFlake()
Flake FlakeCommand::getFlake()
{
auto evalState = getEvalState();
return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
}
LockedFlake lockFlake()
LockedFlake FlakeCommand::lockFlake()
{
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
}
std::optional<FlakeRef> getFlakeRefForCompletion() override
std::optional<FlakeRef> FlakeCommand::getFlakeRefForCompletion()
{
return getFlakeRef();
}
};
static void printFlakeInfo(const Store & store, const Flake & flake)
{

View file

@ -31,3 +31,5 @@ src/nix-env/user-env.cc: src/nix-env/buildenv.nix.gen.hh
src/nix/develop.cc: src/nix/get-env.sh.gen.hh
src/nix-channel/nix-channel.cc: src/nix-channel/unpack-channel.nix.gen.hh
src/nix/doc.cc: src/nix/doc.nix.gen.hh

View file

@ -45,12 +45,12 @@ struct CmdListOptions : InstallableCommand
state->forceAttrs(*option->value);
std::string description = ANSI_ITALIC "<no description>" ANSI_NORMAL;
auto aDescription = option->value->attrs->get(state->symbols.create("description"));
if (aDescription)
std::string doc = ANSI_ITALIC "<no description>" ANSI_NORMAL;
auto aDoc = option->value->attrs->get(state->symbols.create("doc"));
if (aDoc)
// FIXME: render markdown.
description = state->forceString(*aDescription->value);
logger->stdout(" " ANSI_BOLD "Description:" ANSI_NORMAL " %s", description);
doc = state->forceString(*aDoc->value);
logger->stdout(" " ANSI_BOLD "Description:" ANSI_NORMAL " %s", doc);
auto aValue = aFinal->value->attrs->get(option->name);
assert(aValue);