nix --help: Display help using lowdown instead of man
Fixes #4476. Fixes #5231.
This commit is contained in:
parent
14205debb2
commit
49a932fb18
8 changed files with 89 additions and 25 deletions
|
@ -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; }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
|
@ -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;
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue