nix --help: Group commands

This commit is contained in:
Eelco Dolstra 2020-05-05 15:18:23 +02:00
parent a721a0b114
commit f132d82a79
26 changed files with 125 additions and 55 deletions

View file

@ -1,13 +1,15 @@
#pragma once #pragma once
namespace nix namespace nix {
{
/* Some ANSI escape sequences. */ /* Some ANSI escape sequences. */
#define ANSI_NORMAL "\e[0m" #define ANSI_NORMAL "\e[0m"
#define ANSI_BOLD "\e[1m" #define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m" #define ANSI_FAINT "\e[2m"
#define ANSI_RED "\e[31;1m" #define ANSI_ITALIC "\e[3m"
#define ANSI_GREEN "\e[32;1m" #define ANSI_RED "\e[31;1m"
#define ANSI_YELLOW "\e[33;1m" #define ANSI_GREEN "\e[32;1m"
#define ANSI_BLUE "\e[34;1m" #define ANSI_YELLOW "\e[33;1m"
#define ANSI_BLUE "\e[34;1m"
} }

View file

@ -59,7 +59,7 @@ void Args::parseCmdline(const Strings & _cmdline)
void Args::printHelp(const string & programName, std::ostream & out) void Args::printHelp(const string & programName, std::ostream & out)
{ {
std::cout << "Usage: " << programName << " <FLAGS>..."; std::cout << fmt(ANSI_BOLD "Usage:" ANSI_NORMAL " %s " ANSI_ITALIC "FLAGS..." ANSI_NORMAL, programName);
for (auto & exp : expectedArgs) { for (auto & exp : expectedArgs) {
std::cout << renderLabels({exp.label}); std::cout << renderLabels({exp.label});
// FIXME: handle arity > 1 // FIXME: handle arity > 1
@ -70,11 +70,11 @@ void Args::printHelp(const string & programName, std::ostream & out)
auto s = description(); auto s = description();
if (s != "") if (s != "")
std::cout << "\nSummary: " << s << ".\n"; std::cout << "\n" ANSI_BOLD "Summary:" ANSI_NORMAL " " << s << ".\n";
if (longFlags.size()) { if (longFlags.size()) {
std::cout << "\n"; std::cout << "\n";
std::cout << "Flags:\n"; std::cout << ANSI_BOLD "Flags:" ANSI_NORMAL "\n";
printFlags(out); printFlags(out);
} }
} }
@ -181,7 +181,7 @@ std::string renderLabels(const Strings & labels)
std::string res; std::string res;
for (auto label : labels) { for (auto label : labels) {
for (auto & c : label) c = std::toupper(c); for (auto & c : label) c = std::toupper(c);
res += " <" + label + ">"; res += " " ANSI_ITALIC + label + ANSI_NORMAL;
} }
return res; return res;
} }
@ -190,10 +190,10 @@ void printTable(std::ostream & out, const Table2 & table)
{ {
size_t max = 0; size_t max = 0;
for (auto & row : table) for (auto & row : table)
max = std::max(max, row.first.size()); max = std::max(max, filterANSIEscapes(row.first, true).size());
for (auto & row : table) { for (auto & row : table) {
out << " " << row.first out << " " << row.first
<< std::string(max - row.first.size() + 2, ' ') << std::string(max - filterANSIEscapes(row.first, true).size() + 2, ' ')
<< row.second << "\n"; << row.second << "\n";
} }
} }
@ -204,8 +204,7 @@ void Command::printHelp(const string & programName, std::ostream & out)
auto exs = examples(); auto exs = examples();
if (!exs.empty()) { if (!exs.empty()) {
out << "\n"; out << "\n" ANSI_BOLD "Examples:" ANSI_NORMAL "\n";
out << "Examples:\n";
for (auto & ex : exs) for (auto & ex : exs)
out << "\n" out << "\n"
<< " " << ex.description << "\n" // FIXME: wrap << " " << ex.description << "\n" // FIXME: wrap
@ -221,49 +220,55 @@ MultiCommand::MultiCommand(const Commands & commands)
auto i = commands.find(ss[0]); auto i = commands.find(ss[0]);
if (i == commands.end()) if (i == commands.end())
throw UsageError("'%s' is not a recognised command", ss[0]); throw UsageError("'%s' is not a recognised command", ss[0]);
command = i->second(); command = {ss[0], i->second()};
command->_name = ss[0];
}}); }});
categories[Command::catDefault] = "Available commands";
} }
void MultiCommand::printHelp(const string & programName, std::ostream & out) void MultiCommand::printHelp(const string & programName, std::ostream & out)
{ {
if (command) { if (command) {
command->printHelp(programName + " " + command->name(), out); command->second->printHelp(programName + " " + command->first, out);
return; return;
} }
out << "Usage: " << programName << " <COMMAND> <FLAGS>... <ARGS>...\n"; out << fmt(ANSI_BOLD "Usage:" ANSI_NORMAL " %s " ANSI_ITALIC "COMMAND FLAGS... ARGS..." ANSI_NORMAL "\n", programName);
out << "\n"; out << "\n" ANSI_BOLD "Common flags:" ANSI_NORMAL "\n";
out << "Common flags:\n";
printFlags(out); printFlags(out);
out << "\n"; std::map<Command::Category, std::map<std::string, ref<Command>>> commandsByCategory;
out << "Available commands:\n";
Table2 table; for (auto & [name, commandFun] : commands) {
for (auto & i : commands) { auto command = commandFun();
auto command = i.second(); commandsByCategory[command->category()].insert_or_assign(name, command);
command->_name = i.first; }
auto descr = command->description();
if (!descr.empty()) for (auto & [category, commands] : commandsByCategory) {
table.push_back(std::make_pair(command->name(), descr)); out << fmt("\n" ANSI_BOLD "%s:" ANSI_NORMAL "\n", categories[category]);
Table2 table;
for (auto & [name, command] : commands) {
auto descr = command->description();
if (!descr.empty())
table.push_back(std::make_pair(name, descr));
}
printTable(out, table);
} }
printTable(out, table);
} }
bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end) bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end)
{ {
if (Args::processFlag(pos, end)) return true; if (Args::processFlag(pos, end)) return true;
if (command && command->processFlag(pos, end)) return true; if (command && command->second->processFlag(pos, end)) return true;
return false; return false;
} }
bool MultiCommand::processArgs(const Strings & args, bool finish) bool MultiCommand::processArgs(const Strings & args, bool finish)
{ {
if (command) if (command)
return command->processArgs(args, finish); return command->second->processArgs(args, finish);
else else
return Args::processArgs(args, finish); return Args::processArgs(args, finish);
} }

