aboutsummaryrefslogtreecommitdiff
path: root/src/zen/zen.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zen/zen.cpp')
-rw-r--r--src/zen/zen.cpp182
1 files changed, 144 insertions, 38 deletions
diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp
index 87d83cc77..3cc52f53b 100644
--- a/src/zen/zen.cpp
+++ b/src/zen/zen.cpp
@@ -9,27 +9,26 @@
#include "cmds/bench_cmd.h"
#include "cmds/builds_cmd.h"
#include "cmds/cache_cmd.h"
-#include "cmds/copy_cmd.h"
+#include "cmds/compute_cmd.h"
#include "cmds/dedup_cmd.h"
#include "cmds/exec_cmd.h"
#include "cmds/help_cmd.h"
+#include "cmds/history_cmd.h"
#include "cmds/hub_cmd.h"
#include "cmds/info_cmd.h"
#include "cmds/print_cmd.h"
#include "cmds/projectstore_cmd.h"
-#include "cmds/rpcreplay_cmd.h"
-#include "cmds/run_cmd.h"
#include "cmds/serve_cmd.h"
#include "cmds/service_cmd.h"
#include "cmds/status_cmd.h"
#include "cmds/top_cmd.h"
-#include "cmds/trace_cmd.h"
#include "cmds/ui_cmd.h"
#include "cmds/up_cmd.h"
#include "cmds/version_cmd.h"
#include "cmds/vfs_cmd.h"
#include "cmds/wipe_cmd.h"
#include "cmds/workspaces_cmd.h"
+#include "trace/trace_cmd.h"
#include <zencore/callstack.h>
#include <zencore/config.h>
@@ -44,9 +43,12 @@
#include <zencore/trace.h>
#include <zencore/windows.h>
#include <zenhttp/httpcommon.h>
+#include <zenutil/config/commandlineoptions.h>
#include <zenutil/config/environmentoptions.h>
#include <zenutil/consoletui.h>
+#include <zenutil/invocationhistory.h>
#include <zenutil/logging.h>
+#include <zenutil/suggest.h>
#include <zenutil/workerpools.h>
#include <zenutil/zenserverprocess.h>
@@ -56,7 +58,7 @@
#include <zencore/memory/memorytrace.h>
#include <zencore/memory/newdelete.h>
-#include "progressbar.h"
+#include "consoleprogress.h"
#if ZEN_WITH_TESTS
# include <zencore/testing.h>
@@ -189,6 +191,9 @@ ZenCmdBase::ParseOptionsPermissive(cxxopts::Options& CmdOptions, int argc, char*
{
CmdOptions.set_width(TuiConsoleColumns(80));
CmdOptions.allow_unrecognised_options();
+ // Revert the flag on scope exit so re-parsing the same Options later is strict.
+ // cxxopts has no getter for the previous state, so we unconditionally clear it.
+ auto _ = MakeGuard([&]() { CmdOptions.disallow_unrecognised_options(); });
cxxopts::ParseResult Result;
@@ -271,20 +276,34 @@ ZenCmdWithSubCommands::PrintHelp()
Options().set_width(TuiConsoleColumns(80));
printf("%s\n", Options().help(Groups).c_str());
- // Append subcommand listing.
+ // Append subcommand listing. When a subcommand has aliases, display them as
+ // "name|alias1|alias2" in the left column so callers discover the alternate spellings.
+ auto FormatSubCmdName = [](ZenSubCmdBase& SubCmd) {
+ std::string Name(SubCmd.SubOptions().program());
+ for (const std::string& Alias : SubCmd.Aliases())
+ {
+ Name.push_back('|');
+ Name.append(Alias);
+ }
+ return Name;
+ };
+
+ std::vector<std::string> FormattedNames;
+ FormattedNames.reserve(m_SubCommands.size());
size_t MaxNameLen = 0;
for (ZenSubCmdBase* SubCmd : m_SubCommands)
{
- MaxNameLen = std::max(MaxNameLen, SubCmd->SubOptions().program().size());
+ FormattedNames.push_back(FormatSubCmdName(*SubCmd));
+ MaxNameLen = std::max(MaxNameLen, FormattedNames.back().size());
}
printf("subcommands:\n");
- for (ZenSubCmdBase* SubCmd : m_SubCommands)
+ for (size_t i = 0; i < m_SubCommands.size(); ++i)
{
printf(" %-*s %s\n",
static_cast<int>(MaxNameLen),
- SubCmd->SubOptions().program().c_str(),
- std::string(SubCmd->Description()).c_str());
+ FormattedNames[i].c_str(),
+ std::string(m_SubCommands[i]->Description()).c_str());
}
printf("\nFor global options run: zen --help\n");
}
@@ -309,16 +328,33 @@ ZenCmdWithSubCommands::PrintSubCommandHelp(cxxopts::Options& SubCmdOptions)
void
ZenCmdWithSubCommands::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
- std::vector<cxxopts::Options*> SubOptionPtrs;
- SubOptionPtrs.reserve(m_SubCommands.size());
- for (ZenSubCmdBase* SubCmd : m_SubCommands)
- {
- SubOptionPtrs.push_back(&SubCmd->SubOptions());
- }
-
+ ZenSubCmdBase* MatchedSubCmd = nullptr;
cxxopts::Options* MatchedSubOption = nullptr;
std::vector<char*> SubCommandArguments;
- int ParentArgc = GetSubCommand(Options(), argc, argv, SubOptionPtrs, MatchedSubOption, SubCommandArguments);
+ int ParentArgc = argc;
+ for (int i = 1; i < argc; ++i)
+ {
+ std::string_view Arg(argv[i]);
+ for (ZenSubCmdBase* SubCmd : m_SubCommands)
+ {
+ const bool NameMatch = SubCmd->SubOptions().program() == Arg;
+ const bool AliasMatch =
+ !NameMatch && std::find(SubCmd->Aliases().begin(), SubCmd->Aliases().end(), Arg) != SubCmd->Aliases().end();
+ if (NameMatch || AliasMatch)
+ {
+ MatchedSubCmd = SubCmd;
+ MatchedSubOption = &SubCmd->SubOptions();
+ break;
+ }
+ }
+ if (MatchedSubCmd != nullptr)
+ {
+ SubCommandArguments.push_back(argv[0]);
+ std::copy(&argv[i + 1], &argv[argc], std::back_inserter(SubCommandArguments));
+ ParentArgc = i + 1;
+ break;
+ }
+ }
// Intercept --help/-h in the parent arg range before calling ParseOptions so
// we can append subcommand information to the output. When a subcommand was
@@ -336,6 +372,46 @@ ZenCmdWithSubCommands::Run(const ZenCliOptions& GlobalOptions, int argc, char**
if (MatchedSubOption == nullptr)
{
+ // If the user typed what looks like a subcommand name (a non-option arg) but nothing
+ // matched, surface "did you mean" suggestions before falling through to full help.
+ std::string_view UnknownAttempt;
+ for (int i = 1; i < argc; ++i)
+ {
+ std::string_view Arg(argv[i]);
+ if (!Arg.empty() && Arg[0] != '-')
+ {
+ UnknownAttempt = Arg;
+ break;
+ }
+ }
+
+ if (!UnknownAttempt.empty() && !m_SubCommands.empty())
+ {
+ std::vector<std::string_view> SubNames;
+ SubNames.reserve(m_SubCommands.size() * 2);
+ for (ZenSubCmdBase* SubCmd : m_SubCommands)
+ {
+ SubNames.emplace_back(SubCmd->SubOptions().program());
+ for (const std::string& Alias : SubCmd->Aliases())
+ {
+ SubNames.emplace_back(Alias);
+ }
+ }
+ std::vector<std::string_view> Suggestions = SuggestSimilarCommands(UnknownAttempt, SubNames);
+ if (!Suggestions.empty())
+ {
+ printf("Unknown subcommand: '%.*s'\n\n", static_cast<int>(UnknownAttempt.size()), UnknownAttempt.data());
+ printf("The most similar subcommands are:\n");
+ for (std::string_view Name : Suggestions)
+ {
+ printf(" %.*s\n", static_cast<int>(Name.size()), Name.data());
+ }
+ printf("\n");
+ fflush(stdout);
+ throw OptionParseException("Unknown subcommand", {});
+ }
+ }
+
if (!ParseOptions(Options(), ParentArgc, argv))
{
return;
@@ -345,15 +421,6 @@ ZenCmdWithSubCommands::Run(const ZenCliOptions& GlobalOptions, int argc, char**
throw OptionParseException("No subcommand specified", {});
}
- ZenSubCmdBase* MatchedSubCmd = nullptr;
- for (ZenSubCmdBase* SubCmd : m_SubCommands)
- {
- if (&SubCmd->SubOptions() == MatchedSubOption)
- {
- MatchedSubCmd = SubCmd;
- break;
- }
- }
ZEN_ASSERT(MatchedSubCmd != nullptr);
// Intercept --help/-h in the subcommand args so we can show combined help
@@ -369,7 +436,7 @@ ZenCmdWithSubCommands::Run(const ZenCliOptions& GlobalOptions, int argc, char**
}
}
- // Parse subcommand args permissively — unrecognised options are collected
+ // Parse subcommand args permissively - unrecognised options are collected
// and forwarded to the parent parser so that parent options (e.g. --path)
// can appear after the subcommand name on the command line.
std::vector<std::string> SubUnmatched;
@@ -552,6 +619,8 @@ main(int argc, char** argv)
{
zen::InstallCrashHandler();
+ zen::LogInvocation("zen", /*Mode*/ "", argc, argv, {zen::HistoryCommand::Name});
+
#if ZEN_PLATFORM_WINDOWS
setlocale(LC_ALL, "en_us.UTF8");
#endif // ZEN_PLATFORM_WINDOWS
@@ -575,12 +644,12 @@ main(int argc, char** argv)
AttachCommand AttachCmd;
BenchCommand BenchCmd;
BuildsCommand BuildsCmd;
+ CacheCommand CacheCmd;
CacheDetailsCommand CacheDetailsCmd;
CacheGetCommand CacheGetCmd;
CacheGenerateCommand CacheGenerateCmd;
CacheInfoCommand CacheInfoCmd;
CacheStatsCommand CacheStatsCmd;
- CopyCommand CopyCmd;
CopyStateCommand CopyStateCmd;
CreateOplogCommand CreateOplogCmd;
CreateProjectCommand CreateProjectCmd;
@@ -589,7 +658,8 @@ main(int argc, char** argv)
DropCommand DropCmd;
DropProjectCommand ProjectDropCmd;
#if ZEN_WITH_COMPUTE_SERVICES
- ExecCommand ExecCmd;
+ ComputeCommand ComputeCmd;
+ ExecCommand ExecCmd;
#endif // ZEN_WITH_COMPUTE_SERVICES
ExportOplogCommand ExportOplogCmd;
FlushCommand FlushCmd;
@@ -597,6 +667,7 @@ main(int argc, char** argv)
GcStatusCommand GcStatusCmd;
GcStopCommand GcStopCmd;
HelpCommand HelpCmd;
+ HistoryCommand HistoryCmd;
HubCommand HubCmd;
ImportOplogCommand ImportOplogCmd;
InfoCommand InfoCmd;
@@ -614,7 +685,6 @@ main(int argc, char** argv)
RpcReplayCommand RpcReplayCmd;
RpcStartRecordingCommand RpcStartRecordingCmd;
RpcStopRecordingCommand RpcStopRecordingCmd;
- RunCommand RunCmd;
ScrubCommand ScrubCmd;
ServeCommand ServeCmd;
StatusCommand StatusCmd;
@@ -635,23 +705,25 @@ main(int argc, char** argv)
{AttachCommand::Name, &AttachCmd, AttachCommand::Description},
{BenchCommand::Name, &BenchCmd, BenchCommand::Description},
{BuildsCommand::Name, &BuildsCmd, BuildsCommand::Description},
+ {CacheCommand::Name, &CacheCmd, CacheCommand::Description},
{CacheDetailsCommand::Name, &CacheDetailsCmd, CacheDetailsCommand::Description},
{CacheInfoCommand::Name, &CacheInfoCmd, CacheInfoCommand::Description},
{CacheGetCommand::Name, &CacheGetCmd, CacheGetCommand::Description},
{CacheGenerateCommand::Name, &CacheGenerateCmd, CacheGenerateCommand::Description},
{CacheStatsCommand::Name, &CacheStatsCmd, CacheStatsCommand::Description},
- {CopyCommand::Name, &CopyCmd, CopyCommand::Description},
{CopyStateCommand::Name, &CopyStateCmd, CopyStateCommand::Description},
{DedupCommand::Name, &DedupCmd, DedupCommand::Description},
{DownCommand::Name, &DownCmd, DownCommand::Description},
{DropCommand::Name, &DropCmd, DropCommand::Description},
#if ZEN_WITH_COMPUTE_SERVICES
+ {ComputeCommand::Name, &ComputeCmd, ComputeCommand::Description},
{ExecCommand::Name, &ExecCmd, ExecCommand::Description},
#endif
{GcStatusCommand::Name, &GcStatusCmd, GcStatusCommand::Description},
{GcStopCommand::Name, &GcStopCmd, GcStopCommand::Description},
{GcCommand::Name, &GcCmd, GcCommand::Description},
- {HelpCommand::Name, &HelpCmd, HelpCommand::Description},
+ {HelpCommand::Name, &HelpCmd, HelpCommand::Description},
+ {HistoryCommand::Name, &HistoryCmd, HistoryCommand::Description},
{HubCommand::Name, &HubCmd, HubCommand::Description},
{InfoCommand::Name, &InfoCmd, InfoCommand::Description},
{JobCommand::Name, &JobCmd, JobCommand::Description},
@@ -674,7 +746,6 @@ main(int argc, char** argv)
{RpcReplayCommand::Name, &RpcReplayCmd, RpcReplayCommand::Description},
{RpcStartRecordingCommand::Name, &RpcStartRecordingCmd, RpcStartRecordingCommand::Description},
{RpcStopRecordingCommand::Name, &RpcStopRecordingCmd, RpcStopRecordingCommand::Description},
- {RunCommand::Name, &RunCmd, RunCommand::Description},
{ScrubCommand::Name, &ScrubCmd, ScrubCommand::Description},
{ServeCommand::Name, &ServeCmd, ServeCommand::Description},
{StatusCommand::Name, &StatusCmd, StatusCommand::Description},
@@ -795,6 +866,9 @@ main(int argc, char** argv)
Options.add_options()("d, debug", "Enable debugging", cxxopts::value<bool>(GlobalOptions.IsDebug));
Options.add_options()("v, verbose", "Enable verbose logging", cxxopts::value<bool>(GlobalOptions.IsVerbose));
+ Options.add_options()("enable-execution-history",
+ "Record this invocation in the per-user execution history (use --enable-execution-history=false to suppress)",
+ cxxopts::value<bool>(GlobalOptions.EnableExecutionHistory)->default_value("true")->implicit_value("true"));
Options.add_options()("malloc", "Configure memory allocator subsystem", cxxopts::value(MemoryOptions)->default_value("mimalloc"));
Options.add_options()("help", "Show command line help");
Options.add_options()("c, command", "Sub command", cxxopts::value<std::string>(SubCommand));
@@ -875,6 +949,11 @@ main(int argc, char** argv)
for (const CommandInfo& CmdInfo : Commands)
{
+ if (CmdInfo.Cmd->IsHidden())
+ {
+ continue;
+ }
+
ZenCmdCategory& Category = CmdInfo.Cmd->CommandCategory();
Categories[Category.Name] = &Category;
@@ -932,13 +1011,19 @@ main(int argc, char** argv)
{
SB.Append(' ');
}
-
- SB.Append(argv[i]);
+ SB.Append(std::string_view(argv[i]));
}
SentryConfig.DatabasePath = SentryDatabasePath;
- Sentry.Initialize(SentryConfig, SB.ToString());
+ if (std::filesystem::path HistoryPath = GetInvocationHistoryPath(); !HistoryPath.empty())
+ {
+ SentryConfig.AttachmentPaths.push_back(std::move(HistoryPath));
+ }
+
+ std::string ScrubbedCmdLine = SB.ToString();
+ ScrubSensitiveValues(ScrubbedCmdLine);
+ Sentry.Initialize(SentryConfig, ScrubbedCmdLine);
SentryIntegration::ClearCaches();
}
@@ -1036,7 +1121,28 @@ main(int argc, char** argv)
}
}
- printf("Unknown command specified: '%s', exiting\n", SubCommand.c_str());
+ printf("Unknown command specified: '%s'\n", SubCommand.c_str());
+
+ std::vector<std::string_view> VisibleNames;
+ VisibleNames.reserve(std::size(Commands));
+ for (const CommandInfo& CmdInfo : Commands)
+ {
+ if (!CmdInfo.Cmd->IsHidden())
+ {
+ VisibleNames.emplace_back(CmdInfo.CmdName);
+ }
+ }
+
+ std::vector<std::string_view> Suggestions = zen::SuggestSimilarCommands(SubCommand, VisibleNames);
+ if (!Suggestions.empty())
+ {
+ printf("\nThe most similar commands are:\n");
+ for (std::string_view Name : Suggestions)
+ {
+ printf(" %.*s\n", static_cast<int>(Name.size()), Name.data());
+ }
+ }
+ printf("\nRun 'zen --help' for the full list of commands.\n");
return (int)ReturnCode::kBadInput;
}
catch (const OptionParseException& Ex)