refactor(cli): migrate runtime and simulator parsers to CLI11
Replace hand-rolled argument parsing with CLI11-backed parse paths for streamer runtime config and simulator runtime options. Simulator help text is now generated from CLI11 app definitions to keep parser/help output in sync, while preserving legacy validation messages and exit semantics used by automation.
This commit is contained in:
+71
@@ -0,0 +1,71 @@
|
|||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Linker files
|
||||||
|
*.ilk
|
||||||
|
|
||||||
|
# Debugger Files
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
*.so.*
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# Build directories
|
||||||
|
build/
|
||||||
|
Build/
|
||||||
|
build-*/
|
||||||
|
|
||||||
|
# CMake generated files
|
||||||
|
CMakeFiles/
|
||||||
|
CMakeCache.txt
|
||||||
|
cmake_install.cmake
|
||||||
|
Makefile
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# vcpkg
|
||||||
|
vcpkg_installed/
|
||||||
|
|
||||||
|
# debug information files
|
||||||
|
*.dwo
|
||||||
|
|
||||||
|
# test output & cache
|
||||||
|
Testing/
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# local evidence artifacts generated by standalone scripts
|
||||||
|
.sisyphus/
|
||||||
@@ -8,6 +8,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
|
if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/lib/CLI11/CMakeLists.txt")
|
||||||
|
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/lib/CLI11" "${CMAKE_CURRENT_BINARY_DIR}/vendor/cli11")
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(cppzmq QUIET)
|
find_package(cppzmq QUIET)
|
||||||
find_package(ZeroMQ QUIET)
|
find_package(ZeroMQ QUIET)
|
||||||
find_package(spdlog REQUIRED)
|
find_package(spdlog REQUIRED)
|
||||||
@@ -76,6 +80,10 @@ elseif (TARGET spdlog)
|
|||||||
list(APPEND CVMAP_STREAMER_LINK_DEPS spdlog)
|
list(APPEND CVMAP_STREAMER_LINK_DEPS spdlog)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (TARGET CLI11::CLI11)
|
||||||
|
list(APPEND CVMAP_STREAMER_LINK_DEPS CLI11::CLI11)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(cvmmap_streamer_common PUBLIC ${CVMAP_STREAMER_LINK_DEPS})
|
target_link_libraries(cvmmap_streamer_common PUBLIC ${CVMAP_STREAMER_LINK_DEPS})
|
||||||
|
|
||||||
function(add_cvmmap_binary target source)
|
function(add_cvmmap_binary target source)
|
||||||
|
|||||||
@@ -4,33 +4,50 @@
|
|||||||
#include <expected>
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include "cvmmap_streamer/ipc/contracts.hpp"
|
#include "cvmmap_streamer/ipc/contracts.hpp"
|
||||||
|
|
||||||
namespace cvmmap_streamer::sim {
|
namespace cvmmap_streamer::sim {
|
||||||
|
|
||||||
struct RuntimeConfig {
|
struct RuntimeConfig {
|
||||||
std::uint32_t frames{360};
|
std::uint32_t frames{360};
|
||||||
std::uint32_t fps{60};
|
std::uint32_t fps{60};
|
||||||
std::uint16_t width{64};
|
std::uint16_t width{64};
|
||||||
std::uint16_t height{48};
|
std::uint16_t height{48};
|
||||||
std::optional<std::uint32_t> emit_reset_at{};
|
std::optional<std::uint32_t> emit_reset_at{};
|
||||||
std::optional<std::uint32_t> emit_reset_every{};
|
std::optional<std::uint32_t> emit_reset_every{};
|
||||||
std::optional<std::uint32_t> switch_format_at{};
|
std::optional<std::uint32_t> switch_format_at{};
|
||||||
std::optional<std::uint16_t> switch_width{};
|
std::optional<std::uint16_t> switch_width{};
|
||||||
std::optional<std::uint16_t> switch_height{};
|
std::optional<std::uint16_t> switch_height{};
|
||||||
std::string label{"sim"};
|
std::string label{"sim"};
|
||||||
std::string shm_name{"cvmmap_sim"};
|
std::string shm_name{"cvmmap_sim"};
|
||||||
std::string zmq_endpoint{"ipc:///tmp/cvmmap_sim"};
|
std::string zmq_endpoint{"ipc:///tmp/cvmmap_sim"};
|
||||||
std::uint8_t channels{3};
|
std::uint8_t channels{3};
|
||||||
ipc::Depth depth{ipc::Depth::U8};
|
ipc::Depth depth{ipc::Depth::U8};
|
||||||
ipc::PixelFormat pixel_format{ipc::PixelFormat::BGR};
|
ipc::PixelFormat pixel_format{ipc::PixelFormat::BGR};
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::uint32_t payload_size_bytes() const;
|
std::uint32_t payload_size_bytes() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **argv);
|
enum class ParseStatus {
|
||||||
void print_help();
|
Ok,
|
||||||
|
Help,
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
}
|
struct ParseResult {
|
||||||
|
ParseStatus status{ParseStatus::Ok};
|
||||||
|
RuntimeConfig config{};
|
||||||
|
std::string message{};
|
||||||
|
int exit_code{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc,
|
||||||
|
char **argv);
|
||||||
|
void print_help();
|
||||||
|
ParseResult parse_runtime_config_with_cli11(
|
||||||
|
int argc, char **argv, std::string_view executable_name = "cvmmap_sim");
|
||||||
|
|
||||||
|
} // namespace cvmmap_streamer::sim
|
||||||
|
|||||||
+488
-453
File diff suppressed because it is too large
Load Diff
+370
-250
@@ -1,8 +1,13 @@
|
|||||||
#include "cvmmap_streamer/sim/options.hpp"
|
#include "cvmmap_streamer/sim/options.hpp"
|
||||||
|
|
||||||
|
#include <CLI/CLI.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
@@ -11,281 +16,396 @@ namespace cvmmap_streamer::sim {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::expected<std::string_view, std::string> next_value(int argc, char **argv, int &index, std::string_view flag_name) {
|
std::string trim_copy(std::string value) {
|
||||||
if (index + 1 >= argc) {
|
auto not_space = [](unsigned char c) {
|
||||||
return std::unexpected("missing value for " + std::string(flag_name));
|
return c != ' ' && c != '\t' && c != '\n' && c != '\r';
|
||||||
}
|
};
|
||||||
++index;
|
while (!value.empty() &&
|
||||||
return std::string_view{argv[index]};
|
!not_space(static_cast<unsigned char>(value.front()))) {
|
||||||
}
|
value.erase(value.begin());
|
||||||
|
}
|
||||||
std::expected<std::uint32_t, std::string> parse_u32(std::string_view raw, std::string_view flag_name) {
|
while (!value.empty() &&
|
||||||
std::uint32_t value{0};
|
!not_space(static_cast<unsigned char>(value.back()))) {
|
||||||
const auto result = std::from_chars(raw.data(), raw.data() + raw.size(), value, 10);
|
value.pop_back();
|
||||||
if (result.ec != std::errc{} || result.ptr != raw.data() + raw.size()) {
|
}
|
||||||
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
return value;
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::expected<std::uint16_t, std::string> parse_u16(std::string_view raw, std::string_view flag_name) {
|
|
||||||
std::uint16_t value{0};
|
|
||||||
const auto result = std::from_chars(raw.data(), raw.data() + raw.size(), value, 10);
|
|
||||||
if (result.ec != std::errc{} || result.ptr != raw.data() + raw.size()) {
|
|
||||||
return std::unexpected("invalid value for " + std::string(flag_name) + ": '" + std::string(raw) + "'");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string normalize_cli_error(std::string raw_message) {
|
||||||
|
if (raw_message.find("The following argument was not expected:") !=
|
||||||
|
std::string::npos) {
|
||||||
|
const auto pos = raw_message.find(':');
|
||||||
|
if (pos != std::string::npos && pos + 1 < raw_message.size()) {
|
||||||
|
return "unknown argument: '" + trim_copy(raw_message.substr(pos + 1)) +
|
||||||
|
"'";
|
||||||
|
}
|
||||||
|
return "unknown argument";
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::array<std::string_view, 12> kFlags{"--frames",
|
||||||
|
"--fps",
|
||||||
|
"--width",
|
||||||
|
"--height",
|
||||||
|
"--emit-reset-at",
|
||||||
|
"--emit-reset-every",
|
||||||
|
"--switch-format-at",
|
||||||
|
"--switch-width",
|
||||||
|
"--switch-height",
|
||||||
|
"--label",
|
||||||
|
"--shm-name",
|
||||||
|
"--zmq-endpoint"};
|
||||||
|
|
||||||
|
if (raw_message.find("requires at least") != std::string::npos ||
|
||||||
|
raw_message.find("requires ") != std::string::npos) {
|
||||||
|
for (const auto flag : kFlags) {
|
||||||
|
if (raw_message.find(flag) != std::string::npos) {
|
||||||
|
return "missing value for " + std::string(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim_copy(std::move(raw_message));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::uint32_t, std::string>
|
||||||
|
parse_u32(std::string_view raw, std::string_view flag_name) {
|
||||||
|
unsigned long parsed{0};
|
||||||
|
const auto *begin = raw.data();
|
||||||
|
const auto *end = raw.data() + raw.size();
|
||||||
|
const auto result = std::from_chars(begin, end, parsed, 10);
|
||||||
|
if (result.ec != std::errc{} || result.ptr != end) {
|
||||||
|
return std::unexpected("invalid value for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
|
}
|
||||||
|
if (parsed > std::numeric_limits<std::uint32_t>::max()) {
|
||||||
|
return std::unexpected("value out of range for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
|
}
|
||||||
|
return static_cast<std::uint32_t>(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::uint16_t, std::string>
|
||||||
|
parse_u16(std::string_view raw, std::string_view flag_name) {
|
||||||
|
unsigned int parsed{0};
|
||||||
|
const auto *begin = raw.data();
|
||||||
|
const auto *end = raw.data() + raw.size();
|
||||||
|
const auto result = std::from_chars(begin, end, parsed, 10);
|
||||||
|
if (result.ec != std::errc{} || result.ptr != end) {
|
||||||
|
return std::unexpected("invalid value for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
|
}
|
||||||
|
if (parsed > std::numeric_limits<std::uint16_t>::max()) {
|
||||||
|
return std::unexpected("value out of range for " + std::string(flag_name) +
|
||||||
|
": '" + std::string(raw) + "'");
|
||||||
|
}
|
||||||
|
return static_cast<std::uint16_t>(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string>
|
||||||
|
validate_runtime_config(const RuntimeConfig &config) {
|
||||||
|
if (config.frames == 0) {
|
||||||
|
return std::unexpected("--frames must be > 0");
|
||||||
|
}
|
||||||
|
if (config.width == 0 || config.height == 0) {
|
||||||
|
return std::unexpected("--width and --height must be > 0");
|
||||||
|
}
|
||||||
|
if (config.label.empty()) {
|
||||||
|
return std::unexpected("--label must not be empty");
|
||||||
|
}
|
||||||
|
if (config.label.size() > ipc::kLabelLenMax) {
|
||||||
|
return std::unexpected("--label exceeds 24 bytes");
|
||||||
|
}
|
||||||
|
if (config.shm_name.empty()) {
|
||||||
|
return std::unexpected("--shm-name must not be empty");
|
||||||
|
}
|
||||||
|
if (config.zmq_endpoint.empty()) {
|
||||||
|
return std::unexpected("--zmq-endpoint must not be empty");
|
||||||
|
}
|
||||||
|
if (config.emit_reset_at && *config.emit_reset_at == 0) {
|
||||||
|
return std::unexpected("--emit-reset-at must be in [1, --frames]");
|
||||||
|
}
|
||||||
|
if (config.emit_reset_at && *config.emit_reset_at > config.frames) {
|
||||||
|
return std::unexpected("--emit-reset-at must be in [1, --frames]");
|
||||||
|
}
|
||||||
|
if (config.emit_reset_every && *config.emit_reset_every == 0) {
|
||||||
|
return std::unexpected("--emit-reset-every must be > 0");
|
||||||
|
}
|
||||||
|
if (config.switch_format_at && *config.switch_format_at == 0) {
|
||||||
|
return std::unexpected("--switch-format-at must be in [1, --frames]");
|
||||||
|
}
|
||||||
|
if (config.switch_format_at && *config.switch_format_at > config.frames) {
|
||||||
|
return std::unexpected("--switch-format-at must be in [1, --frames]");
|
||||||
|
}
|
||||||
|
if (config.switch_width && *config.switch_width == 0) {
|
||||||
|
return std::unexpected("--switch-width must be > 0");
|
||||||
|
}
|
||||||
|
if (config.switch_height && *config.switch_height == 0) {
|
||||||
|
return std::unexpected("--switch-height must be > 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto payload_size = static_cast<std::uint64_t>(config.width) *
|
||||||
|
static_cast<std::uint64_t>(config.height) *
|
||||||
|
static_cast<std::uint64_t>(config.channels);
|
||||||
|
if (payload_size >
|
||||||
|
static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
||||||
|
return std::unexpected(
|
||||||
|
"computed payload size exceeds cv-mmap frame_info.buffer_size range");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto switched_width = config.switch_width.value_or(config.width);
|
||||||
|
const auto switched_height = config.switch_height.value_or(config.height);
|
||||||
|
const auto switched_payload_size =
|
||||||
|
static_cast<std::uint64_t>(switched_width) *
|
||||||
|
static_cast<std::uint64_t>(switched_height) *
|
||||||
|
static_cast<std::uint64_t>(config.channels);
|
||||||
|
if (switched_payload_size >
|
||||||
|
static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
||||||
|
return std::unexpected("computed switched payload size exceeds cv-mmap "
|
||||||
|
"frame_info.buffer_size range");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string build_help_text(std::string_view executable_name = "cvmmap_sim") {
|
||||||
|
RuntimeConfig defaults{};
|
||||||
|
std::string frames_raw;
|
||||||
|
std::string fps_raw;
|
||||||
|
std::string width_raw;
|
||||||
|
std::string height_raw;
|
||||||
|
std::string emit_reset_at_raw;
|
||||||
|
std::string emit_reset_every_raw;
|
||||||
|
std::string switch_format_at_raw;
|
||||||
|
std::string switch_width_raw;
|
||||||
|
std::string switch_height_raw;
|
||||||
|
std::string label_raw;
|
||||||
|
std::string shm_name_raw;
|
||||||
|
std::string zmq_endpoint_raw;
|
||||||
|
|
||||||
|
CLI::App app{"cv-mmap deterministic frame simulator"};
|
||||||
|
app.name(std::string(executable_name));
|
||||||
|
app.allow_extras(false);
|
||||||
|
app.set_help_flag("--help,-h", "show this message");
|
||||||
|
|
||||||
|
app.add_option("--frames", frames_raw, "total frames to emit (default: 360)");
|
||||||
|
app.add_option("--fps", fps_raw,
|
||||||
|
"deterministic frame pacing, 0 disables sleep (default: 60)");
|
||||||
|
app.add_option("--width", width_raw, "frame width (default: 64)");
|
||||||
|
app.add_option("--height", height_raw, "frame height (default: 48)");
|
||||||
|
app.add_option("--emit-reset-at", emit_reset_at_raw,
|
||||||
|
"emit MODULE_STATUS_STREAM_RESET at frame_count n");
|
||||||
|
app.add_option("--emit-reset-every", emit_reset_every_raw,
|
||||||
|
"emit MODULE_STATUS_STREAM_RESET each n frames");
|
||||||
|
app.add_option("--switch-format-at", switch_format_at_raw,
|
||||||
|
"switch metadata frame format at frame_count n");
|
||||||
|
app.add_option("--switch-width", switch_width_raw,
|
||||||
|
"width after format switch (default: --width)");
|
||||||
|
app.add_option("--switch-height", switch_height_raw,
|
||||||
|
"height after format switch (default: --height)");
|
||||||
|
app.add_option("--label", label_raw, "stream label (<=24 bytes)")
|
||||||
|
->default_val(defaults.label);
|
||||||
|
app.add_option("--shm-name", shm_name_raw, "POSIX shm object name")
|
||||||
|
->default_val(defaults.shm_name);
|
||||||
|
app.add_option("--zmq-endpoint", zmq_endpoint_raw, "ZMQ pub endpoint")
|
||||||
|
->default_val(defaults.zmq_endpoint);
|
||||||
|
|
||||||
|
return app.help();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
std::uint32_t RuntimeConfig::payload_size_bytes() const {
|
std::uint32_t RuntimeConfig::payload_size_bytes() const {
|
||||||
const auto width64 = static_cast<std::uint64_t>(width);
|
const auto width64 = static_cast<std::uint64_t>(width);
|
||||||
const auto height64 = static_cast<std::uint64_t>(height);
|
const auto height64 = static_cast<std::uint64_t>(height);
|
||||||
const auto channels64 = static_cast<std::uint64_t>(channels);
|
const auto channels64 = static_cast<std::uint64_t>(channels);
|
||||||
return static_cast<std::uint32_t>(width64 * height64 * channels64);
|
return static_cast<std::uint32_t>(width64 * height64 * channels64);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_help() {
|
void print_help() {
|
||||||
constexpr std::array<std::string_view, 17> kHelpLines{
|
std::istringstream in(build_help_text());
|
||||||
"cvmmap_sim",
|
for (std::string line; std::getline(in, line);) {
|
||||||
"Usage:",
|
spdlog::info("{}", line);
|
||||||
" cvmmap_sim [options]",
|
}
|
||||||
"",
|
|
||||||
"Required simulation controls:",
|
|
||||||
" --frames <n> total frames to emit (default: 360)",
|
|
||||||
" --fps <n> deterministic frame pacing, 0 disables sleep (default: 60)",
|
|
||||||
" --width <px> frame width (default: 64)",
|
|
||||||
" --height <px> frame height (default: 48)",
|
|
||||||
" --emit-reset-at <n> emit MODULE_STATUS_STREAM_RESET at frame_count n",
|
|
||||||
" --emit-reset-every <n> emit MODULE_STATUS_STREAM_RESET each n frames",
|
|
||||||
" --switch-format-at <n> switch metadata frame format at frame_count n",
|
|
||||||
" --switch-width <px> width after format switch (default: --width)",
|
|
||||||
" --switch-height <px> height after format switch (default: --height)",
|
|
||||||
"",
|
|
||||||
"Optional:",
|
|
||||||
" --label <name> --shm-name <name> --zmq-endpoint <endpoint>"};
|
|
||||||
|
|
||||||
for (const auto &line : kHelpLines) {
|
|
||||||
spdlog::info("{}", line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc, char **argv) {
|
ParseResult parse_runtime_config_with_cli11(int argc, char **argv,
|
||||||
RuntimeConfig config{};
|
std::string_view executable_name) {
|
||||||
|
RuntimeConfig config{};
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
std::string frames_raw;
|
||||||
const std::string_view arg{argv[i]};
|
std::string fps_raw;
|
||||||
|
std::string width_raw;
|
||||||
|
std::string height_raw;
|
||||||
|
std::string emit_reset_at_raw;
|
||||||
|
std::string emit_reset_every_raw;
|
||||||
|
std::string switch_format_at_raw;
|
||||||
|
std::string switch_width_raw;
|
||||||
|
std::string switch_height_raw;
|
||||||
|
std::string label_raw;
|
||||||
|
std::string shm_name_raw;
|
||||||
|
std::string zmq_endpoint_raw;
|
||||||
|
|
||||||
if (arg == "--frames") {
|
CLI::App app{"cv-mmap deterministic frame simulator"};
|
||||||
auto value = next_value(argc, argv, i, arg);
|
app.name(std::string(executable_name));
|
||||||
if (!value) {
|
app.allow_extras(false);
|
||||||
return std::unexpected(value.error());
|
app.set_help_flag("--help,-h", "show this message");
|
||||||
}
|
|
||||||
auto parsed = parse_u32(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.frames = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--fps") {
|
app.add_option("--frames", frames_raw, "total frames to emit (default: 360)");
|
||||||
auto value = next_value(argc, argv, i, arg);
|
app.add_option("--fps", fps_raw,
|
||||||
if (!value) {
|
"deterministic frame pacing, 0 disables sleep (default: 60)");
|
||||||
return std::unexpected(value.error());
|
app.add_option("--width", width_raw, "frame width (default: 64)");
|
||||||
}
|
app.add_option("--height", height_raw, "frame height (default: 48)");
|
||||||
auto parsed = parse_u32(*value, arg);
|
app.add_option("--emit-reset-at", emit_reset_at_raw,
|
||||||
if (!parsed) {
|
"emit MODULE_STATUS_STREAM_RESET at frame_count n");
|
||||||
return std::unexpected(parsed.error());
|
app.add_option("--emit-reset-every", emit_reset_every_raw,
|
||||||
}
|
"emit MODULE_STATUS_STREAM_RESET each n frames");
|
||||||
config.fps = *parsed;
|
app.add_option("--switch-format-at", switch_format_at_raw,
|
||||||
continue;
|
"switch metadata frame format at frame_count n");
|
||||||
}
|
app.add_option("--switch-width", switch_width_raw,
|
||||||
|
"width after format switch (default: --width)");
|
||||||
|
app.add_option("--switch-height", switch_height_raw,
|
||||||
|
"height after format switch (default: --height)");
|
||||||
|
app.add_option("--label", label_raw, "stream label (<=24 bytes)");
|
||||||
|
app.add_option("--shm-name", shm_name_raw, "POSIX shm object name");
|
||||||
|
app.add_option("--zmq-endpoint", zmq_endpoint_raw, "ZMQ pub endpoint");
|
||||||
|
|
||||||
if (arg == "--width") {
|
if (argc <= 1) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
return ParseResult{
|
||||||
if (!value) {
|
.status = ParseStatus::Help, .message = app.help(), .exit_code = 0};
|
||||||
return std::unexpected(value.error());
|
}
|
||||||
}
|
|
||||||
auto parsed = parse_u16(*value, arg);
|
|
||||||
if (!parsed) {
|
|
||||||
return std::unexpected(parsed.error());
|
|
||||||
}
|
|
||||||
config.width = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--height") {
|
try {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
app.parse(argc, argv);
|
||||||
if (!value) {
|
} catch (const CLI::CallForHelp &) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{
|
||||||
}
|
.status = ParseStatus::Help, .message = app.help(), .exit_code = 0};
|
||||||
auto parsed = parse_u16(*value, arg);
|
} catch (const CLI::ParseError &e) {
|
||||||
if (!parsed) {
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
return std::unexpected(parsed.error());
|
.message = normalize_cli_error(e.what()),
|
||||||
}
|
.exit_code = 2};
|
||||||
config.height = *parsed;
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--emit-reset-at") {
|
if (!frames_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(frames_raw, "--frames");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
auto parsed = parse_u32(*value, arg);
|
.exit_code = 2};
|
||||||
if (!parsed) {
|
}
|
||||||
return std::unexpected(parsed.error());
|
config.frames = *parsed;
|
||||||
}
|
}
|
||||||
config.emit_reset_at = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--emit-reset-every") {
|
if (!fps_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(fps_raw, "--fps");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
auto parsed = parse_u32(*value, arg);
|
.exit_code = 2};
|
||||||
if (!parsed) {
|
}
|
||||||
return std::unexpected(parsed.error());
|
config.fps = *parsed;
|
||||||
}
|
}
|
||||||
config.emit_reset_every = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--switch-format-at") {
|
if (!width_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u16(width_raw, "--width");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
auto parsed = parse_u32(*value, arg);
|
.exit_code = 2};
|
||||||
if (!parsed) {
|
}
|
||||||
return std::unexpected(parsed.error());
|
config.width = *parsed;
|
||||||
}
|
}
|
||||||
config.switch_format_at = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--switch-width") {
|
if (!height_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u16(height_raw, "--height");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
auto parsed = parse_u16(*value, arg);
|
.exit_code = 2};
|
||||||
if (!parsed) {
|
}
|
||||||
return std::unexpected(parsed.error());
|
config.height = *parsed;
|
||||||
}
|
}
|
||||||
config.switch_width = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--switch-height") {
|
if (!emit_reset_at_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(emit_reset_at_raw, "--emit-reset-at");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
auto parsed = parse_u16(*value, arg);
|
.exit_code = 2};
|
||||||
if (!parsed) {
|
}
|
||||||
return std::unexpected(parsed.error());
|
config.emit_reset_at = *parsed;
|
||||||
}
|
}
|
||||||
config.switch_height = *parsed;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg == "--label") {
|
if (!emit_reset_every_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(emit_reset_every_raw, "--emit-reset-every");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
config.label = std::string(*value);
|
.exit_code = 2};
|
||||||
continue;
|
}
|
||||||
}
|
config.emit_reset_every = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg == "--shm-name") {
|
if (!switch_format_at_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u32(switch_format_at_raw, "--switch-format-at");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
config.shm_name = std::string(*value);
|
.exit_code = 2};
|
||||||
continue;
|
}
|
||||||
}
|
config.switch_format_at = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg == "--zmq-endpoint") {
|
if (!switch_width_raw.empty()) {
|
||||||
auto value = next_value(argc, argv, i, arg);
|
auto parsed = parse_u16(switch_width_raw, "--switch-width");
|
||||||
if (!value) {
|
if (!parsed) {
|
||||||
return std::unexpected(value.error());
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
}
|
.message = parsed.error(),
|
||||||
config.zmq_endpoint = std::string(*value);
|
.exit_code = 2};
|
||||||
continue;
|
}
|
||||||
}
|
config.switch_width = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg == "--help" || arg == "-h") {
|
if (!switch_height_raw.empty()) {
|
||||||
continue;
|
auto parsed = parse_u16(switch_height_raw, "--switch-height");
|
||||||
}
|
if (!parsed) {
|
||||||
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
|
.message = parsed.error(),
|
||||||
|
.exit_code = 2};
|
||||||
|
}
|
||||||
|
config.switch_height = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
return std::unexpected("unknown argument: '" + std::string(arg) + "'");
|
if (!label_raw.empty()) {
|
||||||
}
|
config.label = label_raw;
|
||||||
|
}
|
||||||
|
if (!shm_name_raw.empty()) {
|
||||||
|
config.shm_name = shm_name_raw;
|
||||||
|
}
|
||||||
|
if (!zmq_endpoint_raw.empty()) {
|
||||||
|
config.zmq_endpoint = zmq_endpoint_raw;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.frames == 0) {
|
auto validation = validate_runtime_config(config);
|
||||||
return std::unexpected("--frames must be > 0");
|
if (!validation) {
|
||||||
}
|
return ParseResult{.status = ParseStatus::Error,
|
||||||
if (config.width == 0 || config.height == 0) {
|
.message = validation.error(),
|
||||||
return std::unexpected("--width and --height must be > 0");
|
.exit_code = 2};
|
||||||
}
|
}
|
||||||
if (config.label.empty()) {
|
|
||||||
return std::unexpected("--label must not be empty");
|
|
||||||
}
|
|
||||||
if (config.label.size() > ipc::kLabelLenMax) {
|
|
||||||
return std::unexpected("--label exceeds 24 bytes");
|
|
||||||
}
|
|
||||||
if (config.shm_name.empty()) {
|
|
||||||
return std::unexpected("--shm-name must not be empty");
|
|
||||||
}
|
|
||||||
if (config.zmq_endpoint.empty()) {
|
|
||||||
return std::unexpected("--zmq-endpoint must not be empty");
|
|
||||||
}
|
|
||||||
if (config.emit_reset_at && *config.emit_reset_at == 0) {
|
|
||||||
return std::unexpected("--emit-reset-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.emit_reset_at && *config.emit_reset_at > config.frames) {
|
|
||||||
return std::unexpected("--emit-reset-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.emit_reset_every && *config.emit_reset_every == 0) {
|
|
||||||
return std::unexpected("--emit-reset-every must be > 0");
|
|
||||||
}
|
|
||||||
if (config.switch_format_at && *config.switch_format_at == 0) {
|
|
||||||
return std::unexpected("--switch-format-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.switch_format_at && *config.switch_format_at > config.frames) {
|
|
||||||
return std::unexpected("--switch-format-at must be in [1, --frames]");
|
|
||||||
}
|
|
||||||
if (config.switch_width && *config.switch_width == 0) {
|
|
||||||
return std::unexpected("--switch-width must be > 0");
|
|
||||||
}
|
|
||||||
if (config.switch_height && *config.switch_height == 0) {
|
|
||||||
return std::unexpected("--switch-height must be > 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto payload_size = static_cast<std::uint64_t>(config.width) *
|
return ParseResult{
|
||||||
static_cast<std::uint64_t>(config.height) *
|
.status = ParseStatus::Ok, .config = config, .exit_code = 0};
|
||||||
static_cast<std::uint64_t>(config.channels);
|
|
||||||
if (payload_size > static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
|
||||||
return std::unexpected("computed payload size exceeds cv-mmap frame_info.buffer_size range");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto switched_width = config.switch_width.value_or(config.width);
|
|
||||||
const auto switched_height = config.switch_height.value_or(config.height);
|
|
||||||
const auto switched_payload_size =
|
|
||||||
static_cast<std::uint64_t>(switched_width) *
|
|
||||||
static_cast<std::uint64_t>(switched_height) *
|
|
||||||
static_cast<std::uint64_t>(config.channels);
|
|
||||||
if (switched_payload_size > static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max())) {
|
|
||||||
return std::unexpected("computed switched payload size exceeds cv-mmap frame_info.buffer_size range");
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::expected<RuntimeConfig, std::string> parse_runtime_config(int argc,
|
||||||
|
char **argv) {
|
||||||
|
auto parsed = parse_runtime_config_with_cli11(argc, argv);
|
||||||
|
if (parsed.status == ParseStatus::Ok) {
|
||||||
|
return parsed.config;
|
||||||
|
}
|
||||||
|
if (!parsed.message.empty()) {
|
||||||
|
return std::unexpected(parsed.message);
|
||||||
|
}
|
||||||
|
return std::unexpected("failed to parse runtime options");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace cvmmap_streamer::sim
|
||||||
|
|||||||
Reference in New Issue
Block a user