View file

@ -197,17 +197,10 @@ public:
run() method. */ run() method. */
struct Command : virtual Args struct Command : virtual Args
{ {
private:
std::string _name;
friend class MultiCommand; friend class MultiCommand;
public:
virtual ~Command() { } virtual ~Command() { }
std::string name() { return _name; }
virtual void prepare() { }; virtual void prepare() { };
virtual void run() = 0; virtual void run() = 0;
@ -221,6 +214,12 @@ public:
virtual Examples examples() { return Examples(); } virtual Examples examples() { return Examples(); }
typedef int Category;
static constexpr Category catDefault = 0;
virtual Category category() { return catDefault; }
void printHelp(const string & programName, std::ostream & out) override; void printHelp(const string & programName, std::ostream & out) override;
}; };
@ -233,7 +232,10 @@ class MultiCommand : virtual Args
public: public:
Commands commands; Commands commands;
std::shared_ptr<Command> command; std::map<Command::Category, std::string> categories;
// Selected command, if any.
std::optional<std::pair<std::string, ref<Command>>> command;
MultiCommand(const Commands & commands); MultiCommand(const Commands & commands);

View file

@ -34,6 +34,8 @@ struct CmdAddToStore : MixDryRun, StoreCommand
}; };
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
if (!namePart) namePart = baseNameOf(path); if (!namePart) namePart = baseNameOf(path);

View file

