From debd71ebec37e789473ce16bdb76bf3ab79c3472 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 19 Oct 2024 12:32:43 +0800 Subject: [PATCH] replay: replace QCommandLineParser with getopt for command-line argument parsing. (#33812) * use getopt * fix -h * show help if no args are provided --- tools/replay/main.cc | 182 ++++++++++++++++++++++++++++++------------- 1 file changed, 126 insertions(+), 56 deletions(-) diff --git a/tools/replay/main.cc b/tools/replay/main.cc index a0c072438d..0bf7cc3423 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -1,10 +1,123 @@ +#include + #include -#include +#include +#include +#include #include "common/prefix.h" #include "tools/replay/consoleui.h" #include "tools/replay/replay.h" +const std::string helpText = +R"(Usage: replay [options] +Options: + -a, --allow Whitelist of services to send + -b, --block Blacklist of services to send + -c, --cache Cache segments in memory. Default is 5 + -s, --start Start from + -x, --playback Playback + --demo Use a demo route instead of providing your own + -d, --data_dir Local directory with routes + -p, --prefix Set OPENPILOT_PREFIX + --dcam Load driver camera + --ecam Load wide road camera + --no-loop Stop at the end of the route + --no-cache Turn off local cache + --qcam Load qcamera + --no-hw-decoder Disable HW video decoding + --no-vipc Do not output video + --all Output all messages including uiDebug, userFlag + -h, --help Show this help message +)"; + +struct ReplayConfig { + QString route; + QStringList allow; + QStringList block; + QString data_dir; + std::string prefix; + uint32_t flags = REPLAY_FLAG_NONE; + int start_seconds = 0; + int cache_segments = -1; + float playback_speed = -1; +}; + +bool parseArgs(int argc, char *argv[], ReplayConfig &config) { + const struct option cli_options[] = { + {"allow", required_argument, nullptr, 'a'}, + {"block", required_argument, nullptr, 'b'}, + {"cache", required_argument, nullptr, 'c'}, + {"start", required_argument, nullptr, 's'}, + {"playback", required_argument, nullptr, 'x'}, + {"demo", no_argument, nullptr, 0}, + {"data_dir", required_argument, nullptr, 'd'}, + {"prefix", required_argument, nullptr, 'p'}, + {"dcam", no_argument, nullptr, 0}, + {"ecam", no_argument, nullptr, 0}, + {"no-loop", no_argument, nullptr, 0}, + {"no-cache", no_argument, nullptr, 0}, + {"qcam", no_argument, nullptr, 0}, + {"no-hw-decoder", no_argument, nullptr, 0}, + {"no-vipc", no_argument, nullptr, 0}, + {"all", no_argument, nullptr, 0}, + {"help", no_argument, nullptr, 'h'}, + {nullptr, 0, nullptr, 0}, // Terminating entry + }; + + const std::map flag_map = { + {"dcam", REPLAY_FLAG_DCAM}, + {"ecam", REPLAY_FLAG_ECAM}, + {"no-loop", REPLAY_FLAG_NO_LOOP}, + {"no-cache", REPLAY_FLAG_NO_FILE_CACHE}, + {"qcam", REPLAY_FLAG_QCAMERA}, + {"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER}, + {"no-vipc", REPLAY_FLAG_NO_VIPC}, + {"all", REPLAY_FLAG_ALL_SERVICES}, + }; + + if (argc == 1) { + std::cout << helpText; + return false; + } + + int opt, option_index = 0; + while ((opt = getopt_long(argc, argv, "a:b:c:s:x:d:p:h", cli_options, &option_index)) != -1) { + switch (opt) { + case 'a': config.allow = QString(optarg).split(","); break; + case 'b': config.block = QString(optarg).split(","); break; + case 'c': config.cache_segments = std::atoi(optarg); break; + case 's': config.start_seconds = std::atoi(optarg); break; + case 'x': config.playback_speed = std::atof(optarg); break; + case 'd': config.data_dir = optarg; break; + case 'p': config.prefix = optarg; break; + case 0: { + std::string name = cli_options[option_index].name; + if (name == "demo") { + config.route = DEMO_ROUTE; + } else { + config.flags |= flag_map.at(name); + } + break; + } + case 'h': std::cout << helpText; return false; + default: return false; + } + } + + // Check for a route name (first positional argument) + if (config.route.isEmpty() && optind < argc) { + config.route = argv[optind]; + } + + if (config.route.isEmpty()) { + std::cerr << "No route provided. Use --help for usage information.\n"; + return false; + } + + return true; +} + int main(int argc, char *argv[]) { #ifdef __APPLE__ // With all sockets opened, we might hit the default limit of 256 on macOS @@ -12,72 +125,29 @@ int main(int argc, char *argv[]) { #endif QCoreApplication app(argc, argv); + ReplayConfig config; - const std::tuple flags[] = { - {"dcam", REPLAY_FLAG_DCAM, "load driver camera"}, - {"ecam", REPLAY_FLAG_ECAM, "load wide road camera"}, - {"no-loop", REPLAY_FLAG_NO_LOOP, "stop at the end of the route"}, - {"no-cache", REPLAY_FLAG_NO_FILE_CACHE, "turn off local cache"}, - {"qcam", REPLAY_FLAG_QCAMERA, "load qcamera"}, - {"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER, "disable HW video decoding"}, - {"no-vipc", REPLAY_FLAG_NO_VIPC, "do not output video"}, - {"all", REPLAY_FLAG_ALL_SERVICES, "do output all messages including uiDebug, userFlag" - ". this may causes issues when used along with UI"} - }; - - QCommandLineParser parser; - parser.setApplicationDescription("Mock openpilot components by publishing logged messages."); - parser.addHelpOption(); - parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); - parser.addOption({{"a", "allow"}, "whitelist of services to send", "allow"}); - parser.addOption({{"b", "block"}, "blacklist of services to send", "block"}); - parser.addOption({{"c", "cache"}, "cache segments in memory. default is 5", "n"}); - parser.addOption({{"s", "start"}, "start from ", "seconds"}); - parser.addOption({"x", QString("playback . between %1 - %2") - .arg(ConsoleUI::speed_array.front()).arg(ConsoleUI::speed_array.back()), "speed"}); - parser.addOption({"demo", "use a demo route instead of providing your own"}); - parser.addOption({"data_dir", "local directory with routes", "data_dir"}); - parser.addOption({"prefix", "set OPENPILOT_PREFIX", "prefix"}); - for (auto &[name, _, desc] : flags) { - parser.addOption({name, desc}); - } - - parser.process(app); - const QStringList args = parser.positionalArguments(); - if (args.empty() && !parser.isSet("demo")) { - parser.showHelp(); - } - - const QString route = args.empty() ? DEMO_ROUTE : args.first(); - QStringList allow = parser.value("allow").isEmpty() ? QStringList{} : parser.value("allow").split(","); - QStringList block = parser.value("block").isEmpty() ? QStringList{} : parser.value("block").split(","); - - uint32_t replay_flags = REPLAY_FLAG_NONE; - for (const auto &[name, flag, _] : flags) { - if (parser.isSet(name)) { - replay_flags |= flag; - } + if (!parseArgs(argc, argv, config)) { + return 1; } std::unique_ptr op_prefix; - auto prefix = parser.value("prefix"); - if (!prefix.isEmpty()) { - op_prefix.reset(new OpenpilotPrefix(prefix.toStdString())); + if (!config.prefix.empty()) { + op_prefix = std::make_unique(config.prefix); } - Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app); - if (!parser.value("c").isEmpty()) { - replay->setSegmentCacheLimit(parser.value("c").toInt()); + Replay *replay = new Replay(config.route, config.allow, config.block, nullptr, config.flags, config.data_dir, &app); + if (config.cache_segments > 0) { + replay->setSegmentCacheLimit(config.cache_segments); } - if (!parser.value("x").isEmpty()) { - replay->setSpeed(std::clamp(parser.value("x").toFloat(), - ConsoleUI::speed_array.front(), ConsoleUI::speed_array.back())); + if (config.playback_speed > 0) { + replay->setSpeed(std::clamp(config.playback_speed, ConsoleUI::speed_array.front(), ConsoleUI::speed_array.back())); } if (!replay->load()) { - return 0; + return 1; } ConsoleUI console_ui(replay); - replay->start(parser.value("start").toInt()); + replay->start(config.start_seconds); return app.exec(); }