diff options
Diffstat (limited to 'src/zen/zen.cpp')
| -rw-r--r-- | src/zen/zen.cpp | 182 |
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) |