nix --help: Display help using lowdown instead of man

Fixes #4476.
Fixes #5231.
This commit is contained in:
Eelco Dolstra 2021-09-13 14:41:28 +02:00
parent 14205debb2
commit 49a932fb18
8 changed files with 89 additions and 25 deletions

View file

@ -89,7 +89,7 @@ let
in in
let let
manpages = processCommand { filename = "nix"; command = "nix"; def = command; }; manpages = processCommand { filename = "nix"; command = "nix"; def = builtins.fromJSON command; };
summary = concatStrings (map (manpage: " - [${manpage.command}](command-ref/new-cli/${manpage.name})\n") manpages); summary = concatStrings (map (manpage: " - [${manpage.command}](command-ref/new-cli/${manpage.name})\n") manpages);
in in
(listToAttrs manpages) // { "SUMMARY.md" = summary; } (listToAttrs manpages) // { "SUMMARY.md" = summary; }

View file

@ -44,7 +44,7 @@ $(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli
$(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix $(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix
@rm -rf $@ @rm -rf $@
$(trace-gen) $(nix-eval) --write-to $@ --expr 'import doc/manual/generate-manpage.nix (builtins.fromJSON (builtins.readFile $<))' $(trace-gen) $(nix-eval) --write-to $@ --expr 'import doc/manual/generate-manpage.nix (builtins.readFile $<)'
$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix $(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix
@cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp @cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp

View file

@ -895,23 +895,41 @@ void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
return; return;
} }
Path path2 = resolveExprPath(path); Path resolvedPath = resolveExprPath(path);
if ((i = fileEvalCache.find(path2)) != fileEvalCache.end()) { if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
v = i->second; v = i->second;
return; return;
} }
printTalkative("evaluating file '%1%'", path2); printTalkative("evaluating file '%1%'", resolvedPath);
Expr * e = nullptr; Expr * e = nullptr;
auto j = fileParseCache.find(path2); auto j = fileParseCache.find(resolvedPath);
if (j != fileParseCache.end()) if (j != fileParseCache.end())
e = j->second; e = j->second;
if (!e) if (!e)
e = parseExprFromFile(checkSourcePath(path2)); e = parseExprFromFile(checkSourcePath(resolvedPath));
fileParseCache[path2] = e; cacheFile(path, resolvedPath, e, v, mustBeTrivial);
}
void EvalState::resetFileCache()
{
fileEvalCache.clear();
fileParseCache.clear();
}
void EvalState::cacheFile(
const Path & path,
const Path & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial)
{
fileParseCache[resolvedPath] = e;
try { try {
// Enforce that 'flake.nix' is a direct attrset, not a // Enforce that 'flake.nix' is a direct attrset, not a
@ -921,19 +939,12 @@ void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
throw EvalError("file '%s' must be an attribute set", path); throw EvalError("file '%s' must be an attribute set", path);
eval(e, v); eval(e, v);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", path2); addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
throw; throw;
} }
fileEvalCache[path2] = v; fileEvalCache[resolvedPath] = v;
if (path != path2) fileEvalCache[path] = v; if (path != resolvedPath) fileEvalCache[path] = v;
}
void EvalState::resetFileCache()
{
fileEvalCache.clear();
fileParseCache.clear();
} }

View file

@ -172,6 +172,14 @@ public:
trivial (i.e. doesn't require arbitrary computation). */ trivial (i.e. doesn't require arbitrary computation). */
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false); void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
/* Like `cacheFile`, but with an already parsed expression. */
void cacheFile(
const Path & path,
const Path & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial = false);
void resetFileCache(); void resetFileCache();
/* Look up a file in the search path. */ /* Look up a file in the search path. */

View file

@ -331,6 +331,7 @@ MultiCommand::MultiCommand(const Commands & commands_)
if (i == commands.end()) if (i == commands.end())
throw UsageError("'%s' is not a recognised command", s); throw UsageError("'%s' is not a recognised command", s);
command = {s, i->second()}; command = {s, i->second()};
command->second->parent = this;
}} }}
}); });

View file

