Common System 0.2.0
Common interfaces and patterns for system integration
Loading...
Searching...
No Matches
cli_config_parser.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, πŸ€β˜€πŸŒ•πŸŒ₯ 🌊
3// See the LICENSE file in the project root for full license information.
4
28#pragma once
29
30#include "config_loader.h"
31#include "unified_config.h"
32
34
35#include <algorithm>
36#include <iostream>
37#include <sstream>
38#include <string>
39#include <utility>
40#include <vector>
41
43
47namespace cli_error_codes {
48 constexpr int invalid_argument = 2001;
49 constexpr int missing_value = 2002;
50 constexpr int invalid_key = 2003;
51 constexpr int invalid_format = 2004;
52}
53
60 std::string config_path;
61
63 std::vector<std::pair<std::string, std::string>> overrides;
64
66 bool show_help = false;
67
69 bool show_version = false;
70
72 std::vector<std::string> positional_args;
73};
74
96public:
106 static Result<parsed_args> parse(int argc, char** argv) {
107 parsed_args result;
108
109 for (int i = 1; i < argc; ++i) {
110 std::string arg = argv[i];
111
112 if (arg == "--help" || arg == "-h") {
113 result.show_help = true;
114 } else if (arg == "--version" || arg == "-v") {
115 result.show_version = true;
116 } else if (starts_with(arg, "--config=")) {
117 result.config_path = arg.substr(9);
118 } else if (arg == "--config") {
119 if (i + 1 >= argc) {
122 "Missing value for --config",
123 "cli_config_parser"
124 );
125 }
126 result.config_path = argv[++i];
127 } else if (starts_with(arg, "--set=")) {
128 auto kv_result = parse_key_value(arg.substr(6));
129 if (kv_result.is_err()) {
130 return make_error<parsed_args>(kv_result.error());
131 }
132 result.overrides.push_back(kv_result.value());
133 } else if (arg == "--set") {
134 if (i + 1 >= argc) {
137 "Missing value for --set",
138 "cli_config_parser"
139 );
140 }
141 auto kv_result = parse_key_value(argv[++i]);
142 if (kv_result.is_err()) {
143 return make_error<parsed_args>(kv_result.error());
144 }
145 result.overrides.push_back(kv_result.value());
146 } else if (starts_with(arg, "--")) {
149 "Unknown argument: " + arg,
150 "cli_config_parser"
151 );
152 } else if (starts_with(arg, "-")) {
155 "Unknown short argument: " + arg,
156 "cli_config_parser"
157 );
158 } else {
159 // Positional argument
160 result.positional_args.push_back(arg);
161 }
162 }
163
164 return Result<parsed_args>::ok(std::move(result));
165 }
166
183 static Result<unified_config> load_with_cli_overrides(int argc, char** argv) {
184 // Parse CLI arguments
185 auto parse_result = parse(argc, argv);
186 if (parse_result.is_err()) {
187 return make_error<unified_config>(parse_result.error());
188 }
189
190 auto args = parse_result.value();
191
192 // Handle --help and --version early
193 if (args.show_help || args.show_version) {
194 // Return an "error" to indicate the caller should handle this
197 args.show_help ? "help_requested" : "version_requested",
198 "cli_config_parser"
199 );
200 }
201
202 // Load configuration
203 unified_config config;
204
205 if (!args.config_path.empty()) {
206 // Load from file
207 auto load_result = config_loader::load(args.config_path);
208 if (load_result.is_err()) {
209 return make_error<unified_config>(load_result.error());
210 }
211 config = load_result.value();
212 } else {
213 // Load from environment only
214 auto load_result = config_loader::load_from_env();
215 if (load_result.is_err()) {
216 return make_error<unified_config>(load_result.error());
217 }
218 config = load_result.value();
219 }
220
221 // Apply CLI overrides
222 for (const auto& [key, value] : args.overrides) {
223 auto result = apply_override(config, key, value);
224 if (result.is_err()) {
225 return make_error<unified_config>(result.error());
226 }
227 }
228
229 // Validate final configuration
230 auto validation_result = config_loader::validate(config);
231 if (validation_result.is_err()) {
232 return make_error<unified_config>(validation_result.error());
233 }
234
235 return Result<unified_config>::ok(std::move(config));
236 }
237
243 static void print_help(const std::string& program_name = "program") {
244 std::cout << "Usage: " << program_name << " [OPTIONS]\n\n"
245 << "Options:\n"
246 << " --config=<path> Load configuration from YAML file\n"
247 << " --set <key>=<value> Override a configuration value\n"
248 << " --help, -h Show this help message\n"
249 << " --version, -v Show version information\n\n"
250 << "Configuration keys:\n";
251
252 // Print available configuration keys
253 auto metadata = get_config_metadata();
254 for (const auto& field : metadata) {
255 std::cout << " " << field.path;
256 if (!field.allowed_values.empty()) {
257 std::cout << " (";
258 for (size_t i = 0; i < field.allowed_values.size(); ++i) {
259 if (i > 0) std::cout << "|";
260 std::cout << field.allowed_values[i];
261 }
262 std::cout << ")";
263 }
264 std::cout << "\n " << field.description;
265 if (!field.env_var.empty()) {
266 std::cout << " [env: " << field.env_var << "]";
267 }
268 std::cout << "\n";
269 }
270
271 std::cout << "\nExamples:\n"
272 << " " << program_name << " --config=config.yaml\n"
273 << " " << program_name << " --set logger.level=debug\n"
274 << " " << program_name << " --config=config.yaml --set thread.pool_size=16\n";
275 }
276
282 static void print_version(const std::string& version = "0.1.0.0") {
283 std::cout << "unified_system version " << version << "\n";
284 }
285
286private:
290 static bool starts_with(const std::string& str, const std::string& prefix) {
291 return str.size() >= prefix.size() &&
292 str.compare(0, prefix.size(), prefix) == 0;
293 }
294
299 auto pos = str.find('=');
300 if (pos == std::string::npos) {
303 "Invalid key=value format: " + str,
304 "cli_config_parser"
305 );
306 }
307
308 std::string key = str.substr(0, pos);
309 std::string value = str.substr(pos + 1);
310
311 // Trim whitespace from key
312 key.erase(0, key.find_first_not_of(" \t"));
313 key.erase(key.find_last_not_of(" \t") + 1);
314
315 if (key.empty()) {
318 "Empty key in key=value pair",
319 "cli_config_parser"
320 );
321 }
322
324 std::make_pair(std::move(key), std::move(value)));
325 }
326
336 const std::string& key,
337 const std::string& value) {
338 // Thread configuration
339 if (key == "thread.pool_size") {
340 config.thread.pool_size = parse_size_t(value);
341 } else if (key == "thread.queue_type") {
342 config.thread.queue_type = value;
343 } else if (key == "thread.max_queue_size") {
344 config.thread.max_queue_size = parse_size_t(value);
345 } else if (key == "thread.thread_name_prefix") {
346 config.thread.thread_name_prefix = value;
347 }
348 // Logger configuration
349 else if (key == "logger.level") {
350 config.logger.level = value;
351 } else if (key == "logger.async") {
352 config.logger.async = parse_bool(value);
353 } else if (key == "logger.buffer_size") {
354 config.logger.buffer_size = parse_size_t(value);
355 } else if (key == "logger.file_path") {
356 config.logger.file_path = value;
357 } else if (key == "logger.max_file_size") {
358 config.logger.max_file_size = parse_size_t(value);
359 } else if (key == "logger.max_backup_files") {
360 config.logger.max_backup_files = parse_size_t(value);
361 } else if (key == "logger.format_pattern") {
362 config.logger.format_pattern = value;
363 }
364 // Monitoring configuration
365 else if (key == "monitoring.enabled") {
366 config.monitoring.enabled = parse_bool(value);
367 } else if (key == "monitoring.metrics_interval" ||
368 key == "monitoring.metrics_interval_ms") {
369 config.monitoring.metrics_interval = std::chrono::milliseconds{parse_int64(value)};
370 } else if (key == "monitoring.health_check_interval" ||
371 key == "monitoring.health_check_interval_ms") {
372 config.monitoring.health_check_interval = std::chrono::milliseconds{parse_int64(value)};
373 } else if (key == "monitoring.prometheus_port") {
374 config.monitoring.prometheus_port = static_cast<uint16_t>(parse_size_t(value));
375 } else if (key == "monitoring.prometheus_path") {
376 config.monitoring.prometheus_path = value;
377 }
378 // Tracing configuration
379 else if (key == "monitoring.tracing.enabled") {
380 config.monitoring.tracing.enabled = parse_bool(value);
381 } else if (key == "monitoring.tracing.sampling_rate") {
383 } else if (key == "monitoring.tracing.exporter") {
384 config.monitoring.tracing.exporter = value;
385 } else if (key == "monitoring.tracing.endpoint") {
386 config.monitoring.tracing.endpoint = value;
387 }
388 // Database configuration
389 else if (key == "database.backend") {
390 config.database.backend = value;
391 } else if (key == "database.connection_string") {
392 config.database.connection_string = value;
393 } else if (key == "database.log_queries") {
394 config.database.log_queries = parse_bool(value);
395 } else if (key == "database.slow_query_threshold" ||
396 key == "database.slow_query_threshold_ms") {
397 config.database.slow_query_threshold = std::chrono::milliseconds{parse_int64(value)};
398 } else if (key == "database.pool.min_size") {
399 config.database.pool.min_size = parse_size_t(value);
400 } else if (key == "database.pool.max_size") {
401 config.database.pool.max_size = parse_size_t(value);
402 } else if (key == "database.pool.idle_timeout" ||
403 key == "database.pool.idle_timeout_ms") {
404 config.database.pool.idle_timeout = std::chrono::milliseconds{parse_int64(value)};
405 } else if (key == "database.pool.acquire_timeout" ||
406 key == "database.pool.acquire_timeout_ms") {
407 config.database.pool.acquire_timeout = std::chrono::milliseconds{parse_int64(value)};
408 }
409 // Network configuration
410 else if (key == "network.compression") {
411 config.network.compression = value;
412 } else if (key == "network.buffer_size") {
413 config.network.buffer_size = parse_size_t(value);
414 } else if (key == "network.connect_timeout" ||
415 key == "network.connect_timeout_ms") {
416 config.network.connect_timeout = std::chrono::milliseconds{parse_int64(value)};
417 } else if (key == "network.io_timeout" ||
418 key == "network.io_timeout_ms") {
419 config.network.io_timeout = std::chrono::milliseconds{parse_int64(value)};
420 } else if (key == "network.keepalive_interval" ||
421 key == "network.keepalive_interval_ms") {
422 config.network.keepalive_interval = std::chrono::milliseconds{parse_int64(value)};
423 } else if (key == "network.max_connections") {
424 config.network.max_connections = parse_size_t(value);
425 }
426 // TLS configuration
427 else if (key == "network.tls.enabled") {
428 config.network.tls.enabled = parse_bool(value);
429 } else if (key == "network.tls.version") {
430 config.network.tls.version = value;
431 } else if (key == "network.tls.cert_path") {
432 config.network.tls.cert_path = value;
433 } else if (key == "network.tls.key_path") {
434 config.network.tls.key_path = value;
435 } else if (key == "network.tls.ca_path") {
436 config.network.tls.ca_path = value;
437 } else if (key == "network.tls.verify_peer") {
438 config.network.tls.verify_peer = parse_bool(value);
439 }
440 // Unknown key
441 else {
444 "Unknown configuration key: " + key,
445 "cli_config_parser"
446 );
447 }
448
449 return VoidResult::ok({});
450 }
451
452 // Value parsing helpers
453
454 static size_t parse_size_t(const std::string& value) {
455 try {
456 return std::stoull(value);
457 } catch (...) {
458 return 0;
459 }
460 }
461
462 static int64_t parse_int64(const std::string& value) {
463 try {
464 return std::stoll(value);
465 } catch (...) {
466 return 0;
467 }
468 }
469
470 static double parse_double(const std::string& value) {
471 try {
472 return std::stod(value);
473 } catch (...) {
474 return 0.0;
475 }
476 }
477
478 static bool parse_bool(const std::string& value) {
479 std::string lower = value;
480 std::transform(lower.begin(), lower.end(), lower.begin(),
481 [](unsigned char c) { return std::tolower(c); });
482 return lower == "true" || lower == "1" || lower == "yes" || lower == "on";
483 }
484};
485
486} // namespace kcenon::common::config
Result type for error handling with member function support.
Definition core.cppm:165
static Result< T > ok(U &&value)
Create a successful result with value (static factory)
Definition core.h:223
Parses command-line arguments for configuration.
static bool starts_with(const std::string &str, const std::string &prefix)
Check if a string starts with a prefix.
static Result< parsed_args > parse(int argc, char **argv)
Parse command-line arguments.
static Result< unified_config > load_with_cli_overrides(int argc, char **argv)
Load configuration with CLI overrides.
static double parse_double(const std::string &value)
static void print_version(const std::string &version="0.1.0.0")
Print version information to stdout.
static size_t parse_size_t(const std::string &value)
static void print_help(const std::string &program_name="program")
Print help message to stdout.
static Result< std::pair< std::string, std::string > > parse_key_value(const std::string &str)
Parse a key=value string.
static VoidResult apply_override(unified_config &config, const std::string &key, const std::string &value)
Apply a configuration override.
static bool parse_bool(const std::string &value)
static int64_t parse_int64(const std::string &value)
static VoidResult validate(const unified_config &config)
Validate a configuration.
static Result< unified_config > load_from_env()
Load configuration from environment variables only.
static Result< unified_config > load(const std::string &path)
Load configuration from a YAML file.
YAML-based configuration loader for the unified system.
std::vector< field_metadata > get_config_metadata()
Get metadata for all configuration fields.
Result< T > make_error(int code, const std::string &message, const std::string &module="")
Create an error result with code and message.
Definition utilities.h:91
VoidResult ok()
Create a successful void result.
Definition utilities.h:71
Umbrella header for Result<T> type and related utilities.
bool log_queries
Enable query logging.
std::string connection_string
Connection string or URI.
std::string backend
Database backend: "postgresql", "mysql", "sqlite", "mongodb", "redis".
pool_config pool
Connection pool configuration.
std::chrono::milliseconds slow_query_threshold
Slow query threshold.
bool async
Enable async logging.
size_t max_file_size
Maximum file size in bytes (for rotating_file)
std::string file_path
Log file path (for file writers)
size_t max_backup_files
Maximum number of backup files (for rotating_file)
size_t buffer_size
Async buffer size in bytes.
std::string format_pattern
Log format pattern.
std::string level
Log level: "trace", "debug", "info", "warn", "error", "critical", "off".
std::chrono::milliseconds metrics_interval
Metrics collection interval.
uint16_t prometheus_port
Prometheus metrics port (0 to disable)
std::chrono::milliseconds health_check_interval
Health check interval.
std::string prometheus_path
Prometheus metrics path.
tracing_config tracing
Tracing configuration.
std::chrono::milliseconds connect_timeout
Connection timeout.
size_t max_connections
Maximum concurrent connections (server)
std::chrono::milliseconds io_timeout
Read/write timeout.
size_t buffer_size
Send/receive buffer size.
tls_config tls
TLS configuration.
std::string compression
Compression type: "none", "lz4", "gzip", "deflate", "zstd".
std::chrono::milliseconds keepalive_interval
Keep-alive interval.
Parsed command-line arguments.
std::vector< std::string > positional_args
Unparsed positional arguments.
std::string config_path
Configuration file path (from –config)
std::vector< std::pair< std::string, std::string > > overrides
Configuration overrides (from –set key=value)
size_t max_size
Maximum pool size.
size_t min_size
Minimum pool size.
std::chrono::milliseconds acquire_timeout
Connection acquisition timeout.
std::chrono::milliseconds idle_timeout
Idle connection timeout.
std::string queue_type
Queue type: "mutex", "lockfree", "bounded".
size_t pool_size
Number of worker threads (default: hardware concurrency)
size_t max_queue_size
Maximum queue size (for bounded queue)
std::string thread_name_prefix
Thread naming prefix.
std::string version
TLS version: "1.2", "1.3".
std::string key_path
Private key file path.
bool verify_peer
Verify peer certificate.
std::string cert_path
Certificate file path.
std::string ca_path
CA certificate path (for client verification)
std::string endpoint
Exporter endpoint.
std::string exporter
Exporter type: "otlp", "jaeger", "zipkin", "console".
double sampling_rate
Sampling rate (0.0 to 1.0)
Root configuration structure for the unified system.
network_config network
Network system configuration.
logger_config logger
Logger system configuration.
thread_config thread
Thread system configuration.
monitoring_config monitoring
Monitoring system configuration.
database_config database
Database system configuration.
Unified configuration schema for the entire system.