diff --git a/implementations/Crafter.Debug.Cli-Client.cpp b/implementations/Crafter.Debug.Cli-Client.cpp index 6e03563..677af1c 100644 --- a/implementations/Crafter.Debug.Cli-Client.cpp +++ b/implementations/Crafter.Debug.Cli-Client.cpp @@ -878,13 +878,19 @@ namespace { for (std::size_t i = 0; i < args.size(); ++i) { const std::string_view arg = args[i]; if (!options.command.empty()) { - // Global flags may follow the command too, before its own. - if (arg == "--json") { + // Global flags go anywhere, as agents write them: `wait + // --paused --timeout 60s`. No command has options of these + // names, but for `call --json '{...}'`, its arguments as JSON. + const bool callArguments = options.command == "call" && arg == "--json" && i + 1 < args.size() && args[i + 1].starts_with('{'); + if (arg == "--json" && !callArguments) { options.json = true; continue; } - options.rest.emplace_back(arg); - continue; + if (arg != "--timeout" && arg != "-i" && arg != "--instance") { + options.rest.emplace_back(arg); + if (callArguments) options.rest.emplace_back(args[++i]); + continue; + } } if ((arg == "-i" || arg == "--instance") && i + 1 < args.size()) { options.instance = args[++i]; @@ -897,6 +903,9 @@ namespace { return exitUsage; } options.timeout = *timeout; + } else if (arg == "--timeout" || arg == "-i" || arg == "--instance") { + *env.err << std::format("crafter-debug: {} needs a value\n", arg); + return exitUsage; } else if (arg == "-h" || arg == "--help") { *env.out << usage; return exitOk; diff --git a/tests/ShouldRunClientCommands/main.cpp b/tests/ShouldRunClientCommands/main.cpp index 4694e7c..28451e3 100644 --- a/tests/ShouldRunClientCommands/main.cpp +++ b/tests/ShouldRunClientCommands/main.cpp @@ -161,6 +161,10 @@ int main() { Check(r.code == 0 && Has(r.out, "'score >= 1' held at frame"), "wait for a condition"); r = Run({ "--timeout", "100ms", "wait", "score < 0" }, instances); Check(r.code == 5 && Has(r.err, "was not true within"), "a wait that times out exits 5 and says what the paths were"); + r = Run({ "wait", "score < 0", "--timeout", "100ms" }, instances); + Check(r.code == 5 && Has(r.err, "was not true within"), std::format("global flags also go after the command's own ({})", r.err)); + r = Run({ "wait", "--frames", "1", "--timeout" }, instances); + Check(r.code == 2 && Has(r.err, "--timeout needs a value"), "a global flag without its value is a usage error"); r = Run({ "log", "--last", "5" }, instances); Check(r.code == 0 && Has(r.out, "AGENT"), "log shows the agent's actions"); @@ -178,6 +182,12 @@ int main() { } catch (const ParseError&) { } Check(r.code == 0 && json, "--json prints JSON"); + r = Run({ "status", "--json" }, instances); + Check(r.code == 0 && r.out.starts_with("{"), "--json after the command too"); + r = Run({ "call", "Player_SetAim", "--json", R"({"slot": 4, "yaw": 5})" }, instances); + Check(r.code == 0 && Has(r.out, R"({"slot":4,"yaw":5.0})"), std::format("call takes its arguments as JSON ({})", r.out + r.err)); + r = Run({ "call", "Player_SetAim", "--json", R"({"slot": 4})", "yaw=7", "--json" }, instances); + Check(r.code == 0 && Has(r.out, R"({"slot":4,"yaw":7.0})"), std::format("JSON arguments with key=value on top, and --json after them ({})", r.out + r.err)); r = Run({ "stauts" }, instances); Check(r.code == 2 && Has(r.err, "did you mean 'status'"), "a mistyped command name gets a suggestion");