#include "../src/cli/cli.h" #include #include #include #include #include "../src/cli/arg_parser.h" #include "../src/cli/help_formatter.h" #include "../src/cli/text_wrap.h" using namespace detz::cli; static Options opts(std::vector a) { std::vector v{const_cast("detz")}; for (auto x : a) v.push_back(const_cast(x)); return Parser().parseOpts(static_cast(v.size()), v.data()); } static bool errHas(const Options& o, const std::string& m) { return o.error.find("tai: error: " + m) != std::string::npos && o.error.rfind("usage: tai", 0) == 0; } static void argparseCases() { assert(opts({"--vers"}).version); assert(errHas(opts({"--ins"}), "ambiguous option: --ins could match --install, --install-telegram")); assert(errHas(opts({"--profile"}), "argument --profile: expected one argument")); assert(errHas(opts({"--yes=1"}), "argument --yes: ignored explicit argument '1'")); assert(errHas(opts({"--cap-max-turns", "abc"}), "argument --cap-max-turns: invalid int value: 'abc'")); assert(errHas(opts({"--debug", "zzz"}), "argument --debug: invalid choice: 'zzz' (choose from file, stdout, stderr)")); assert(errHas(opts({"--bogus"}), "unrecognized arguments: --bogus")); assert(opts({"-h", "--bogus"}).help && opts({"-h", "--bogus"}).error.empty()); assert(errHas(opts({"--cap-permission-mode", "zzz", "-h"}), "argument --cap-permission-mode: invalid choice")); assert(opts({"--debug"}).debug.value() == "file"); assert(opts({"--debug=stderr"}).debug.value() == "stderr"); assert(opts({"--profile=x", "hello", "world"}).prompt == (std::vector{"hello", "world"})); assert(opts({"--cap", "--yes", "x"}).cap.value() == (std::vector{"--yes", "x"})); assert(!opts({"--cap", "--yes"}).yes); assert(opts({"--mplex"}).mplex.has_value() && opts({"--mplex"}).mplex->empty()); assert(opts({"--", "--yes"}).prompt == (std::vector{"--yes"})); assert(opts({"--cap-max-turns", "-1"}).cap_max_turns.value() == -1); assert(opts({"--mcp-port", " 1_0 "}).mcp_port.value() == 10); assert(errHas(opts({"--model", "x"}), "unrecognized arguments: --model x") || errHas(opts({"--model", "x"}), "unrecognized arguments: --model")); long long n = 0; assert(ArgParser::pyInt("\xd9\xa3", n) && n == 3); assert(!ArgParser::pyInt("3x", n) && !ArgParser::pyInt("_3", n) && !ArgParser::pyInt("", n)); assert(ArgParser::pyRepr("it's") == "\"it's\"" && ArgParser::pyRepr("a'\"") == "'a\\'\"'"); } static void helpCases() { // Usage, help and parser share one table: every option string of the table is in --help. setenv("COLUMNS", "80", 1); const std::string help = Parser::helpText(); for (const auto& f : FlagTable::all()) for (const auto& n : f.names) assert(help.find(n) != std::string::npos); assert(help.rfind("usage: tai [-h] [--profile PROFILE]", 0) == 0); // width 80: the wrapped help column starts at 24 assert(help.find(" --debug [SINK] extensive debug logging to /debug.log (file),\n") != std::string::npos); setenv("COLUMNS", "40", 1); assert(HelpFormatter(HelpFormatter::terminalColumns()).formatUsage().find("\n [--yes] [--yolo] [--auto]\n") != std::string::npos); // textwrap: breaks after hyphens inside words, breaks over-long words assert(TextWrap::wrap("per-record context", 10) == (std::vector{"per-record", "context"})); assert(TextWrap::wrap("abcdefghij", 4) == (std::vector{"abcd", "efgh", "ij"})); unsetenv("COLUMNS"); } int main() { argparseCases(); helpCases(); detz::cli::Parser p; // golden --help contains every flag std::string h = detz::cli::Parser::helpText(); for (auto f : {"--profile","--yes","--yolo","--auto","--continue","--debug","--debug-format","--env","--no-opencode","--update-models","--eval-retrieval","--stats-rag","--version","--cap-manifest","--cap-speak","--cap-cwd","--cap-model","--cap-permission-mode","--cap-max-turns","--cap ","--serve-mcp","--mcp-port","--mcp-bind","--telegram","--scheduler","--install","--install-telegram","--uninstall-telegram","--plexer-wrap","--mplex","--demo","--demo-shot","--tui","--tui-local","--theme"}) assert(h.find(f) != std::string::npos); // parse smoke const char* av[] = {"detz","--yes","--debug","--cap","resolve","foo"}; auto c = p.parse(6, (char**)av); assert(c.opts.yes && c.opts.debug.value()=="file" && c.opts.cap && c.opts.cap->size()==2); const char* bv[] = {"detz","--version"}; assert(p.parse(2,(char**)bv).run()==0); const char* cv[] = {"detz"}; assert(p.parse(1,(char**)cv).name=="--help"); return 0; }