mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-29 09:06:15 +02:00
Improve the error message for “multicommands” commands (#9510)
* Factor out the default `MultiCommand` behavior All the `MultiCommand`s had (nearly) the same behavior when called without a subcommand. Factor out this behavior into the `NixMultiCommand` class. * Display the list of available subcommands when none is specified Whenever a user runs a command that excepts a subcommand, add the list of available subcommands to the error message. * Print the multi-command lists as Markdown lists This takes more screen real estate, but is also much more readable than a comma-separated list
This commit is contained in:
parent
fbc855b3c3
commit
7fff625e39
15 changed files with 55 additions and 89 deletions
|
@ -1,4 +1,5 @@
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
|
#include "markdown.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
|
@ -34,6 +35,19 @@ nlohmann::json NixMultiCommand::toJSON()
|
||||||
return MultiCommand::toJSON();
|
return MultiCommand::toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NixMultiCommand::run()
|
||||||
|
{
|
||||||
|
if (!command) {
|
||||||
|
std::set<std::string> subCommandTextLines;
|
||||||
|
for (auto & [name, _] : commands)
|
||||||
|
subCommandTextLines.insert(fmt("- `%s`", name));
|
||||||
|
std::string markdownError = fmt("`nix %s` requires a sub-command. Available sub-commands:\n\n%s\n",
|
||||||
|
commandName, concatStringsSep("\n", subCommandTextLines));
|
||||||
|
throw UsageError(renderMarkdownToTerminal(markdownError));
|
||||||
|
}
|
||||||
|
command->second->run();
|
||||||
|
}
|
||||||
|
|
||||||
StoreCommand::StoreCommand()
|
StoreCommand::StoreCommand()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,13 @@ static constexpr Command::Category catNixInstallation = 102;
|
||||||
|
|
||||||
static constexpr auto installablesCategory = "Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)";
|
static constexpr auto installablesCategory = "Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)";
|
||||||
|
|
||||||
struct NixMultiCommand : virtual MultiCommand, virtual Command
|
struct NixMultiCommand : MultiCommand, virtual Command
|
||||||
{
|
{
|
||||||
nlohmann::json toJSON() override;
|
nlohmann::json toJSON() override;
|
||||||
|
|
||||||
|
using MultiCommand::MultiCommand;
|
||||||
|
|
||||||
|
virtual void run() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// For the overloaded run methods
|
// For the overloaded run methods
|
||||||
|
|
|
@ -483,7 +483,7 @@ bool Args::processArgs(const Strings & args, bool finish)
|
||||||
if (!anyCompleted)
|
if (!anyCompleted)
|
||||||
exp.handler.fun(ss);
|
exp.handler.fun(ss);
|
||||||
|
|
||||||
/* Move the list element to the processedArgs. This is almost the same as
|
/* Move the list element to the processedArgs. This is almost the same as
|
||||||
`processedArgs.push_back(expectedArgs.front()); expectedArgs.pop_front()`,
|
`processedArgs.push_back(expectedArgs.front()); expectedArgs.pop_front()`,
|
||||||
except that it will only adjust the next and prev pointers of the list
|
except that it will only adjust the next and prev pointers of the list
|
||||||
elements, meaning the actual contents don't move in memory. This is
|
elements, meaning the actual contents don't move in memory. This is
|
||||||
|
@ -622,8 +622,9 @@ std::optional<ExperimentalFeature> Command::experimentalFeature ()
|
||||||
return { Xp::NixCommand };
|
return { Xp::NixCommand };
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiCommand::MultiCommand(const Commands & commands_)
|
MultiCommand::MultiCommand(std::string_view commandName, const Commands & commands_)
|
||||||
: commands(commands_)
|
: commands(commands_)
|
||||||
|
, commandName(commandName)
|
||||||
{
|
{
|
||||||
expectArgs({
|
expectArgs({
|
||||||
.label = "subcommand",
|
.label = "subcommand",
|
||||||
|
|
|
@ -223,11 +223,11 @@ protected:
|
||||||
std::list<ExpectedArg> expectedArgs;
|
std::list<ExpectedArg> expectedArgs;
|
||||||
/**
|
/**
|
||||||
* List of processed positional argument forms.
|
* List of processed positional argument forms.
|
||||||
*
|
*
|
||||||
* All items removed from `expectedArgs` are added here. After all
|
* All items removed from `expectedArgs` are added here. After all
|
||||||
* arguments were processed, this list should be exactly the same as
|
* arguments were processed, this list should be exactly the same as
|
||||||
* `expectedArgs` was before.
|
* `expectedArgs` was before.
|
||||||
*
|
*
|
||||||
* This list is used to extend the lifetime of the argument forms.
|
* This list is used to extend the lifetime of the argument forms.
|
||||||
* If this is not done, some closures that reference the command
|
* If this is not done, some closures that reference the command
|
||||||
* itself will segfault.
|
* itself will segfault.
|
||||||
|
@ -356,13 +356,16 @@ public:
|
||||||
*/
|
*/
|
||||||
std::optional<std::pair<std::string, ref<Command>>> command;
|
std::optional<std::pair<std::string, ref<Command>>> command;
|
||||||
|
|
||||||
MultiCommand(const Commands & commands);
|
MultiCommand(std::string_view commandName, const Commands & commands);
|
||||||
|
|
||||||
bool processFlag(Strings::iterator & pos, Strings::iterator end) override;
|
bool processFlag(Strings::iterator & pos, Strings::iterator end) override;
|
||||||
|
|
||||||
bool processArgs(const Strings & args, bool finish) override;
|
bool processArgs(const Strings & args, bool finish) override;
|
||||||
|
|
||||||
nlohmann::json toJSON() override;
|
nlohmann::json toJSON() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string commandName = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
Strings argvToStrings(int argc, char * * argv);
|
Strings argvToStrings(int argc, char * * argv);
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
struct CmdConfig : virtual NixMultiCommand
|
struct CmdConfig : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdConfig() : MultiCommand(RegisterCommand::getCommandsFor({"config"}))
|
CmdConfig() : NixMultiCommand("config", RegisterCommand::getCommandsFor({"config"}))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
@ -18,13 +18,6 @@ struct CmdConfig : virtual NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catUtility; }
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix config' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CmdConfigShow : Command, MixJSON
|
struct CmdConfigShow : Command, MixJSON
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
struct CmdDerivation : virtual NixMultiCommand
|
struct CmdDerivation : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdDerivation() : MultiCommand(RegisterCommand::getCommandsFor({"derivation"}))
|
CmdDerivation() : NixMultiCommand("derivation", RegisterCommand::getCommandsFor({"derivation"}))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
@ -13,13 +13,6 @@ struct CmdDerivation : virtual NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catUtility; }
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix derivation' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdDerivation = registerCommand<CmdDerivation>("derivation");
|
static auto rCmdDerivation = registerCommand<CmdDerivation>("derivation");
|
||||||
|
|
|
@ -1399,7 +1399,9 @@ struct CmdFlakePrefetch : FlakeCommand, MixJSON
|
||||||
struct CmdFlake : NixMultiCommand
|
struct CmdFlake : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdFlake()
|
CmdFlake()
|
||||||
: MultiCommand({
|
: NixMultiCommand(
|
||||||
|
"flake",
|
||||||
|
{
|
||||||
{"update", []() { return make_ref<CmdFlakeUpdate>(); }},
|
{"update", []() { return make_ref<CmdFlakeUpdate>(); }},
|
||||||
{"lock", []() { return make_ref<CmdFlakeLock>(); }},
|
{"lock", []() { return make_ref<CmdFlakeLock>(); }},
|
||||||
{"metadata", []() { return make_ref<CmdFlakeMetadata>(); }},
|
{"metadata", []() { return make_ref<CmdFlakeMetadata>(); }},
|
||||||
|
@ -1429,10 +1431,8 @@ struct CmdFlake : NixMultiCommand
|
||||||
|
|
||||||
void run() override
|
void run() override
|
||||||
{
|
{
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix flake' requires a sub-command.");
|
|
||||||
experimentalFeatureSettings.require(Xp::Flakes);
|
experimentalFeatureSettings.require(Xp::Flakes);
|
||||||
command->second->run();
|
NixMultiCommand::run();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,9 @@ struct CmdToBase : Command
|
||||||
struct CmdHash : NixMultiCommand
|
struct CmdHash : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdHash()
|
CmdHash()
|
||||||
: MultiCommand({
|
: NixMultiCommand(
|
||||||
|
"hash",
|
||||||
|
{
|
||||||
{"file", []() { return make_ref<CmdHashBase>(FileIngestionMethod::Flat);; }},
|
{"file", []() { return make_ref<CmdHashBase>(FileIngestionMethod::Flat);; }},
|
||||||
{"path", []() { return make_ref<CmdHashBase>(FileIngestionMethod::Recursive); }},
|
{"path", []() { return make_ref<CmdHashBase>(FileIngestionMethod::Recursive); }},
|
||||||
{"to-base16", []() { return make_ref<CmdToBase>(HashFormat::Base16); }},
|
{"to-base16", []() { return make_ref<CmdToBase>(HashFormat::Base16); }},
|
||||||
|
@ -146,13 +148,6 @@ struct CmdHash : NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catUtility; }
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix hash' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdHash = registerCommand<CmdHash>("hash");
|
static auto rCmdHash = registerCommand<CmdHash>("hash");
|
||||||
|
|
|
@ -67,7 +67,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
|
||||||
bool helpRequested = false;
|
bool helpRequested = false;
|
||||||
bool showVersion = false;
|
bool showVersion = false;
|
||||||
|
|
||||||
NixArgs() : MultiCommand(RegisterCommand::getCommandsFor({})), MixCommonArgs("nix")
|
NixArgs() : MultiCommand("", RegisterCommand::getCommandsFor({})), MixCommonArgs("nix")
|
||||||
{
|
{
|
||||||
categories.clear();
|
categories.clear();
|
||||||
categories[catHelp] = "Help commands";
|
categories[catHelp] = "Help commands";
|
||||||
|
|
|
@ -4,7 +4,7 @@ using namespace nix;
|
||||||
|
|
||||||
struct CmdNar : NixMultiCommand
|
struct CmdNar : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdNar() : MultiCommand(RegisterCommand::getCommandsFor({"nar"}))
|
CmdNar() : NixMultiCommand("nar", RegisterCommand::getCommandsFor({"nar"}))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
@ -20,13 +20,6 @@ struct CmdNar : NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catUtility; }
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix nar' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdNar = registerCommand<CmdNar>("nar");
|
static auto rCmdNar = registerCommand<CmdNar>("nar");
|
||||||
|
|
|
@ -825,7 +825,9 @@ struct CmdProfileWipeHistory : virtual StoreCommand, MixDefaultProfile, MixDryRu
|
||||||
struct CmdProfile : NixMultiCommand
|
struct CmdProfile : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdProfile()
|
CmdProfile()
|
||||||
: MultiCommand({
|
: NixMultiCommand(
|
||||||
|
"profile",
|
||||||
|
{
|
||||||
{"install", []() { return make_ref<CmdProfileInstall>(); }},
|
{"install", []() { return make_ref<CmdProfileInstall>(); }},
|
||||||
{"remove", []() { return make_ref<CmdProfileRemove>(); }},
|
{"remove", []() { return make_ref<CmdProfileRemove>(); }},
|
||||||
{"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
|
{"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
|
||||||
|
@ -848,13 +850,6 @@ struct CmdProfile : NixMultiCommand
|
||||||
#include "profile.md"
|
#include "profile.md"
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix profile' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdProfile = registerCommand<CmdProfile>("profile");
|
static auto rCmdProfile = registerCommand<CmdProfile>("profile");
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
struct CmdRealisation : virtual NixMultiCommand
|
struct CmdRealisation : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdRealisation() : MultiCommand(RegisterCommand::getCommandsFor({"realisation"}))
|
CmdRealisation() : NixMultiCommand("realisation", RegisterCommand::getCommandsFor({"realisation"}))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
@ -16,13 +16,6 @@ struct CmdRealisation : virtual NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catUtility; }
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix realisation' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdRealisation = registerCommand<CmdRealisation>("realisation");
|
static auto rCmdRealisation = registerCommand<CmdRealisation>("realisation");
|
||||||
|
|
|
@ -196,10 +196,12 @@ struct CmdRegistryPin : RegistryCommand, EvalCommand
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CmdRegistry : virtual NixMultiCommand
|
struct CmdRegistry : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdRegistry()
|
CmdRegistry()
|
||||||
: MultiCommand({
|
: NixMultiCommand(
|
||||||
|
"registry",
|
||||||
|
{
|
||||||
{"list", []() { return make_ref<CmdRegistryList>(); }},
|
{"list", []() { return make_ref<CmdRegistryList>(); }},
|
||||||
{"add", []() { return make_ref<CmdRegistryAdd>(); }},
|
{"add", []() { return make_ref<CmdRegistryAdd>(); }},
|
||||||
{"remove", []() { return make_ref<CmdRegistryRemove>(); }},
|
{"remove", []() { return make_ref<CmdRegistryRemove>(); }},
|
||||||
|
@ -221,14 +223,6 @@ struct CmdRegistry : virtual NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catSecondary; }
|
Category category() override { return catSecondary; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
experimentalFeatureSettings.require(Xp::Flakes);
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix registry' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdRegistry = registerCommand<CmdRegistry>("registry");
|
static auto rCmdRegistry = registerCommand<CmdRegistry>("registry");
|
||||||
|
|
|
@ -205,7 +205,9 @@ struct CmdKeyConvertSecretToPublic : Command
|
||||||
struct CmdKey : NixMultiCommand
|
struct CmdKey : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdKey()
|
CmdKey()
|
||||||
: MultiCommand({
|
: NixMultiCommand(
|
||||||
|
"key",
|
||||||
|
{
|
||||||
{"generate-secret", []() { return make_ref<CmdKeyGenerateSecret>(); }},
|
{"generate-secret", []() { return make_ref<CmdKeyGenerateSecret>(); }},
|
||||||
{"convert-secret-to-public", []() { return make_ref<CmdKeyConvertSecretToPublic>(); }},
|
{"convert-secret-to-public", []() { return make_ref<CmdKeyConvertSecretToPublic>(); }},
|
||||||
})
|
})
|
||||||
|
@ -218,13 +220,6 @@ struct CmdKey : NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catUtility; }
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix key' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdKey = registerCommand<CmdKey>("key");
|
static auto rCmdKey = registerCommand<CmdKey>("key");
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
struct CmdStore : virtual NixMultiCommand
|
struct CmdStore : NixMultiCommand
|
||||||
{
|
{
|
||||||
CmdStore() : MultiCommand(RegisterCommand::getCommandsFor({"store"}))
|
CmdStore() : NixMultiCommand("store", RegisterCommand::getCommandsFor({"store"}))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
@ -13,13 +13,6 @@ struct CmdStore : virtual NixMultiCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
Category category() override { return catUtility; }
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
if (!command)
|
|
||||||
throw UsageError("'nix store' requires a sub-command.");
|
|
||||||
command->second->run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdStore = registerCommand<CmdStore>("store");
|
static auto rCmdStore = registerCommand<CmdStore>("store");
|
||||||
|
|
Loading…
Reference in a new issue