@ -30,9 +30,11 @@ struct CmdCatStore : StoreCommand, MixCat
std::string description() override std::string description() override
{ {
return "print the contents of a store file on stdout"; return "print the contents of a file in the Nix store on stdout";
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
cat(store->getFSAccessor()); cat(store->getFSAccessor());
@ -51,9 +53,11 @@ struct CmdCatNar : StoreCommand, MixCat
std::string description() override std::string description() override
{ {
return "print the contents of a file inside a NAR file"; return "print the contents of a file inside a NAR file on stdout";
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
cat(makeNarAccessor(make_ref<std::string>(readFile(narPath)))); cat(makeNarAccessor(make_ref<std::string>(readFile(narPath))));

View file

@ -10,6 +10,10 @@ namespace nix {
extern std::string programPath; extern std::string programPath;
static constexpr Command::Category catSecondary = 100;
static constexpr Command::Category catUtility = 101;
static constexpr Command::Category catNixInstallation = 102;
/* A command that requires a Nix store. */ /* A command that requires a Nix store. */
struct StoreCommand : virtual Command struct StoreCommand : virtual Command
{ {

View file

@ -80,6 +80,8 @@ struct CmdCopy : StorePathsCommand
}; };
} }
Category category() override { return catSecondary; }
ref<Store> createStore() override ref<Store> createStore() override
{ {
return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri); return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri);

View file

@ -328,6 +328,8 @@ struct CmdPrintDevEnv : Common
}; };
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
auto buildEnvironment = getBuildEnvironment(store).first; auto buildEnvironment = getBuildEnvironment(store).first;

View file

@ -43,6 +43,8 @@ struct CmdDoctor : StoreCommand
return "check your system for potential problems and print a PASS or FAIL for each check."; return "check your system for potential problems and print a PASS or FAIL for each check.";
} }
Category category() override { return catNixInstallation; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
logger->log("Running checks against store uri: " + store->getUri()); logger->log("Running checks against store uri: " + store->getUri());

View file

@ -20,6 +20,8 @@ struct CmdDumpPath : StorePathCommand
}; };
} }
Category category() override { return catUtility; }
void run(ref<Store> store, const StorePath & storePath) override void run(ref<Store> store, const StorePath & storePath) override
{ {
FdSink sink(STDOUT_FILENO); FdSink sink(STDOUT_FILENO);

View file

@ -25,6 +25,8 @@ struct CmdEdit : InstallableCommand
}; };
} }
Category category() override { return catSecondary; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
auto state = getEvalState(); auto state = getEvalState();

View file

@ -45,6 +45,8 @@ struct CmdEval : MixJSON, InstallableCommand
}; };
} }
Category category() override { return catSecondary; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
if (raw && json) if (raw && json)

View file

@ -41,6 +41,8 @@ struct CmdHash : Command
: "print cryptographic hash of the NAR serialisation of a path"; : "print cryptographic hash of the NAR serialisation of a path";
} }
Category category() override { return catUtility; }
void run() override void run() override
{ {
for (auto path : paths) { for (auto path : paths) {
@ -87,6 +89,8 @@ struct CmdToBase : Command
"SRI"); "SRI");
} }
Category category() override { return catUtility; }
void run() override void run() override
{ {
for (auto s : args) for (auto s : args)

View file

@ -31,6 +31,8 @@ struct CmdLog : InstallableCommand
}; };
} }
Category category() override { return catSecondary; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
settings.readOnlyMode = true; settings.readOnlyMode = true;

View file

@ -100,9 +100,11 @@ struct CmdLsStore : StoreCommand, MixLs
std::string description() override std::string description() override
{ {
return "show information about a store path"; return "show information about a path in the Nix store";
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
list(store->getFSAccessor()); list(store->getFSAccessor());
@ -131,9 +133,11 @@ struct CmdLsNar : Command, MixLs
std::string description() override std::string description() override
{ {
return "show information about the contents of a NAR file"; return "show information about a path inside a NAR file";
} }
Category category() override { return catUtility; }
void run() override void run() override
{ {
list(makeNarAccessor(make_ref<std::string>(readFile(narPath, true)))); list(makeNarAccessor(make_ref<std::string>(readFile(narPath, true))));

View file

@ -59,6 +59,12 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
NixArgs() : MultiCommand(*RegisterCommand::commands), MixCommonArgs("nix") NixArgs() : MultiCommand(*RegisterCommand::commands), MixCommonArgs("nix")
{ {
categories.clear();
categories[Command::catDefault] = "Main commands";
categories[catSecondary] = "Infrequently used commands";
categories[catUtility] = "Utility/scripting commands";
categories[catNixInstallation] = "Commands for upgrading or troubleshooting your Nix installation";
addFlag({ addFlag({
.longName = "help", .longName = "help",
.description = "show usage information", .description = "show usage information",
@ -111,8 +117,8 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
Args::printFlags(out); Args::printFlags(out);
std::cout << std::cout <<
"\n" "\n"
"In addition, most configuration settings can be overriden using '--<name> <value>'.\n" "In addition, most configuration settings can be overriden using '--" ANSI_ITALIC "name value" ANSI_NORMAL "'.\n"
"Boolean settings can be overriden using '--<name>' or '--no-<name>'. See 'nix\n" "Boolean settings can be overriden using '--" ANSI_ITALIC "name" ANSI_NORMAL "' or '--no-" ANSI_ITALIC "name" ANSI_NORMAL "'. See 'nix\n"
"--help-config' for a list of configuration settings.\n"; "--help-config' for a list of configuration settings.\n";
} }
@ -121,10 +127,10 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
MultiCommand::printHelp(programName, out); MultiCommand::printHelp(programName, out);
#if 0 #if 0
out << "\nFor full documentation, run 'man " << programName << "' or 'man " << programName << "-<COMMAND>'.\n"; out << "\nFor full documentation, run 'man " << programName << "' or 'man " << programName << "-" ANSI_ITALIC "COMMAND" ANSI_NORMAL "'.\n";
#endif #endif
std::cout << "\nNote: this program is EXPERIMENTAL and subject to change.\n"; std::cout << "\nNote: this program is " ANSI_RED "EXPERIMENTAL" ANSI_NORMAL " and subject to change.\n";
} }
void showHelpAndExit() void showHelpAndExit()
@ -191,8 +197,8 @@ void mainWrapped(int argc, char * * argv)
if (args.refresh) if (args.refresh)
settings.tarballTtl = 0; settings.tarballTtl = 0;
args.command->prepare(); args.command->second->prepare();
args.command->run(); args.command->second->run();
} }
} }

View file

@ -31,6 +31,9 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
}, },
}; };
} }
Category category() override { return catUtility; }
void run(ref<Store> store, StorePaths storePaths) override void run(ref<Store> store, StorePaths storePaths) override
{ {
auto paths = store->topoSortPaths(storePathsToSet(storePaths)); auto paths = store->topoSortPaths(storePathsToSet(storePaths));

View file

@ -23,6 +23,8 @@ struct CmdOptimiseStore : StoreCommand
}; };
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
store->optimiseStore(); store->optimiseStore();