@ -12,6 +12,8 @@ namespace nix {
enum HashType : char; enum HashType : char;
class MultiCommand;
class Args class Args
{ {
public: public:
@ -169,11 +171,13 @@ public:
virtual nlohmann::json toJSON(); virtual nlohmann::json toJSON();
friend class MultiCommand; friend class MultiCommand;
MultiCommand * parent = nullptr;
}; };
/* A command is an argument parser that can be executed by calling its /* A command is an argument parser that can be executed by calling its
run() method. */ run() method. */
struct Command : virtual Args struct Command : virtual public Args
{ {
friend class MultiCommand; friend class MultiCommand;
@ -193,7 +197,7 @@ typedef std::map<std::string, std::function<ref<Command>()>> Commands;
/* An argument parser that supports multiple subcommands, /* An argument parser that supports multiple subcommands,
i.e. <command> <subcommand>. */ i.e. <command> <subcommand>. */
class MultiCommand : virtual Args class MultiCommand : virtual public Args
{ {
public: public:
Commands commands; Commands commands;

View file

@ -14,7 +14,7 @@ nix_SOURCES := \
$(wildcard src/nix-instantiate/*.cc) \ $(wildcard src/nix-instantiate/*.cc) \
$(wildcard src/nix-store/*.cc) \ $(wildcard src/nix-store/*.cc) \
nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr -I src/libmain -I src/libcmd nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr -I src/libmain -I src/libcmd -I doc/manual
nix_LIBS = libexpr libmain libfetchers libstore libutil libcmd nix_LIBS = libexpr libmain libfetchers libstore libutil libcmd
@ -30,3 +30,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/main.cc: doc/manual/generate-manpage.nix.gen.hh doc/manual/utils.nix.gen.hh

View file

@ -10,6 +10,7 @@
#include "filetransfer.hh" #include "filetransfer.hh"
#include "finally.hh" #include "finally.hh"
#include "loggers.hh" #include "loggers.hh"
#include "markdown.hh"
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
@ -163,9 +164,43 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
} }
}; };
static void showHelp(std::vector<std::string> subcommand) /* Render the help for the specified subcommand to stdout using
lowdown. */
static void showHelp(std::vector<std::string> subcommand, MultiCommand & toplevel)
{ {
showManPage(subcommand.empty() ? "nix" : fmt("nix3-%s", concatStringsSep("-", subcommand))); auto mdName = subcommand.empty() ? "nix" : fmt("nix3-%s", concatStringsSep("-", subcommand));
evalSettings.restrictEval = false;
evalSettings.pureEval = false;
EvalState state({}, openStore("dummy://"));
auto vGenerateManpage = state.allocValue();
state.eval(state.parseExprFromString(
#include "generate-manpage.nix.gen.hh"
, "/"), *vGenerateManpage);
auto vUtils = state.allocValue();
state.cacheFile(
"/utils.nix", "/utils.nix",
state.parseExprFromString(
#include "utils.nix.gen.hh"
, "/"),
*vUtils);
auto vJson = state.allocValue();
mkString(*vJson, toplevel.toJSON().dump());
auto vRes = state.allocValue();
state.callFunction(*vGenerateManpage, *vJson, *vRes, noPos);
auto attr = vRes->attrs->get(state.symbols.create(mdName + ".md"));
if (!attr)
throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
auto markdown = state.forceString(*attr->value);
RunPager pager;
std::cout << renderMarkdownToTerminal(markdown) << "\n";
} }
struct CmdHelp : Command struct CmdHelp : Command
@ -194,7 +229,10 @@ struct CmdHelp : Command
void run() override void run() override
{ {
showHelp(subcommand); assert(parent);
MultiCommand * toplevel = parent;
while (toplevel->parent) toplevel = toplevel->parent;
showHelp(subcommand, *toplevel);
} }
}; };
@ -277,7 +315,7 @@ void mainWrapped(int argc, char * * argv)
} else } else
break; break;
} }
showHelp(subcommand); showHelp(subcommand, args);
return; return;
} catch (UsageError &) { } catch (UsageError &) {
if (!completions) throw; if (!completions) throw;