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
namespace nix
{
/* Some ANSI escape sequences. */
#define ANSI_NORMAL "\e[0m"
#define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m"
#define ANSI_RED "\e[31;1m"
#define ANSI_GREEN "\e[32;1m"
#define ANSI_YELLOW "\e[33;1m"
#define ANSI_BLUE "\e[34;1m"
namespace nix {
/* Some ANSI escape sequences. */
#define ANSI_NORMAL "\e[0m"
#define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m"
#define ANSI_ITALIC "\e[3m"
#define ANSI_RED "\e[31;1m"
#define ANSI_GREEN "\e[32;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)
{
std::cout << "Usage: " << programName << " <FLAGS>...";
std::cout << fmt(ANSI_BOLD "Usage:" ANSI_NORMAL " %s " ANSI_ITALIC "FLAGS..." ANSI_NORMAL, programName);
for (auto & exp : expectedArgs) {
std::cout << renderLabels({exp.label});
// FIXME: handle arity > 1
@ -70,11 +70,11 @@ void Args::printHelp(const string & programName, std::ostream & out)
auto s = description();
if (s != "")
std::cout << "\nSummary: " << s << ".\n";
std::cout << "\n" ANSI_BOLD "Summary:" ANSI_NORMAL " " << s << ".\n";
if (longFlags.size()) {
std::cout << "\n";
std::cout << "Flags:\n";
std::cout << ANSI_BOLD "Flags:" ANSI_NORMAL "\n";
printFlags(out);
}
}
@ -181,7 +181,7 @@ std::string renderLabels(const Strings & labels)
std::string res;
for (auto label : labels) {
for (auto & c : label) c = std::toupper(c);
res += " <" + label + ">";
res += " " ANSI_ITALIC + label + ANSI_NORMAL;
}
return res;
}
@ -190,10 +190,10 @@ void printTable(std::ostream & out, const Table2 & table)
{
size_t max = 0;
for (auto & row : table)
max = std::max(max, row.first.size());
max = std::max(max, filterANSIEscapes(row.first, true).size());
for (auto & row : table) {
out << " " << row.first
<< std::string(max - row.first.size() + 2, ' ')
<< std::string(max - filterANSIEscapes(row.first, true).size() + 2, ' ')
<< row.second << "\n";
}
}
@ -204,8 +204,7 @@ void Command::printHelp(const string & programName, std::ostream & out)
auto exs = examples();
if (!exs.empty()) {
out << "\n";
out << "Examples:\n";
out << "\n" ANSI_BOLD "Examples:" ANSI_NORMAL "\n";
for (auto & ex : exs)
out << "\n"
<< " " << ex.description << "\n" // FIXME: wrap
@ -221,49 +220,55 @@ MultiCommand::MultiCommand(const Commands & commands)
auto i = commands.find(ss[0]);
if (i == commands.end())
throw UsageError("'%s' is not a recognised command", ss[0]);
command = i->second();
command->_name = ss[0];
command = {ss[0], i->second()};
}});
categories[Command::catDefault] = "Available commands";
}
void MultiCommand::printHelp(const string & programName, std::ostream & out)
{
if (command) {
command->printHelp(programName + " " + command->name(), out);
command->second->printHelp(programName + " " + command->first, out);
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 << "Common flags:\n";
out << "\n" ANSI_BOLD "Common flags:" ANSI_NORMAL "\n";
printFlags(out);
out << "\n";
out << "Available commands:\n";
std::map<Command::Category, std::map<std::string, ref<Command>>> commandsByCategory;
Table2 table;
for (auto & i : commands) {
auto command = i.second();
command->_name = i.first;
auto descr = command->description();
if (!descr.empty())
table.push_back(std::make_pair(command->name(), descr));
for (auto & [name, commandFun] : commands) {
auto command = commandFun();
commandsByCategory[command->category()].insert_or_assign(name, command);
}
for (auto & [category, commands] : commandsByCategory) {
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)
{
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;
}
bool MultiCommand::processArgs(const Strings & args, bool finish)
{
if (command)
return command->processArgs(args, finish);
return command->second->processArgs(args, finish);
else
return Args::processArgs(args, finish);
}

View file

@ -197,17 +197,10 @@ public:
run() method. */
struct Command : virtual Args
{
private:
std::string _name;
friend class MultiCommand;
public:
virtual ~Command() { }
std::string name() { return _name; }
virtual void prepare() { };
virtual void run() = 0;
@ -221,6 +214,12 @@ public:
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;
};
@ -233,7 +232,10 @@ class MultiCommand : virtual Args
public:
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);

View file

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

View file

@ -30,9 +30,11 @@ struct CmdCatStore : StoreCommand, MixCat
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
{
cat(store->getFSAccessor());
@ -51,9 +53,11 @@ struct CmdCatNar : StoreCommand, MixCat
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
{
cat(makeNarAccessor(make_ref<std::string>(readFile(narPath))));

View file

@ -10,6 +10,10 @@ namespace nix {
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. */
struct StoreCommand : virtual Command
{

View file

@ -80,6 +80,8 @@ struct CmdCopy : StorePathsCommand
};
}
Category category() override { return catSecondary; }
ref<Store> createStore() override
{
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
{
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.";
}
Category category() override { return catNixInstallation; }
void run(ref<Store> store) override
{
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
{
FdSink sink(STDOUT_FILENO);

View file

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

View file

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

View file

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

View file

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

View file

@ -100,9 +100,11 @@ struct CmdLsStore : StoreCommand, MixLs
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
{
list(store->getFSAccessor());
@ -131,9 +133,11 @@ struct CmdLsNar : Command, MixLs
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
{
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")
{
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({
.longName = "help",
.description = "show usage information",
@ -111,8 +117,8 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
Args::printFlags(out);
std::cout <<
"\n"
"In addition, most configuration settings can be overriden using '--<name> <value>'.\n"
"Boolean settings can be overriden using '--<name>' or '--no-<name>'. See 'nix\n"
"In addition, most configuration settings can be overriden using '--" ANSI_ITALIC "name value" ANSI_NORMAL "'.\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";
}
@ -121,10 +127,10 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
MultiCommand::printHelp(programName, out);
#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
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()
@ -191,8 +197,8 @@ void mainWrapped(int argc, char * * argv)
if (args.refresh)
settings.tarballTtl = 0;
args.command->prepare();
args.command->run();
args.command->second->prepare();
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
{
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
{
store->optimiseStore();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -51,6 +51,8 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
};
}
Category category() override { return catNixInstallation; }
void run(ref<Store> store) override
{
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
{
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
{
auto package = parseInstallable(*this, store, _package, false);