View file

@ -29,6 +29,8 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
return "query information about store paths"; return "query information about store paths";
} }
Category category() override { return catSecondary; }
Examples examples() override Examples examples() override
{ {
return { return {

View file

@ -21,6 +21,8 @@ struct CmdPingStore : StoreCommand
}; };
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
store->connect(); store->connect();

View file

@ -13,6 +13,8 @@ struct CmdShowConfig : Command, MixJSON
return "show the Nix configuration"; return "show the Nix configuration";
} }
Category category() override { return catUtility; }
void run() override void run() override
{ {
if (json) { if (json) {

View file

@ -42,6 +42,8 @@ struct CmdShowDerivation : InstallablesCommand
}; };
} }
Category category() override { return catUtility; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
auto drvPaths = toDerivations(store, installables, true); auto drvPaths = toDerivations(store, installables, true);

View file

@ -27,6 +27,8 @@ struct CmdCopySigs : StorePathsCommand
return "copy path signatures from substituters (like binary caches)"; return "copy path signatures from substituters (like binary caches)";
} }
Category category() override { return catUtility; }
void run(ref<Store> store, StorePaths storePaths) override void run(ref<Store> store, StorePaths storePaths) override
{ {
if (substituterUris.empty()) if (substituterUris.empty())
@ -112,6 +114,8 @@ struct CmdSignPaths : StorePathsCommand
return "sign the specified paths"; return "sign the specified paths";
} }
Category category() override { return catUtility; }
void run(ref<Store> store, StorePaths storePaths) override void run(ref<Store> store, StorePaths storePaths) override
{ {
if (secretKeyFile.empty()) if (secretKeyFile.empty())

View file

@ -51,6 +51,8 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
}; };
} }
Category category() override { return catNixInstallation; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
evalSettings.pureEval = true; evalSettings.pureEval = true;

View file

@ -49,6 +49,8 @@ struct CmdVerify : StorePathsCommand
}; };
} }
Category category() override { return catSecondary; }
void run(ref<Store> store, StorePaths storePaths) override void run(ref<Store> store, StorePaths storePaths) override
{ {
std::vector<ref<Store>> substituters; std::vector<ref<Store>> substituters;

View file

@ -68,6 +68,8 @@ struct CmdWhyDepends : SourceExprCommand
}; };
} }
Category category() override { return catSecondary; }
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
auto package = parseInstallable(*this, store, _package, false); auto package = parseInstallable(*this, store, _package, false);