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:
parent
f731443384
commit
0b337fca34
8 changed files with 251 additions and 44 deletions
|
|
@ -12,14 +12,14 @@ let
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
{ description ? null, extends ? [], options ? {}, config ? ({ config }: {}) } @ inArgs:
|
{ doc ? null, extends ? [], options ? {}, config ? ({ config }: {}) } @ inArgs:
|
||||||
|
|
||||||
let thisModule = rec {
|
let thisModule = rec {
|
||||||
type = "module";
|
type = "module";
|
||||||
|
|
||||||
_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);
|
_allModules = [thisModule] ++ builtins.concatLists (map (mod: assert mod.type or "<untyped>" == "module"; mod._allModules) extends);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,14 @@ struct CmdBundle : InstallableCommand
|
||||||
if (!evalState->isDerivation(*vRes))
|
if (!evalState->isDerivation(*vRes))
|
||||||
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
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)
|
if (!attr1)
|
||||||
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
||||||
|
|
||||||
PathSet context2;
|
PathSet context2;
|
||||||
StorePath drvPath = store->parseStorePath(evalState->coerceToPath(*attr1->pos, *attr1->value, 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)
|
if (!attr2)
|
||||||
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
throw Error("the bundler '%s' does not produce a derivation", bundler.what());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,23 @@ struct MixFlakeOptions : virtual Args, EvalCommand
|
||||||
{ return {}; }
|
{ 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. */
|
/* How to handle derivations in commands that operate on store paths. */
|
||||||
enum class OperateOn {
|
enum class OperateOn {
|
||||||
/* Operate on the output path. */
|
/* Operate on the output path. */
|
||||||
|
|
|
||||||
101
src/nix/doc.cc
Normal file
101
src/nix/doc.cc
Normal 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
94
src/nix/doc.nix
Normal 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
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
@ -20,45 +20,38 @@
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
using namespace nix::flake;
|
using namespace nix::flake;
|
||||||
|
|
||||||
class FlakeCommand : virtual Args, public MixFlakeOptions
|
FlakeCommand::FlakeCommand()
|
||||||
{
|
{
|
||||||
std::string flakeUrl = ".";
|
expectArgs({
|
||||||
|
.label = "flake-url",
|
||||||
|
.optional = true,
|
||||||
|
.handler = {&flakeUrl},
|
||||||
|
.completer = {[&](size_t, std::string_view prefix) {
|
||||||
|
completeFlakeRef(getStore(), prefix);
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
FlakeRef FlakeCommand::getFlakeRef()
|
||||||
|
{
|
||||||
|
return parseFlakeRef(flakeUrl, absPath(".")); //FIXME
|
||||||
|
}
|
||||||
|
|
||||||
FlakeCommand()
|
Flake FlakeCommand::getFlake()
|
||||||
{
|
{
|
||||||
expectArgs({
|
auto evalState = getEvalState();
|
||||||
.label = "flake-url",
|
return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
|
||||||
.optional = true,
|
}
|
||||||
.handler = {&flakeUrl},
|
|
||||||
.completer = {[&](size_t, std::string_view prefix) {
|
|
||||||
completeFlakeRef(getStore(), prefix);
|
|
||||||
}}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
FlakeRef getFlakeRef()
|
LockedFlake FlakeCommand::lockFlake()
|
||||||
{
|
{
|
||||||
return parseFlakeRef(flakeUrl, absPath(".")); //FIXME
|
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
Flake getFlake()
|
std::optional<FlakeRef> FlakeCommand::getFlakeRefForCompletion()
|
||||||
{
|
{
|
||||||
auto evalState = getEvalState();
|
return getFlakeRef();
|
||||||
return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
LockedFlake lockFlake()
|
|
||||||
{
|
|
||||||
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<FlakeRef> getFlakeRefForCompletion() override
|
|
||||||
{
|
|
||||||
return getFlakeRef();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void printFlakeInfo(const Store & store, const Flake & flake)
|
static void printFlakeInfo(const Store & store, const Flake & flake)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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/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-channel/nix-channel.cc: src/nix-channel/unpack-channel.nix.gen.hh
|
||||||
|
|
||||||
|
src/nix/doc.cc: src/nix/doc.nix.gen.hh
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,12 @@ struct CmdListOptions : InstallableCommand
|
||||||
|
|
||||||
state->forceAttrs(*option->value);
|
state->forceAttrs(*option->value);
|
||||||
|
|
||||||
std::string description = ANSI_ITALIC "<no description>" ANSI_NORMAL;
|
std::string doc = ANSI_ITALIC "<no description>" ANSI_NORMAL;
|
||||||
auto aDescription = option->value->attrs->get(state->symbols.create("description"));
|
auto aDoc = option->value->attrs->get(state->symbols.create("doc"));
|
||||||
if (aDescription)
|
if (aDoc)
|
||||||
// FIXME: render markdown.
|
// FIXME: render markdown.
|
||||||
description = state->forceString(*aDescription->value);
|
doc = state->forceString(*aDoc->value);
|
||||||
logger->stdout(" " ANSI_BOLD "Description:" ANSI_NORMAL " %s", description);
|
logger->stdout(" " ANSI_BOLD "Description:" ANSI_NORMAL " %s", doc);
|
||||||
|
|
||||||
auto aValue = aFinal->value->attrs->get(option->name);
|
auto aValue = aFinal->value->attrs->get(option->name);
|
||||||
assert(aValue);
|
assert(aValue);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue