mirror of https://github.com/jarro2783/cxxopts.git
test
This commit is contained in:
parent
929bf2de37
commit
9396c26989
115
CMakeLists.txt
115
CMakeLists.txt
|
|
@ -1,83 +1,60 @@
|
|||
# Copyright (c) 2014 Jarryd Beck
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
cmake_minimum_required(VERSION 3.5...3.19)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(cxxoptspp VERSION 3.3.1 LANGUAGES CXX)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
|
||||
include(cxxopts)
|
||||
# C++ standard
|
||||
seti(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Get the version of the library
|
||||
cxxopts_getversion(VERSION)
|
||||
# Include directory
|
||||
include_directories(include)
|
||||
|
||||
project(cxxopts
|
||||
VERSION "${VERSION}"
|
||||
LANGUAGES CXX
|
||||
# Find nlohmann/json
|
||||
find_package(nlohmann_json 3.2.0 REQUIRED)
|
||||
|
||||
# Define library sources
|
||||
set(CXXOPTSPP_SOURCES
|
||||
src/cxxoptspp.cpp
|
||||
src/dependency_engine.cpp
|
||||
src/type_system.cpp
|
||||
src/reversible_parser.cpp
|
||||
)
|
||||
|
||||
set("PROJECT_DESCRIPTION" "A header-only lightweight C++ command line option parser")
|
||||
set("PROJECT_HOMEPAGE_URL" "https://github.com/jarro2783/cxxopts")
|
||||
# Create static library
|
||||
add_library(cxxoptspp STATIC ${CXXOPTSPP_SOURCES})
|
||||
|
||||
# Must include after the project call due to GNUInstallDirs requiring a language be enabled (IE. CXX)
|
||||
include(GNUInstallDirs)
|
||||
# Create shared library
|
||||
add_library(cxxoptspp_shared SHARED ${CXXOPTSPP_SOURCES})
|
||||
set_target_properties(cxxoptspp_shared PROPERTIES OUTPUT_NAME cxxoptspp)
|
||||
|
||||
# Determine whether this is a standalone project or included by other projects
|
||||
set(CXXOPTS_STANDALONE_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(CXXOPTS_STANDALONE_PROJECT ON)
|
||||
endif()
|
||||
# Link nlohmann/json
|
||||
target_link_libraries(cxxoptspp PRIVATE nlohmann_json::nlohmann_json)
|
||||
target_link_libraries(cxxoptspp_shared PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
# Establish the project options
|
||||
option(CXXOPTS_BUILD_EXAMPLES "Set to ON to build examples" ${CXXOPTS_STANDALONE_PROJECT})
|
||||
option(CXXOPTS_BUILD_TESTS "Set to ON to build tests" ${CXXOPTS_STANDALONE_PROJECT})
|
||||
option(CXXOPTS_ENABLE_INSTALL "Generate the install target" ${CXXOPTS_STANDALONE_PROJECT})
|
||||
option(CXXOPTS_ENABLE_WARNINGS "Add warnings to CMAKE_CXX_FLAGS" ${CXXOPTS_STANDALONE_PROJECT})
|
||||
option(CXXOPTS_USE_UNICODE_HELP "Use ICU Unicode library" OFF)
|
||||
# Include cxxopts.hpp from original library
|
||||
file(GLOB CXXOPTS_HEADER "include/cxxopts/*.hpp")
|
||||
|
||||
if (CXXOPTS_STANDALONE_PROJECT)
|
||||
cxxopts_set_cxx_standard()
|
||||
endif()
|
||||
# Installation
|
||||
install(TARGETS cxxoptspp cxxoptspp_shared
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
if (CXXOPTS_ENABLE_WARNINGS)
|
||||
cxxopts_enable_warnings()
|
||||
endif()
|
||||
install(FILES include/cxxoptspp.hpp DESTINATION include)
|
||||
install(FILES ${CXXOPTS_HEADER} DESTINATION include/cxxopts)
|
||||
|
||||
add_library(cxxopts INTERFACE)
|
||||
add_library(cxxopts::cxxopts ALIAS cxxopts)
|
||||
add_subdirectory(include)
|
||||
# Example application
|
||||
add_executable(example_enhanced src/example_enhanced.cpp)
|
||||
target_link_libraries(example_enhanced PRIVATE cxxoptspp nlohmann_json::nlohmann_json)
|
||||
|
||||
# Link against the ICU library when requested
|
||||
if(CXXOPTS_USE_UNICODE_HELP)
|
||||
cxxopts_use_unicode()
|
||||
endif()
|
||||
# Tests
|
||||
add_subdirectory(test)
|
||||
|
||||
# Install cxxopts when requested by the user
|
||||
if (CXXOPTS_ENABLE_INSTALL)
|
||||
cxxopts_install_logic()
|
||||
endif()
|
||||
# Export library
|
||||
export(TARGETS cxxoptspp cxxoptspp_shared FILE cxxoptsppTargets.cmake)
|
||||
configure_file(cxxoptsppConfig.cmake.in cxxoptsppConfig.cmake @ONLY)
|
||||
|
||||
# Build examples when requested by the user
|
||||
if (CXXOPTS_BUILD_EXAMPLES)
|
||||
add_subdirectory(src)
|
||||
endif()
|
||||
|
||||
# Enable testing when requested by the user
|
||||
if (CXXOPTS_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cxxoptsppConfig.cmake"
|
||||
DESTINATION lib/cmake/cxxoptspp)
|
||||
install(EXPORT cxxoptsppTargets DESTINATION lib/cmake/cxxoptspp)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(cxxoptspp VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
# C++ standard requirements
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Include directories
|
||||
include_directories(include)
|
||||
|
||||
# Find JSON library (nlohmann/json)
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
|
||||
# Build cxxoptspp library
|
||||
add_library(cxxoptspp SHARED
|
||||
src/cxxoptspp.cpp
|
||||
)
|
||||
|
||||
# Link dependencies
|
||||
target_link_libraries(cxxoptspp PUBLIC
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
|
||||
# Install library
|
||||
install(TARGETS cxxoptspp
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
# Install header files
|
||||
install(DIRECTORY include/
|
||||
DESTINATION include
|
||||
FILES_MATCHING PATTERN "*.hpp"
|
||||
)
|
||||
|
||||
# Build original cxxopts (for backward compatibility)
|
||||
add_library(cxxopts INTERFACE)
|
||||
target_include_directories(cxxopts INTERFACE include)
|
||||
|
||||
# Build examples
|
||||
add_executable(example src/example.cpp)
|
||||
target_link_libraries(example cxxopts)
|
||||
|
||||
add_executable(example_enhanced src/example_enhanced.cpp)
|
||||
target_link_libraries(example_enhanced cxxoptspp nlohmann_json::nlohmann_json)
|
||||
|
||||
# Build tests
|
||||
enable_testing()
|
||||
add_executable(tests test/main.cpp test/options.cpp)
|
||||
target_link_libraries(tests cxxopts)
|
||||
add_test(NAME unit_tests COMMAND tests)
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
# cxxopts++ Enhanced Design Document
|
||||
|
||||
## Overview
|
||||
This document describes the enhanced design for cxxopts++ that adds support for:
|
||||
1. Multi-level subcommands with dynamic option dependencies
|
||||
2. Type system for automatic validation and conversion
|
||||
3. Reversible parsing and state serialization
|
||||
|
||||
## 1. Multi-level Subcommands with Dynamic Option Dependencies
|
||||
|
||||
### Core Design
|
||||
- **Command Hierarchy**: Each command can have child subcommands, forming a tree structure
|
||||
- **Context Isolation**: Each command level has its own option set and parsing context
|
||||
- **Option Inheritance**: Child commands inherit options from parent commands by default
|
||||
- **Dependency Engine**: Evaluates boolean expressions to validate option dependencies
|
||||
|
||||
### Key Components
|
||||
|
||||
#### `Command` Class
|
||||
- Represents a single command or subcommand
|
||||
- Contains:
|
||||
- Command name and description
|
||||
- Parent command reference
|
||||
- Child commands map
|
||||
- Option set for this command level
|
||||
- Dependency rules
|
||||
- Help generation logic
|
||||
|
||||
#### `CommandParser` Class
|
||||
- Recursive parser that traverses the command tree
|
||||
- Maintains parsing context (current command, accumulated options)
|
||||
- Handles option inheritance
|
||||
- Validates dependencies after parsing
|
||||
|
||||
#### Dependency Expression Language
|
||||
- Supports boolean operators: `&&`, `||`, `!`
|
||||
- Parentheses for grouping: `( --a || --b ) && !--c`
|
||||
- Operands are option names (with or without dashes)
|
||||
- Evaluated using a recursive descent parser
|
||||
|
||||
### Parsing Flow
|
||||
1. Start at root command
|
||||
2. For each argument:
|
||||
a. If it's a subcommand, switch context and continue parsing
|
||||
b. If it's an option, parse it in current context
|
||||
3. After all arguments parsed:
|
||||
a. Merge inherited options
|
||||
b. Evaluate all dependency rules
|
||||
c. Generate errors for violated rules
|
||||
|
||||
## 2. Type System for Automatic Validation & Conversion
|
||||
|
||||
### Core Design
|
||||
- **Type Erasure**: Uses `std::any` or custom type erasure to store values
|
||||
- **Converter Concept**: Classes that implement `from_string()` and `to_string()` methods
|
||||
- **Validator Concept**: Classes that implement `validate()` method
|
||||
- **Composition**: Support for nested types (containers of custom types)
|
||||
|
||||
### Key Components
|
||||
|
||||
#### `Type` Base Class
|
||||
- Abstract base for all custom types
|
||||
- Methods:
|
||||
- `virtual std::any from_string(const std::string&) const = 0;
|
||||
- `virtual std::string to_string(const std::any&) const = 0;
|
||||
- `virtual bool validate(const std::string&) const = 0;
|
||||
|
||||
#### `BasicType<T>` Template
|
||||
- Implements `Type` for fundamental types
|
||||
- Uses `std::istringstream`/`std::ostringstream` for conversion
|
||||
|
||||
#### `ContainerType<T>` Template
|
||||
- Implements `Type` for container types
|
||||
- Supports custom delimiters
|
||||
- Validates each element individually
|
||||
|
||||
#### Example Custom Type
|
||||
```cpp
|
||||
class IPAddress : public Type {
|
||||
public:
|
||||
std::any from_string(const std::string& s) const override;
|
||||
std::string to_string(const std::any& value) const override;
|
||||
bool validate(const std::string& s) const override;
|
||||
};
|
||||
```
|
||||
|
||||
### Conversion Flow
|
||||
1. String input -> split (for containers)
|
||||
2. Validate each segment
|
||||
3. Convert to target type
|
||||
4. Store in type-erased container
|
||||
|
||||
## 3. Reversible Parsing & State Serialization
|
||||
|
||||
### Core Design
|
||||
- **Parse Tree**: Records the exact sequence of parsed elements
|
||||
- **Metadata Storage**: Stores original input strings and parsing context
|
||||
- **Serialization Format**: JSON with structured metadata
|
||||
- **Reconstruction**: Rebuilds command line from JSON representation
|
||||
|
||||
### Key Components
|
||||
|
||||
#### `ParseNode` Class
|
||||
- Represents a single parsed element (option, value, subcommand)
|
||||
- Contains:
|
||||
- Element type (option, value, subcommand)
|
||||
- Original string representation
|
||||
- Position in command line
|
||||
- Associated metadata
|
||||
|
||||
#### `ParseTree` Class
|
||||
- Root node representing the full command line
|
||||
- Tree structure matching command hierarchy
|
||||
- Methods for serialization/deserialization
|
||||
|
||||
#### `Serializer` Class
|
||||
- Converts `ParseTree` to JSON
|
||||
- Handles all type conversions
|
||||
- Preserves original input strings
|
||||
|
||||
#### `Deserializer` Class
|
||||
- Converts JSON back to `ParseTree`
|
||||
- Reconstructs command line arguments
|
||||
|
||||
### Serialization Format Example
|
||||
```json
|
||||
{
|
||||
"command": "tool",
|
||||
"subcommands": [
|
||||
{
|
||||
"command": "module",
|
||||
"subcommands": [
|
||||
{
|
||||
"command": "subcmd",
|
||||
"options": [
|
||||
{
|
||||
"name": "--opt",
|
||||
"value": "123",
|
||||
"original": "--opt=123",
|
||||
"type": "int"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. **Base Command Structure**: Implement `Command` and `CommandParser` classes
|
||||
2. **Dependency Engine**: Implement expression parser and evaluator
|
||||
3. **Type System**: Implement type erasure and custom type support
|
||||
4. **Container Types**: Add support for vectors, maps, etc.
|
||||
5. **Serialization**: Implement `ParseTree` and JSON serialization
|
||||
6. **Integration**: Merge with existing cxxopts infrastructure
|
||||
7. **Testing**: Create comprehensive test cases for all features
|
||||
|
||||
## Compatibility
|
||||
- The enhanced library will be backward compatible with existing cxxopts code
|
||||
- New features will be opt-in by default
|
||||
- All existing APIs will continue to work unchanged
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# 选项继承与覆盖问题分析
|
||||
|
||||
## 当前实现
|
||||
当前的选项继承通过 `merge_options` 函数实现,其逻辑是:
|
||||
1. 创建当前命令的选项副本
|
||||
2. 遍历从当前命令到根命令的所有父命令
|
||||
3. 重建合并的选项(注释说明 "We don't merge options directly because cxxopts::Options doesn't support it")
|
||||
|
||||
## 问题
|
||||
1. **覆盖逻辑不明确**:当子命令与父命令存在同名选项时,无法明确子命令选项是否应该覆盖父命令选项
|
||||
2. **重复解析风险**:重建选项可能导致选项被多次解析
|
||||
3. **优先级未定义**:解析结果可能与预期的选项覆盖逻辑不一致
|
||||
|
||||
## 分析
|
||||
从代码来看,`merge_options` 函数并没有实际合并选项,因为 `cxxopts::Options` 不支持直接合并。它只是遍历了父命令,但没有将父命令的选项添加到合并后的选项中。
|
||||
|
||||
当前的解析流程是:
|
||||
1. 创建当前命令的选项副本
|
||||
2. 遍历父命令(但没有合并选项)
|
||||
3. 解析剩余的参数
|
||||
|
||||
这意味着**只有当前命令的选项会被解析**,父命令的选项不会被继承。这可能不是预期的行为。
|
||||
|
||||
## 解决方案
|
||||
需要重新实现选项继承逻辑,明确选项覆盖规则。
|
||||
|
||||
### 选项覆盖规则
|
||||
1. **子命令选项优先**:如果子命令与父命令存在同名选项,则子命令选项覆盖父命令选项
|
||||
2. **父命令选项默认**:如果子命令没有定义某个选项,则继承父命令的选项
|
||||
|
||||
### 实现思路
|
||||
1. 从根命令到当前命令遍历所有命令
|
||||
2. 将每个命令的选项添加到合并后的选项中
|
||||
3. 后添加的选项会覆盖先添加的选项
|
||||
|
||||
### 修改代码
|
||||
需要修改 `merge_options` 函数,将父命令的选项添加到合并后的选项中:
|
||||
|
||||
```cpp
|
||||
void CommandParser::merge_options(const Command& cmd, cxxopts::Options& merged) const {
|
||||
const Command* parent = cmd.parent();
|
||||
if (parent) {
|
||||
merge_options(*parent, merged);
|
||||
// Get all options from parent
|
||||
const auto& parent_opts = parent->options();
|
||||
// We need to copy the options from parent to merged
|
||||
// This requires access to the internal options map of cxxopts::Options
|
||||
// However, cxxopts::Options doesn't expose this directly
|
||||
// So we need to find another way to copy options
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
由于 `cxxopts::Options` 没有提供直接访问内部选项的接口,可能需要修改 `cxxopts` 库或寻找其他解决方案。
|
||||
|
||||
## 结论
|
||||
当前实现没有正确实现选项继承,导致父命令的选项不会被解析。需要修改实现以解决这个问题。
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#include <cxxoptspp.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
try {
|
||||
// Create root command
|
||||
cxxoptspp::Command root_cmd("app", "Test application for dependency rules");
|
||||
root_cmd.add_option("mode", "Operating mode", "default");
|
||||
root_cmd.add_option("output", "Output file path", "out.txt");
|
||||
root_cmd.add_option("verbose", "Enable verbose mode", false);
|
||||
|
||||
// Add a dependency rule: mode == file
|
||||
root_cmd.add_dependency("--mode == file");
|
||||
|
||||
// Add another rule: if verbose is enabled, output must end with .log
|
||||
root_cmd.add_dependency("--verbose && --output == *.log");
|
||||
|
||||
// Create parser and parse
|
||||
cxxoptspp::CommandParser parser(root_cmd);
|
||||
auto result = parser.parse(argc, argv);
|
||||
|
||||
// Print results
|
||||
std::cout << "Command executed successfully!" << std::endl;
|
||||
std::cout << "Mode: " << result["mode"].as<std::string>() << std::endl;
|
||||
std::cout << "Output: " << result["output"].as<std::string>() << std::endl;
|
||||
std::cout << "Verbose: " << result["verbose"].as<bool>() << std::endl;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#include "cxxoptspp.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
// Create root command
|
||||
Command root("app", "Multilevel subcommands example");
|
||||
root.add_option("--verbose", "Verbose mode", false, false);
|
||||
root.add_option("--config", "Configuration file", string("config.json"), false);
|
||||
|
||||
// Create level 1 subcommand
|
||||
auto sub1 = root.add_subcommand("sub1", "Level 1 subcommand");
|
||||
sub1->add_option("--config", "Configuration file", string("sub1_config.json"), false);
|
||||
sub1->add_option("--sub1-option", "Sub1 specific option", 123, false);
|
||||
|
||||
// Create level 2 subcommand
|
||||
auto sub1_sub1 = sub1->add_subcommand("sub1", "Level 2 subcommand");
|
||||
sub1_sub1->add_option("--verbose", "Verbose mode", true, false);
|
||||
sub1_sub1->add_option("--sub1-sub1-option", "Sub1-sub1 specific option", string("test"), false);
|
||||
|
||||
// Create another level 1 subcommand
|
||||
auto sub2 = root.add_subcommand("sub2", "Another level 1 subcommand");
|
||||
sub2->add_option("--config", "Configuration file", string("sub2_config.json"), false);
|
||||
|
||||
// Parse arguments
|
||||
CommandParser parser(root);
|
||||
auto result = parser.parse(argc, argv);
|
||||
|
||||
// Print results
|
||||
cout << "Current command: " << parser.current_command().name() << endl;
|
||||
cout << "Verbose: " << result["verbose"].as<bool>() << endl;
|
||||
cout << "Config: " << result["config"].as<string>() << endl;
|
||||
|
||||
if (result.count("sub1-option")) {
|
||||
cout << "Sub1 option: " << result["sub1-option"].as<int>() << endl;
|
||||
}
|
||||
|
||||
if (result.count("sub1-sub1-option")) {
|
||||
cout << "Sub1-sub1 option: " << result["sub1-sub1-option"].as<string>() << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,501 @@
|
|||
#ifndef CXXOPTSPP_HPP_INCLUDED
|
||||
#define CXXOPTSPP_HPP_INCLUDED
|
||||
|
||||
#include <cxxopts.hpp>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <any>
|
||||
#include <functional>
|
||||
#include <regex>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace cxxoptspp {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Forward declarations
|
||||
class Command;
|
||||
class CommandParser;
|
||||
class Type;
|
||||
class DependencyRule;
|
||||
class ParseTree;
|
||||
|
||||
// ============================
|
||||
// 1. Multi-level Subcommands
|
||||
// ============================
|
||||
|
||||
class Command {
|
||||
public:
|
||||
Command(std::string name, std::string description)
|
||||
: m_name(std::move(name)), m_description(std::move(description)), m_parent(nullptr), m_options(m_name, m_description) {}
|
||||
|
||||
// Subcommand management
|
||||
std::shared_ptr<Command> add_subcommand(std::string name, std::string description = "");
|
||||
std::shared_ptr<Command> get_subcommand(const std::string& name) const;
|
||||
const CommandMap& subcommands() const;
|
||||
|
||||
// Option management with inheritance support
|
||||
template <typename T>
|
||||
Command& add_option(std::string long_option, std::string description, T defaultValue, bool required = false) {
|
||||
// Add the option to the internal cxxopts::Options object
|
||||
m_options.add_option("", std::move(long_option), std::move(description), cxxopts::value<T>(defaultValue)->default_value(std::to_string(defaultValue)), required);
|
||||
|
||||
// Save the option definition with type erasure
|
||||
auto def = std::make_unique<OptionDefinition<T>>(std::move(long_option), std::move(description), defaultValue, required);
|
||||
m_option_definitions.push_back(std::move(def));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Command& add_option(std::string long_option, std::string description, std::string defaultValue, bool required = false) {
|
||||
// Add the option to the internal cxxopts::Options object
|
||||
m_options.add_option("", std::move(long_option), std::move(description), cxxopts::value<std::string>(defaultValue)->default_value('"' + defaultValue + '"'), required);
|
||||
|
||||
// Save the option definition with type erasure
|
||||
auto def = std::make_unique<OptionDefinition<std::string>>(std::move(long_option), std::move(description), std::move(defaultValue), required);
|
||||
m_option_definitions.push_back(std::move(def));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void parse_positional(const std::string& option);
|
||||
void parse_positional(std::vector<std::string> options);
|
||||
|
||||
// Getter for option definitions
|
||||
const std::vector<OptionDefinition>& option_definitions() const;
|
||||
|
||||
// Dependency management
|
||||
void add_dependency(std::string rule);
|
||||
const DependencyList& dependencies() const;
|
||||
|
||||
// Getters
|
||||
const std::string& name() const;
|
||||
const std::string& description() const;
|
||||
const cxxopts::Options& options() const;
|
||||
cxxopts::Options& options();
|
||||
const Command* parent() const;
|
||||
|
||||
// Getter for option definitions (for CommandParser only)
|
||||
const std::vector<std::unique_ptr<OptionDefinitionBase>>& option_definitions() const;
|
||||
|
||||
private:
|
||||
friend class CommandParser;
|
||||
|
||||
std::string m_name;
|
||||
std::string m_description;
|
||||
const Command* m_parent;
|
||||
CommandMap m_subcommands;
|
||||
cxxopts::Options m_options;
|
||||
DependencyList m_dependencies;
|
||||
|
||||
// For option inheritance with type erasure
|
||||
struct OptionDefinitionBase {
|
||||
std::string option;
|
||||
std::string description;
|
||||
std::string defaultValue;
|
||||
bool required;
|
||||
|
||||
OptionDefinitionBase(std::string option, std::string description, std::string defaultValue, bool required)
|
||||
: option(std::move(option)), description(std::move(description)), defaultValue(std::move(defaultValue)), required(required) {}
|
||||
|
||||
virtual ~OptionDefinitionBase() = default;
|
||||
virtual void add_to_options(cxxopts::Options& options) const = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct OptionDefinition : public OptionDefinitionBase {
|
||||
OptionDefinition(std::string option, std::string description, T defaultValue, bool required)
|
||||
: OptionDefinitionBase(std::move(option), std::move(description), std::to_string(defaultValue), required), defaultValue(defaultValue) {}
|
||||
|
||||
void add_to_options(cxxopts::Options& options) const override {
|
||||
T value = defaultValue;
|
||||
options.add_option("", option, description, cxxopts::value<T>(value)->default_value(std::to_string(defaultValue)), required);
|
||||
}
|
||||
|
||||
T defaultValue;
|
||||
};
|
||||
|
||||
// Specialization for string type
|
||||
template <>
|
||||
struct OptionDefinition<std::string> : public OptionDefinitionBase {
|
||||
OptionDefinition(std::string option, std::string description, std::string defaultValue, bool required)
|
||||
: OptionDefinitionBase(std::move(option), std::move(description), defaultValue, required), defaultValue(std::move(defaultValue)) {}
|
||||
|
||||
void add_to_options(cxxopts::Options& options) const override {
|
||||
std::string value = defaultValue;
|
||||
options.add_option("", option, description, cxxopts::value<std::string>(value)->default_value('"' + defaultValue + '"'), required);
|
||||
}
|
||||
|
||||
std::string defaultValue;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<OptionDefinitionBase>> m_option_definitions;
|
||||
};
|
||||
|
||||
class CommandParser {
|
||||
public:
|
||||
explicit CommandParser(Command& root_command);
|
||||
|
||||
cxxopts::ParseResult parse(int argc, const char* const argv[]);
|
||||
cxxopts::ParseResult parse(const std::vector<std::string>& args);
|
||||
|
||||
const Command& current_command() const;
|
||||
const cxxopts::ParseResult& result() const;
|
||||
|
||||
private:
|
||||
struct ParseContext {
|
||||
const Command* current_cmd;
|
||||
std::vector<std::string> remaining_args;
|
||||
cxxopts::Options merged_options;
|
||||
std::vector<std::shared_ptr<DependencyRule>> merged_dependencies;
|
||||
};
|
||||
|
||||
void merge_options(const Command& cmd, cxxopts::Options& merged) const;
|
||||
void merge_dependencies(const Command& cmd, std::vector<std::shared_ptr<DependencyRule>>& merged) const;
|
||||
void validate_dependencies(const cxxopts::ParseResult& result) const;
|
||||
|
||||
Command& m_root_command;
|
||||
const Command* m_current_command;
|
||||
cxxopts::ParseResult m_result;
|
||||
};
|
||||
|
||||
// ============================
|
||||
// 2. Type System
|
||||
// ============================
|
||||
|
||||
class Type {
|
||||
public:
|
||||
virtual ~Type() = default;
|
||||
|
||||
virtual std::any from_string(const std::string& input) const = 0;
|
||||
virtual std::string to_string(const std::any& value) const = 0;
|
||||
virtual bool validate(const std::string& input) const = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class BasicType : public Type {
|
||||
public:
|
||||
std::any from_string(const std::string& input) const override {
|
||||
T value;
|
||||
std::istringstream iss(input);
|
||||
iss >> value;
|
||||
if (!iss.eof()) {
|
||||
throw cxxopts::exceptions::incorrect_argument_type(input);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string to_string(const std::any& value) const override {
|
||||
std::ostringstream oss;
|
||||
oss << std::any_cast<const T&>(value);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool validate(const std::string& input) const override {
|
||||
try {
|
||||
from_string(input);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ContainerType : public Type {
|
||||
public:
|
||||
explicit ContainerType(char delimiter = ',') : m_delimiter(delimiter) {}
|
||||
|
||||
std::any from_string(const std::string& input) const override {
|
||||
std::vector<T> result;
|
||||
std::istringstream iss(input);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(iss, token, m_delimiter)) {
|
||||
BasicType<T> element_type;
|
||||
result.push_back(std::any_cast<T>(element_type.from_string(token)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_string(const std::any& value) const override {
|
||||
const auto& container = std::any_cast<const std::vector<T>&>(value);
|
||||
std::ostringstream oss;
|
||||
BasicType<T> element_type;
|
||||
|
||||
for (size_t i = 0; i < container.size(); ++i) {
|
||||
if (i > 0) oss << m_delimiter;
|
||||
oss << element_type.to_string(container[i]);
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool validate(const std::string& input) const override {
|
||||
try {
|
||||
from_string(input);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
char m_delimiter;
|
||||
};
|
||||
|
||||
// Example custom type: IP Address
|
||||
class IPAddress {
|
||||
public:
|
||||
IPAddress() = default;
|
||||
IPAddress(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
||||
: octets{a, b, c, d} {}
|
||||
|
||||
uint8_t octets[4];
|
||||
|
||||
std::string to_string() const {
|
||||
std::ostringstream oss;
|
||||
oss << static_cast<int>(octets[0]) << "."
|
||||
<< static_cast<int>(octets[1]) << "."
|
||||
<< static_cast<int>(octets[2]) << "."
|
||||
<< static_cast<int>(octets[3]);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool operator==(const IPAddress& other) const {
|
||||
return std::memcmp(octets, other.octets, 4) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Specialize parse_value for IPAddress
|
||||
template<>
|
||||
inline void parse_value(const std::string& text, IPAddress& value) {
|
||||
std::regex ip_regex(R"((\d+)\.(\d+)\.(\d+)\.(\d+))");
|
||||
std::smatch match;
|
||||
|
||||
if (!std::regex_match(text, match, ip_regex)) {
|
||||
throw cxxopts::exceptions::incorrect_argument_type(text);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int octet = std::stoi(match[i+1]);
|
||||
if (octet < 0 || octet > 255) {
|
||||
throw cxxopts::exceptions::incorrect_argument_type(text);
|
||||
}
|
||||
value.octets[i] = static_cast<uint8_t>(octet);
|
||||
}
|
||||
}
|
||||
|
||||
// Specialize parse_value for vector<IPAddress>
|
||||
template<>
|
||||
inline void parse_value(const std::string& text, std::vector<IPAddress>& value) {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
std::stringstream in(text);
|
||||
std::string token;
|
||||
while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
|
||||
IPAddress ip;
|
||||
parse_value(token, ip);
|
||||
value.emplace_back(std::move(ip));
|
||||
}
|
||||
}
|
||||
|
||||
class IPAddressType : public Type {
|
||||
public:
|
||||
std::any from_string(const std::string& input) const override {
|
||||
return IPAddress::from_string(input);
|
||||
}
|
||||
|
||||
std::string to_string(const std::any& value) const override {
|
||||
return std::any_cast<const IPAddress&>(value).to_string();
|
||||
}
|
||||
|
||||
bool validate(const std::string& input) const override {
|
||||
try {
|
||||
IPAddress::from_string(input);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ============================
|
||||
// 3. Dependency Rules Engine
|
||||
// ============================
|
||||
|
||||
class DependencyRule {
|
||||
public:
|
||||
virtual ~DependencyRule() = default;
|
||||
virtual bool evaluate(const cxxopts::ParseResult& result) const = 0;
|
||||
virtual std::string error_message() const = 0;
|
||||
};
|
||||
|
||||
class SimpleDependencyRule : public DependencyRule {
|
||||
public:
|
||||
explicit SimpleDependencyRule(std::string rule_str);
|
||||
bool evaluate(const cxxopts::ParseResult& result) const override;
|
||||
std::string error_message() const override;
|
||||
|
||||
private:
|
||||
enum class TokenType { AND, OR, NOT, LPAREN, RPAREN, OPTION, END };
|
||||
|
||||
struct Token {
|
||||
TokenType type;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
std::vector<Token> tokenize(const std::string& rule) const;
|
||||
std::vector<Token> infix_to_postfix(const std::vector<Token>& infix) const;
|
||||
bool evaluate_postfix(const std::vector<Token>& postfix, const cxxopts::ParseResult& result) const;
|
||||
|
||||
std::string m_rule_str;
|
||||
std::vector<Token> m_postfix;
|
||||
std::string m_error_msg;
|
||||
};
|
||||
|
||||
// ============================
|
||||
// 4. Reversible Parsing & Serialization
|
||||
// ============================
|
||||
|
||||
class ParseNode {
|
||||
public:
|
||||
enum class NodeType { ROOT, COMMAND, OPTION, VALUE, POSITIONAL };
|
||||
|
||||
ParseNode(NodeType type, std::string value, const Command* cmd = nullptr)
|
||||
: m_type(type), m_value(std::move(value)), m_command(cmd) {}
|
||||
|
||||
// Add child node
|
||||
void add_child(std::shared_ptr<ParseNode> child) {
|
||||
m_children.push_back(std::move(child));
|
||||
}
|
||||
|
||||
// Serialization
|
||||
json to_json() const;
|
||||
|
||||
// Deserialization
|
||||
static std::shared_ptr<ParseNode> from_json(const json& j, const Command* root_cmd);
|
||||
|
||||
// Reconstruct command line
|
||||
std::vector<std::string> to_command_line() const;
|
||||
|
||||
private:
|
||||
NodeType m_type;
|
||||
std::string m_value;
|
||||
const Command* m_command;
|
||||
std::vector<std::shared_ptr<ParseNode>> m_children;
|
||||
};
|
||||
|
||||
class ParseTree {
|
||||
public:
|
||||
ParseTree() : m_root(std::make_shared<ParseNode>(ParseNode::NodeType::ROOT, "")) {}
|
||||
|
||||
// Add root command
|
||||
void set_root_command(std::shared_ptr<Command> cmd) {
|
||||
m_root->add_child(std::make_shared<ParseNode>(ParseNode::NodeType::COMMAND, cmd->name(), cmd.get()));
|
||||
m_current_node = m_root->m_children.back().get();
|
||||
}
|
||||
|
||||
// Add subcommand
|
||||
void add_subcommand(std::shared_ptr<Command> cmd) {
|
||||
auto child = std::make_shared<ParseNode>(ParseNode::NodeType::COMMAND, cmd->name(), cmd.get());
|
||||
m_current_node->add_child(std::move(child));
|
||||
m_current_node = m_current_node->m_children.back().get();
|
||||
}
|
||||
|
||||
// Add option
|
||||
void add_option(const std::string& option_str, const Command* cmd = nullptr) {
|
||||
m_current_node->add_child(std::make_shared<ParseNode>(ParseNode::NodeType::OPTION, option_str, cmd));
|
||||
}
|
||||
|
||||
// Add value
|
||||
void add_value(const std::string& value_str, const Command* cmd = nullptr) {
|
||||
m_current_node->add_child(std::make_shared<ParseNode>(ParseNode::NodeType::VALUE, value_str, cmd));
|
||||
}
|
||||
|
||||
// Serialization
|
||||
json to_json() const { return m_root->to_json(); }
|
||||
|
||||
// Deserialization
|
||||
static ParseTree from_json(const json& j, const Command* root_cmd);
|
||||
|
||||
// Reconstruct command line
|
||||
std::vector<std::string> to_command_line() const { return m_root->to_command_line(); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<ParseNode> m_root;
|
||||
ParseNode* m_current_node;
|
||||
};
|
||||
|
||||
// Helper functions for option creation with custom types
|
||||
template <typename T>
|
||||
std::shared_ptr<cxxopts::Value> value() {
|
||||
return std::make_shared<cxxopts::values::standard_value<T>>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<cxxopts::Value> value(T& t) {
|
||||
return std::make_shared<cxxopts::values::standard_value<T>>(&t);
|
||||
}
|
||||
|
||||
// Custom value with type validation
|
||||
template <typename T>
|
||||
class TypedValue : public cxxopts::Value {
|
||||
public:
|
||||
TypedValue() : m_result(std::make_shared<T>()), m_store(m_result.get()) {}
|
||||
explicit TypedValue(T* t) : m_store(t) {}
|
||||
|
||||
void add(const std::string& text) const override {
|
||||
// Not implemented for custom types
|
||||
throw cxxopts::exceptions::option_has_no_value("TypedValue");
|
||||
}
|
||||
|
||||
void parse(const std::string& text) const override {
|
||||
BasicType<T> type;
|
||||
*m_store = std::any_cast<T>(type.from_string(text));
|
||||
}
|
||||
|
||||
bool is_container() const override { return false; }
|
||||
void parse() const override { /* no default */ }
|
||||
bool has_default() const override { return false; }
|
||||
bool has_implicit() const override { return false; }
|
||||
|
||||
std::shared_ptr<cxxopts::Value> default_value(const std::string&) override { return shared_from_this(); }
|
||||
std::shared_ptr<cxxopts::Value> implicit_value(const std::string&) override { return shared_from_this(); }
|
||||
std::shared_ptr<cxxopts::Value> no_implicit_value() override { return shared_from_this(); }
|
||||
std::string get_default_value() const override { return ""; }
|
||||
std::string get_implicit_value() const override { return ""; }
|
||||
bool is_boolean() const override { return false; }
|
||||
std::shared_ptr<cxxopts::Value> clone() const override {
|
||||
return std::make_shared<TypedValue<T>>(*this);
|
||||
}
|
||||
|
||||
const T& get() const { return *m_store; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<T> m_result;
|
||||
T* m_store;
|
||||
};
|
||||
|
||||
// Export to namespace cxxopts for backward compatibility
|
||||
namespace cxxopts {
|
||||
using cxxoptspp::Command;
|
||||
using cxxoptspp::CommandParser;
|
||||
using cxxoptspp::Type;
|
||||
using cxxoptspp::BasicType;
|
||||
using cxxoptspp::ContainerType;
|
||||
using cxxoptspp::IPAddress;
|
||||
using cxxoptspp::IPAddressType;
|
||||
using cxxoptspp::DependencyRule;
|
||||
using cxxoptspp::SimpleDependencyRule;
|
||||
using cxxoptspp::ParseTree;
|
||||
using cxxoptspp::TypedValue;
|
||||
}
|
||||
|
||||
} // namespace cxxoptspp
|
||||
|
||||
#endif // CXXOPTSPP_HPP_INCLUDED
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,411 @@
|
|||
#include <cxxoptspp.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
|
||||
namespace cxxoptspp {
|
||||
|
||||
// ============================
|
||||
// Command Implementation
|
||||
// ============================
|
||||
|
||||
Command::Command(std::string name, std::string description)
|
||||
: m_name(std::move(name)),
|
||||
m_description(std::move(description)),
|
||||
m_parent(nullptr),
|
||||
m_options(m_name)
|
||||
{}
|
||||
|
||||
std::shared_ptr<Command> Command::add_subcommand(std::string name, std::string description) {
|
||||
auto cmd = std::make_shared<Command>(std::move(name), std::move(description));
|
||||
cmd->m_parent = this;
|
||||
m_subcommands[cmd->m_name] = cmd;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
std::shared_ptr<Command> Command::get_subcommand(const std::string& name) const {
|
||||
auto it = m_subcommands.find(name);
|
||||
return it != m_subcommands.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
const Command::CommandMap& Command::subcommands() const {
|
||||
return m_subcommands;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Command::parse_positional(const std::string& option) {
|
||||
m_options.parse_positional(option);
|
||||
}
|
||||
|
||||
void Command::parse_positional(std::vector<std::string> options) {
|
||||
m_options.parse_positional(std::move(options));
|
||||
}
|
||||
|
||||
void Command::add_dependency(std::string rule) {
|
||||
m_dependencies.push_back(std::make_shared<SimpleDependencyRule>(std::move(rule)));
|
||||
}
|
||||
|
||||
const Command::DependencyList& Command::dependencies() const {
|
||||
return m_dependencies;
|
||||
}
|
||||
|
||||
const std::string& Command::name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
const std::string& Command::description() const {
|
||||
return m_description;
|
||||
}
|
||||
|
||||
const cxxopts::Options& Command::options() const {
|
||||
return m_options;
|
||||
}
|
||||
|
||||
cxxopts::Options& Command::options() {
|
||||
return m_options;
|
||||
}
|
||||
|
||||
const Command* Command::parent() const {
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Command::OptionDefinitionBase>>& Command::option_definitions() const {
|
||||
return m_option_definitions;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// CommandParser Implementation
|
||||
// ============================
|
||||
|
||||
CommandParser::CommandParser(Command& root_command)
|
||||
: m_root_command(root_command),
|
||||
m_current_command(&root_command)
|
||||
{}
|
||||
|
||||
cxxopts::ParseResult CommandParser::parse(int argc, const char* const argv[]) {
|
||||
return parse(std::vector<std::string>(argv, argv + argc));
|
||||
}
|
||||
|
||||
cxxopts::ParseResult CommandParser::parse(const std::vector<std::string>& args) {
|
||||
if (args.empty()) {
|
||||
m_result = cxxopts::ParseResult(std::make_shared<cxxopts::OptionMap>());
|
||||
return m_result;
|
||||
}
|
||||
|
||||
std::vector<std::string> remaining_args(args.begin() + 1, args.end()); // skip program name
|
||||
m_current_command = &m_root_command;
|
||||
|
||||
// Traverse command tree
|
||||
while (!remaining_args.empty()) {
|
||||
const auto& arg = remaining_args.front();
|
||||
auto subcmd = m_current_command->get_subcommand(arg);
|
||||
|
||||
if (subcmd) {
|
||||
m_current_command = subcmd.get();
|
||||
remaining_args.erase(remaining_args.begin());
|
||||
} else {
|
||||
break; // found an option, start parsing
|
||||
}
|
||||
}
|
||||
|
||||
// Merge options from all ancestor commands (root to current) with proper inheritance
|
||||
// Create a new Options object with the current command's name and description
|
||||
cxxopts::Options merged_options(m_current_command->name(), m_current_command->description());
|
||||
|
||||
// Collect all commands from root to current (inclusive)
|
||||
std::vector<const Command*> command_chain;
|
||||
const Command* current = m_current_command;
|
||||
while (current) {
|
||||
command_chain.insert(command_chain.begin(), current); // Insert at front to get root first
|
||||
current = current->parent();
|
||||
}
|
||||
|
||||
// Add options from all commands in the chain (root to current)
|
||||
// This ensures that child command options override parent command options
|
||||
for (const Command* cmd : command_chain) {
|
||||
for (const auto& option_def : cmd->option_definitions()) {
|
||||
option_def->add_to_options(merged_options);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge dependencies from all parent commands
|
||||
std::vector<std::shared_ptr<DependencyRule>> merged_deps = m_current_command->dependencies();
|
||||
merge_dependencies(*m_current_command, merged_deps);
|
||||
|
||||
// Parse the remaining arguments
|
||||
m_result = merged_options.parse(remaining_args);
|
||||
|
||||
// Validate dependencies
|
||||
validate_dependencies(m_result);
|
||||
|
||||
return m_result;
|
||||
}
|
||||
|
||||
const Command& CommandParser::current_command() const {
|
||||
return *m_current_command;
|
||||
}
|
||||
|
||||
const cxxopts::ParseResult& CommandParser::result() const {
|
||||
return m_result;
|
||||
}
|
||||
|
||||
void CommandParser::merge_options(const Command& cmd, cxxopts::Options& merged) const {
|
||||
const Command* parent = cmd.parent();
|
||||
if (parent) {
|
||||
merge_options(*parent, merged);
|
||||
// Workaround: Create a new Options object for the parent and parse an empty vector
|
||||
// This will populate the option map with default values
|
||||
cxxopts::Options parent_options = parent->options();
|
||||
parent_options.parse({});
|
||||
// Note: We can't directly copy options because cxxopts::Options doesn't support it
|
||||
// Instead, we rely on the fact that we've already traversed from root to current command
|
||||
// This ensures that the merged options will have the correct priority (child options override parent options)
|
||||
}
|
||||
}
|
||||
|
||||
void CommandParser::merge_dependencies(const Command& cmd, std::vector<std::shared_ptr<DependencyRule>>& merged) const {
|
||||
const Command* parent = cmd.parent();
|
||||
if (parent) {
|
||||
auto parent_deps = parent->dependencies();
|
||||
merged.insert(merged.begin(), parent_deps.begin(), parent_deps.end());
|
||||
merge_dependencies(*parent, merged);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandParser::validate_dependencies(const cxxopts::ParseResult& result) const {
|
||||
// Check all merged dependencies
|
||||
const Command* cmd = m_current_command;
|
||||
while (cmd) {
|
||||
for (const auto& dep : cmd->dependencies()) {
|
||||
if (!dep->evaluate(result)) {
|
||||
throw cxxopts::exceptions::parsing(dep->error_message());
|
||||
}
|
||||
}
|
||||
cmd = cmd->parent();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// SimpleDependencyRule Implementation
|
||||
// ============================
|
||||
|
||||
SimpleDependencyRule::SimpleDependencyRule(std::string rule_str)
|
||||
: m_rule_str(std::move(rule_str)) {
|
||||
auto tokens = tokenize(m_rule_str);
|
||||
m_postfix = infix_to_postfix(tokens);
|
||||
m_error_msg = "Dependency violation: " + m_rule_str;
|
||||
}
|
||||
|
||||
bool SimpleDependencyRule::evaluate(const cxxopts::ParseResult& result) const {
|
||||
return evaluate_postfix(m_postfix, result);
|
||||
}
|
||||
|
||||
std::string SimpleDependencyRule::error_message() const {
|
||||
return m_error_msg;
|
||||
}
|
||||
|
||||
std::vector<SimpleDependencyRule::Token> SimpleDependencyRule::tokenize(const std::string& rule) const {
|
||||
std::vector<Token> tokens;
|
||||
std::istringstream iss(rule);
|
||||
std::string token;
|
||||
|
||||
while (iss >> token) {
|
||||
if (token == "&&") {
|
||||
tokens.push_back({TokenType::AND, token});
|
||||
} else if (token == "||") {
|
||||
tokens.push_back({TokenType::OR, token});
|
||||
} else if (token == "!") {
|
||||
tokens.push_back({TokenType::NOT, token});
|
||||
} else if (token == "(") {
|
||||
tokens.push_back({TokenType::LPAREN, token});
|
||||
} else if (token == ")") {
|
||||
tokens.push_back({TokenType::RPAREN, token});
|
||||
} else {
|
||||
// Option name - remove leading dashes if present
|
||||
std::string opt_name = token;
|
||||
if (opt_name.starts_with("--")) {
|
||||
opt_name = opt_name.substr(2);
|
||||
} else if (opt_name.starts_with("-")) {
|
||||
opt_name = opt_name.substr(1);
|
||||
}
|
||||
tokens.push_back({TokenType::OPTION, opt_name});
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push_back({TokenType::END, ""});
|
||||
return tokens;
|
||||
}
|
||||
|
||||
std::vector<SimpleDependencyRule::Token> SimpleDependencyRule::infix_to_postfix(const std::vector<Token>& infix) const {
|
||||
std::vector<Token> postfix;
|
||||
std::stack<Token> operators;
|
||||
|
||||
auto precedence = [](TokenType type) -> int {
|
||||
if (type == TokenType::NOT) return 3;
|
||||
if (type == TokenType::AND) return 2;
|
||||
if (type == TokenType::OR) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
for (const auto& token : infix) {
|
||||
switch (token.type) {
|
||||
case TokenType::OPTION:
|
||||
postfix.push_back(token);
|
||||
break;
|
||||
case TokenType::LPAREN:
|
||||
operators.push(token);
|
||||
break;
|
||||
case TokenType::RPAREN:
|
||||
while (!operators.empty() && operators.top().type != TokenType::LPAREN) {
|
||||
postfix.push_back(operators.top());
|
||||
operators.pop();
|
||||
}
|
||||
if (!operators.empty()) operators.pop(); // pop '('
|
||||
break;
|
||||
case TokenType::AND:
|
||||
case TokenType::OR:
|
||||
case TokenType::NOT:
|
||||
while (!operators.empty() && precedence(operators.top().type) >= precedence(token.type)) {
|
||||
postfix.push_back(operators.top());
|
||||
operators.pop();
|
||||
}
|
||||
operators.push(token);
|
||||
break;
|
||||
case TokenType::END:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (!operators.empty()) {
|
||||
postfix.push_back(operators.top());
|
||||
operators.pop();
|
||||
}
|
||||
|
||||
return postfix;
|
||||
}
|
||||
|
||||
bool SimpleDependencyRule::evaluate_postfix(const std::vector<Token>& postfix, const cxxopts::ParseResult& result) const {
|
||||
std::stack<bool> values;
|
||||
|
||||
for (const auto& token : postfix) {
|
||||
switch (token.type) {
|
||||
case TokenType::OPTION:
|
||||
values.push(result.count(token.value) > 0);
|
||||
break;
|
||||
case TokenType::AND:
|
||||
{
|
||||
bool rhs = values.top(); values.pop();
|
||||
bool lhs = values.top(); values.pop();
|
||||
values.push(lhs && rhs);
|
||||
break;
|
||||
}
|
||||
case TokenType::OR:
|
||||
{
|
||||
bool rhs = values.top(); values.pop();
|
||||
bool lhs = values.top(); values.pop();
|
||||
values.push(lhs || rhs);
|
||||
break;
|
||||
}
|
||||
case TokenType::NOT:
|
||||
{
|
||||
bool val = values.top(); values.pop();
|
||||
values.push(!val);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !values.empty() && values.top();
|
||||
}
|
||||
|
||||
// ============================
|
||||
// ParseNode Implementation
|
||||
// ============================
|
||||
|
||||
json ParseNode::to_json() const {
|
||||
json j;
|
||||
j["type"] = static_cast<int>(m_type);
|
||||
j["value"] = m_value;
|
||||
if (m_command) {
|
||||
j["command_name"] = m_command->name();
|
||||
}
|
||||
if (!m_children.empty()) {
|
||||
json children;
|
||||
for (const auto& child : m_children) {
|
||||
children.push_back(child->to_json());
|
||||
}
|
||||
j["children"] = children;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
std::shared_ptr<ParseNode> ParseNode::from_json(const json& j, const Command* root_cmd) {
|
||||
int type_int = j.at("type").get<int>();
|
||||
NodeType type = static_cast<NodeType>(type_int);
|
||||
std::string value = j.at("value").get<std::string>();
|
||||
|
||||
// Find command if specified
|
||||
const Command* cmd = nullptr;
|
||||
if (j.contains("command_name")) {
|
||||
std::string cmd_name = j.at("command_name").get<std::string>();
|
||||
// For simplicity, we only set root command
|
||||
if (root_cmd && root_cmd->name() == cmd_name) {
|
||||
cmd = root_cmd;
|
||||
}
|
||||
// TODO: Handle nested commands
|
||||
}
|
||||
|
||||
auto node = std::make_shared<ParseNode>(type, value, cmd);
|
||||
|
||||
if (j.contains("children")) {
|
||||
for (const auto& child_j : j.at("children")) {
|
||||
node->add_child(from_json(child_j, root_cmd));
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
std::vector<std::string> ParseNode::to_command_line() const {
|
||||
std::vector<std::string> args;
|
||||
|
||||
switch (m_type) {
|
||||
case NodeType::ROOT:
|
||||
break;
|
||||
case NodeType::COMMAND:
|
||||
case NodeType::OPTION:
|
||||
args.push_back(m_value);
|
||||
break;
|
||||
case NodeType::VALUE:
|
||||
args.push_back(m_value);
|
||||
break;
|
||||
case NodeType::POSITIONAL:
|
||||
args.push_back(m_value);
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto& child : m_children) {
|
||||
auto child_args = child->to_command_line();
|
||||
args.insert(args.end(), child_args.begin(), child_args.end());
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// ParseTree Implementation
|
||||
// ============================
|
||||
|
||||
ParseTree ParseTree::from_json(const json& j, const Command* root_cmd) {
|
||||
ParseTree tree;
|
||||
auto root_node = ParseNode::from_json(j, root_cmd);
|
||||
tree.m_root = std::move(root_node);
|
||||
tree.m_current_node = tree.m_root.get();
|
||||
return tree;
|
||||
}
|
||||
|
||||
} // namespace cxxoptspp
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
#include <cxxoptspp.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
namespace cxxoptspp {
|
||||
|
||||
// Enhanced DependencyRule with value comparison support
|
||||
|
||||
class EnhancedDependencyRule : public DependencyRule {
|
||||
public:
|
||||
explicit EnhancedDependencyRule(std::string rule_str)
|
||||
: m_rule_str(std::move(rule_str)) {
|
||||
auto tokens = tokenize(m_rule_str);
|
||||
m_postfix = infix_to_postfix(tokens);
|
||||
m_error_msg = "Dependency violation: " + m_rule_str;
|
||||
}
|
||||
|
||||
bool evaluate(const cxxopts::ParseResult& result) const override {
|
||||
return evaluate_postfix(m_postfix, result);
|
||||
}
|
||||
|
||||
std::string error_message() const override {
|
||||
return m_error_msg;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class TokenType {
|
||||
AND, OR, NOT,
|
||||
LPAREN, RPAREN,
|
||||
OPTION,
|
||||
EQ, NE, GT, LT, GE, LE,
|
||||
VALUE,
|
||||
END
|
||||
};
|
||||
|
||||
struct Token {
|
||||
TokenType type;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
std::vector<Token> tokenize(const std::string& rule) const {
|
||||
std::vector<Token> tokens;
|
||||
std::string input = rule;
|
||||
|
||||
// Handle operators with two characters first
|
||||
std::vector<std::pair<std::string, TokenType>> operators = {
|
||||
{"&&", TokenType::AND},
|
||||
{"||", TokenType::OR},
|
||||
{"==", TokenType::EQ},
|
||||
{"!=", TokenType::NE},
|
||||
{">=", TokenType::GE},
|
||||
{"<=", TokenType::LE},
|
||||
{">", TokenType::GT},
|
||||
{"<", TokenType::LT},
|
||||
{"!", TokenType::NOT},
|
||||
{"(", TokenType::LPAREN},
|
||||
{")", TokenType::RPAREN}
|
||||
};
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < input.size()) {
|
||||
// Skip whitespace
|
||||
while (pos < input.size() && std::isspace(input[pos])) {
|
||||
++pos;
|
||||
}
|
||||
if (pos >= input.size()) break;
|
||||
|
||||
bool found = false;
|
||||
|
||||
// Check for operators
|
||||
for (const auto& [op_str, op_type] : operators) {
|
||||
if (input.substr(pos, op_str.size()) == op_str) {
|
||||
tokens.push_back({op_type, op_str});
|
||||
pos += op_str.size();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) continue;
|
||||
|
||||
// Check for quoted values
|
||||
if (input[pos] == '"' || input[pos] == "'") {
|
||||
char quote = input[pos++];
|
||||
size_t end_pos = input.find(quote, pos);
|
||||
if (end_pos == std::string::npos) {
|
||||
throw std::invalid_argument("Unclosed quote in rule: " + rule);
|
||||
}
|
||||
std::string val = input.substr(pos, end_pos - pos);
|
||||
tokens.push_back({TokenType::VALUE, val});
|
||||
pos = end_pos + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for options or values
|
||||
size_t end_pos = pos;
|
||||
while (end_pos < input.size() &&
|
||||
!std::isspace(input[end_pos]) &&
|
||||
input[end_pos] != '"' && input[end_pos] != "'" &&
|
||||
input.substr(end_pos, 2) != "&&" && input.substr(end_pos, 2) != "||" &&
|
||||
input.substr(end_pos, 2) != "==" && input.substr(end_pos, 2) != "!=" &&
|
||||
input.substr(end_pos, 2) != ">=" && input.substr(end_pos, 2) != "<=" &&
|
||||
input[end_pos] != '>' && input[end_pos] != '<' &&
|
||||
input[end_pos] != '!' && input[end_pos] != '(' && input[end_pos] != ')') {
|
||||
++end_pos;
|
||||
}
|
||||
|
||||
std::string token_str = input.substr(pos, end_pos - pos);
|
||||
if (!token_str.empty()) {
|
||||
// Check if it's an option (starts with -- or -)
|
||||
if (token_str.starts_with("--")) {
|
||||
tokens.push_back({TokenType::OPTION, token_str.substr(2)});
|
||||
} else if (token_str.starts_with("-")) {
|
||||
tokens.push_back({TokenType::OPTION, token_str.substr(1)});
|
||||
} else {
|
||||
// Assume it's a value
|
||||
tokens.push_back({TokenType::VALUE, token_str});
|
||||
}
|
||||
}
|
||||
|
||||
pos = end_pos;
|
||||
}
|
||||
|
||||
tokens.push_back({TokenType::END, ""});
|
||||
return tokens;
|
||||
}
|
||||
|
||||
std::vector<Token> infix_to_postfix(const std::vector<Token>& infix) const {
|
||||
std::vector<Token> postfix;
|
||||
std::stack<Token> operators;
|
||||
|
||||
auto precedence = [](TokenType type) -> int {
|
||||
if (type == TokenType::NOT) return 5;
|
||||
if (type == TokenType::GT || type == TokenType::LT || type == TokenType::GE || type == TokenType::LE) return 4;
|
||||
if (type == TokenType::EQ || type == TokenType::NE) return 3;
|
||||
if (type == TokenType::AND) return 2;
|
||||
if (type == TokenType::OR) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
for (const auto& token : infix) {
|
||||
switch (token.type) {
|
||||
case TokenType::OPTION:
|
||||
case TokenType::VALUE:
|
||||
postfix.push_back(token);
|
||||
break;
|
||||
case TokenType::LPAREN:
|
||||
operators.push(token);
|
||||
break;
|
||||
case TokenType::RPAREN:
|
||||
while (!operators.empty() && operators.top().type != TokenType::LPAREN) {
|
||||
postfix.push_back(operators.top());
|
||||
operators.pop();
|
||||
}
|
||||
if (!operators.empty()) operators.pop(); // pop '('
|
||||
break;
|
||||
case TokenType::AND:
|
||||
case TokenType::OR:
|
||||
case TokenType::NOT:
|
||||
case TokenType::EQ:
|
||||
case TokenType::NE:
|
||||
case TokenType::GT:
|
||||
case TokenType::LT:
|
||||
case TokenType::GE:
|
||||
case TokenType::LE:
|
||||
while (!operators.empty() && precedence(operators.top().type) >= precedence(token.type)) {
|
||||
postfix.push_back(operators.top());
|
||||
operators.pop();
|
||||
}
|
||||
operators.push(token);
|
||||
break;
|
||||
case TokenType::END:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (!operators.empty()) {
|
||||
postfix.push_back(operators.top());
|
||||
operators.pop();
|
||||
}
|
||||
|
||||
return postfix;
|
||||
}
|
||||
|
||||
bool evaluate_postfix(const std::vector<Token>& postfix, const cxxopts::ParseResult& result) const {
|
||||
// Stack holds either booleans (for existence checks) or strings (for option names/values)
|
||||
std::stack<std::variant<bool, std::string>> values;
|
||||
|
||||
for (const auto& token : postfix) {
|
||||
switch (token.type) {
|
||||
case TokenType::OPTION:
|
||||
{
|
||||
// For options, we need to store the name to retrieve the value later if needed
|
||||
values.push(token.value);
|
||||
break;
|
||||
}
|
||||
case TokenType::VALUE:
|
||||
{
|
||||
values.push(token.value);
|
||||
break;
|
||||
}
|
||||
case TokenType::AND:
|
||||
case TokenType::OR:
|
||||
case TokenType::NOT:
|
||||
{
|
||||
// For logical operations, we need boolean values
|
||||
// Convert any string option names to existence booleans
|
||||
auto convert_to_bool = [&result](std::variant<bool, std::string>& var) -> bool {
|
||||
if (std::holds_alternative<bool>(var)) {
|
||||
return std::get<bool>(var);
|
||||
}
|
||||
const std::string& opt_name = std::get<std::string>(var);
|
||||
return result.count(opt_name) > 0;
|
||||
};
|
||||
|
||||
if (token.type == TokenType::NOT) {
|
||||
auto val_var = values.top(); values.pop();
|
||||
bool val = convert_to_bool(val_var);
|
||||
values.push(!val);
|
||||
} else {
|
||||
auto rhs_var = values.top(); values.pop();
|
||||
auto lhs_var = values.top(); values.pop();
|
||||
|
||||
bool lhs = convert_to_bool(lhs_var);
|
||||
bool rhs = convert_to_bool(rhs_var);
|
||||
|
||||
if (token.type == TokenType::AND) {
|
||||
values.push(lhs && rhs);
|
||||
} else { // OR
|
||||
values.push(lhs || rhs);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TokenType::EQ:
|
||||
case TokenType::NE:
|
||||
case TokenType::GT:
|
||||
case TokenType::LT:
|
||||
case TokenType::GE:
|
||||
case TokenType::LE:
|
||||
{
|
||||
auto rhs_var = values.top(); values.pop();
|
||||
auto lhs_var = values.top(); values.pop();
|
||||
|
||||
std::string lhs_val, rhs_val;
|
||||
|
||||
// Resolve lhs - could be an option name or a value
|
||||
if (std::holds_alternative<std::string>(lhs_var)) {
|
||||
const std::string& lhs_str = std::get<std::string>(lhs_var);
|
||||
// Check if it's an option (exists in result)
|
||||
if (result.count(lhs_str) > 0) {
|
||||
// It's an option - get its value from the result
|
||||
lhs_val = result[lhs_str].as<std::string>();
|
||||
} else {
|
||||
// It's a literal value
|
||||
lhs_val = lhs_str;
|
||||
}
|
||||
} else {
|
||||
// This should not happen for comparison operations
|
||||
values.push(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Resolve rhs - could be an option name or a value
|
||||
if (std::holds_alternative<std::string>(rhs_var)) {
|
||||
const std::string& rhs_str = std::get<std::string>(rhs_var);
|
||||
// Check if it's an option (exists in result)
|
||||
if (result.count(rhs_str) > 0) {
|
||||
// It's an option - get its value from the result
|
||||
rhs_val = result[rhs_str].as<std::string>();
|
||||
} else {
|
||||
// It's a literal value
|
||||
rhs_val = rhs_str;
|
||||
}
|
||||
} else {
|
||||
// This should not happen for comparison operations
|
||||
values.push(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform the appropriate comparison
|
||||
bool result_val = false;
|
||||
switch (token.type) {
|
||||
case TokenType::EQ:
|
||||
result_val = (lhs_val == rhs_val);
|
||||
break;
|
||||
case TokenType::NE:
|
||||
result_val = (lhs_val != rhs_val);
|
||||
break;
|
||||
case TokenType::GT:
|
||||
result_val = (lhs_val > rhs_val);
|
||||
break;
|
||||
case TokenType::LT:
|
||||
result_val = (lhs_val < rhs_val);
|
||||
break;
|
||||
case TokenType::GE:
|
||||
result_val = (lhs_val >= rhs_val);
|
||||
break;
|
||||
case TokenType::LE:
|
||||
result_val = (lhs_val <= rhs_val);
|
||||
break;
|
||||
}
|
||||
|
||||
values.push(result_val);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Not implemented yet
|
||||
values.push(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the final result is a boolean
|
||||
if (values.empty()) return false;
|
||||
if (std::holds_alternative<bool>(values.top())) {
|
||||
return std::get<bool>(values.top());
|
||||
}
|
||||
// Last token was an option name, convert to existence check
|
||||
return result.count(std::get<std::string>(values.top())) > 0;
|
||||
}
|
||||
|
||||
std::string m_rule_str;
|
||||
std::vector<Token> m_postfix;
|
||||
std::string m_error_msg;
|
||||
};
|
||||
|
||||
// Update Command to use EnhancedDependencyRule
|
||||
void Command::add_dependency(std::string rule) {
|
||||
m_dependencies.push_back(std::make_shared<EnhancedDependencyRule>(std::move(rule)));
|
||||
}
|
||||
|
||||
} // namespace cxxoptspp
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
#include <iostream>
|
||||
#include <cxxoptspp.hpp>
|
||||
|
||||
using namespace cxxopts;
|
||||
using namespace cxxoptspp;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
// Create root command
|
||||
Command root_cmd("mytool", "A tool with enhanced features");
|
||||
|
||||
// Add root options
|
||||
int verbose_level = 0;
|
||||
root_cmd.add_options()
|
||||
("h,help", "Print help message")
|
||||
("v,verbose", "Increase verbosity", value(verbose_level))
|
||||
("d,debug", "Enable debug mode", value<bool>());
|
||||
|
||||
// Add subcommands
|
||||
auto module_cmd = root_cmd.add_subcommand("module", "Module subcommand");
|
||||
auto subcmd = module_cmd->add_subcommand("subcmd", "Nested subcommand");
|
||||
|
||||
// Add module options
|
||||
std::string module_name;
|
||||
module_cmd->add_options()
|
||||
("n,name", "Module name", value(module_name))
|
||||
("m,mode", "Operation mode", value<std::string>());
|
||||
|
||||
// Add nested subcommand options with dependencies
|
||||
std::string output_file;
|
||||
bool overwrite = false;
|
||||
|
||||
subcmd->add_options()
|
||||
("o,output", "Output file", value(output_file))
|
||||
("w,overwrite", "Overwrite existing file", value(overwrite))
|
||||
("s,size", "File size", value<int>())
|
||||
("t,type", "File type", value<std::string>());
|
||||
|
||||
// Add dependency rules
|
||||
// --output is only valid when --mode=file (inherited from module)
|
||||
subcmd->add_dependency("(mode == file) ==> output");
|
||||
// --verbose needs to be used with --debug
|
||||
root_cmd.add_dependency("verbose ==> debug");
|
||||
// Either --size or --type must be provided
|
||||
subcmd->add_dependency("size || type");
|
||||
|
||||
// Example with custom type (IPAddress)
|
||||
Command network_cmd = root_cmd.add_subcommand("network", "Network operations");
|
||||
IPAddress server_ip;
|
||||
std::vector<IPAddress> client_ips;
|
||||
|
||||
network_cmd->add_options()
|
||||
("s,server", "Server IP address", value<IPAddress>())
|
||||
("c,clients", "Client IP addresses (comma-separated)", value<std::vector<IPAddress>>());
|
||||
|
||||
// Parse command line
|
||||
CommandParser parser(root_cmd);
|
||||
ParseResult result = parser.parse(argc, argv);
|
||||
|
||||
// Handle help
|
||||
if (result.count("help")) {
|
||||
std::cout << root_cmd.options().help() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Display results
|
||||
std::cout << "Root options:" << std::endl;
|
||||
if (result.count("verbose")) {
|
||||
std::cout << " Verbose level: " << result["verbose"].as<int>() << std::endl;
|
||||
}
|
||||
if (result.count("debug")) {
|
||||
std::cout << " Debug mode: enabled" << std::endl;
|
||||
}
|
||||
|
||||
const Command& current_cmd = parser.current_command();
|
||||
std::cout << "\nCurrent command path: " << current_cmd.name() << std::endl;
|
||||
|
||||
// Show module options if applicable
|
||||
if (result.count("name")) {
|
||||
std::cout << "Module name: " << result["name"].as<std::string>() << std::endl;
|
||||
}
|
||||
if (result.count("mode")) {
|
||||
std::cout << "Mode: " << result["mode"].as<std::string>() << std::endl;
|
||||
}
|
||||
if (result.count("output")) {
|
||||
std::cout << "Output file: " << result["output"].as<std::string>() << std::endl;
|
||||
}
|
||||
if (result.count("overwrite")) {
|
||||
std::cout << "Overwrite: " << (result["overwrite"].as<bool>() ? "yes" : "no") << std::endl;
|
||||
}
|
||||
|
||||
// Example with IP address custom type
|
||||
if (current_cmd.name() == "network") {
|
||||
if (result.count("server")) {
|
||||
IPAddress ip = result["server"].as<IPAddress>();
|
||||
std::cout << "Server IP: " << ip.to_string() << std::endl;
|
||||
}
|
||||
if (result.count("clients")) {
|
||||
auto ips = result["clients"].as<std::vector<IPAddress>>();
|
||||
std::cout << "Client IPs: ";
|
||||
for (size_t i = 0; i < ips.size(); ++i) {
|
||||
if (i > 0) std::cout << ", ";
|
||||
std::cout << ips[i].to_string();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Example of reversible parsing
|
||||
std::cout << "\nReversible parsing example:" << std::endl;
|
||||
std::cout << "Original command: ";
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
std::cout << argv[i] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// TODO: Demonstrate serialization/deserialization
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
#include <cxxoptspp.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
namespace cxxoptspp {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// ============================
|
||||
// ParseNode Implementation
|
||||
// ============================
|
||||
|
||||
ParseNode::ParseNode()
|
||||
: type(Type::ROOT) {}
|
||||
|
||||
ParseNode::ParseNode(std::string name)
|
||||
: type(Type::COMMAND), name(std::move(name)) {}
|
||||
|
||||
ParseNode::ParseNode(std::string name, std::string value)
|
||||
: type(Type::OPTION), name(std::move(name)), value(std::move(value)) {}
|
||||
|
||||
json ParseNode::to_json() const {
|
||||
json j;
|
||||
|
||||
j["type"] = static_cast<int>(type);
|
||||
|
||||
if (!name.empty()) {
|
||||
j["name"] = name;
|
||||
}
|
||||
|
||||
if (!value.empty()) {
|
||||
j["value"] = value;
|
||||
}
|
||||
|
||||
if (!children.empty()) {
|
||||
json j_children = json::array();
|
||||
for (const auto& child : children) {
|
||||
j_children.push_back(child.to_json());
|
||||
}
|
||||
j["children"] = j_children;
|
||||
}
|
||||
|
||||
if (!metadata.empty()) {
|
||||
j["metadata"] = metadata;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
ParseNode ParseNode::from_json(const json& j) {
|
||||
ParseNode node;
|
||||
|
||||
int type_int = j.at("type").get<int>();
|
||||
node.type = static_cast<ParseNode::Type>(type_int);
|
||||
|
||||
if (j.contains("name")) {
|
||||
node.name = j.at("name").get<std::string>();
|
||||
}
|
||||
|
||||
if (j.contains("value")) {
|
||||
node.value = j.at("value").get<std::string>();
|
||||
}
|
||||
|
||||
if (j.contains("children")) {
|
||||
for (const auto& j_child : j.at("children")) {
|
||||
node.children.push_back(from_json(j_child));
|
||||
}
|
||||
}
|
||||
|
||||
if (j.contains("metadata")) {
|
||||
node.metadata = j.at("metadata").get<json>();
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
std::vector<std::string> ParseNode::to_command_line() const {
|
||||
std::vector<std::string> args;
|
||||
|
||||
switch (type) {
|
||||
case Type::ROOT:
|
||||
for (const auto& child : children) {
|
||||
auto child_args = child.to_command_line();
|
||||
args.insert(args.end(), child_args.begin(), child_args.end());
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::COMMAND:
|
||||
args.push_back(name);
|
||||
for (const auto& child : children) {
|
||||
auto child_args = child.to_command_line();
|
||||
args.insert(args.end(), child_args.begin(), child_args.end());
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::OPTION:
|
||||
if (value.empty()) {
|
||||
// Boolean option, just use --name
|
||||
args.push_back("--" + name);
|
||||
} else {
|
||||
// Value option, use --name=value or --name value
|
||||
// Use the shorter form when possible
|
||||
if (value.find(' ') == std::string::npos) {
|
||||
args.push_back("--" + name + "=" + value);
|
||||
} else {
|
||||
args.push_back("--" + name);
|
||||
args.push_back(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// ParseTree Implementation
|
||||
// ============================
|
||||
|
||||
ParseTree::ParseTree()
|
||||
: m_root(std::make_shared<ParseNode>()) {}
|
||||
|
||||
json ParseTree::to_json() const {
|
||||
return m_root->to_json();
|
||||
}
|
||||
|
||||
void ParseTree::from_json(const json& j) {
|
||||
m_root = std::make_shared<ParseNode>(ParseNode::from_json(j));
|
||||
}
|
||||
|
||||
std::string ParseTree::to_string() const {
|
||||
return to_json().dump(2);
|
||||
}
|
||||
|
||||
std::vector<std::string> ParseTree::to_command_line() const {
|
||||
return m_root->to_command_line();
|
||||
}
|
||||
|
||||
// ============================
|
||||
// StateSerializer Implementation
|
||||
// ============================
|
||||
|
||||
std::string StateSerializer::serialize(const CommandParser& parser, const cxxopts::ParseResult& result) {
|
||||
json j;
|
||||
|
||||
// Serialize options with original input strings
|
||||
for (const auto& [opt_name, opt_value] : parser.original_inputs()) {
|
||||
j["original_inputs"][opt_name] = opt_value;
|
||||
}
|
||||
|
||||
// Serialize command hierarchy
|
||||
if (parser.current_command()) {
|
||||
j["command_hierarchy"] = parser.command_hierarchy();
|
||||
}
|
||||
|
||||
// Serialize option definitions (simplified)
|
||||
// In real implementation, we'd need to track all option metadata
|
||||
|
||||
return j.dump(2);
|
||||
}
|
||||
|
||||
std::string StateSerializer::serialize(const ParseTree& tree) {
|
||||
return tree.to_string();
|
||||
}
|
||||
|
||||
std::vector<std::string> StateSerializer::deserialize_to_command_line(const std::string& serialized) {
|
||||
json j = json::parse(serialized);
|
||||
ParseTree tree;
|
||||
tree.from_json(j);
|
||||
return tree.to_command_line();
|
||||
}
|
||||
|
||||
// ============================
|
||||
// CommandParser Implementation for reversible parsing
|
||||
// ============================
|
||||
|
||||
void CommandParser::trace_option(const std::string& opt_name, const std::string& opt_value) {
|
||||
m_original_inputs[opt_name] = opt_value;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> CommandParser::original_inputs() const {
|
||||
return m_original_inputs;
|
||||
}
|
||||
|
||||
std::vector<std::string> CommandParser::command_hierarchy() const {
|
||||
std::vector<std::string> hierarchy;
|
||||
|
||||
auto cmd = m_current_command;
|
||||
while (cmd) {
|
||||
hierarchy.push_back(cmd->name());
|
||||
cmd = cmd->parent();
|
||||
}
|
||||
|
||||
std::reverse(hierarchy.begin(), hierarchy.end());
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// Additional helper functions for JSON serialization/deserialization
|
||||
// =============================================================
|
||||
|
||||
json Command::to_json() const {
|
||||
json j;
|
||||
|
||||
j["name"] = m_name;
|
||||
j["description"] = m_description;
|
||||
|
||||
if (m_parent) {
|
||||
j["parent"] = m_parent->name();
|
||||
}
|
||||
|
||||
// Serialize subcommands
|
||||
json subcommands = json::array();
|
||||
for (const auto& [name, cmd] : m_subcommands) {
|
||||
subcommands.push_back(cmd->to_json());
|
||||
}
|
||||
j["subcommands"] = subcommands;
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
json Options::to_json() const {
|
||||
json j;
|
||||
|
||||
// This would need to access the internal option details from cxxopts
|
||||
// For demonstration purposes, we'll keep it simple
|
||||
j["options"] = "... internal options ...";
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
} // namespace cxxoptspp
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cxxoptspp.hpp>
|
||||
|
||||
int main() {
|
||||
// Test 1: Command hierarchy
|
||||
std::cout << "Test 1: Command hierarchy" << std::endl;
|
||||
try {
|
||||
cxxoptspp::Command root_cmd("tool", "A tool with subcommands");
|
||||
auto& module_cmd = root_cmd.add_subcommand("module", "A module command");
|
||||
auto& subcmd_cmd = module_cmd.add_subcommand("subcmd", "A subcommand");
|
||||
|
||||
subcmd_cmd.add_option<int>("--opt", "An option");
|
||||
|
||||
cxxoptspp::CommandParser parser(root_cmd);
|
||||
|
||||
int argc = 4;
|
||||
const char* argv[] = {"tool", "module", "subcmd", "--opt=42"};
|
||||
|
||||
auto result = parser.parse(argc, argv);
|
||||
if (result.count("opt")) {
|
||||
std::cout << "✓ Parsed --opt=" << result["opt"].as<int>() << std::endl;
|
||||
} else {
|
||||
std::cout << "✗ Failed to parse --opt" << std::endl;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "✗ Exception: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
// Test 2: Dependency rules
|
||||
std::cout << std::endl << "Test 2: Dependency rules" << std::endl;
|
||||
try {
|
||||
cxxoptspp::Command cmd("test", "Dependency test");
|
||||
cmd.add_option<bool>("--verbose", "Verbose output");
|
||||
cmd.add_option<bool>("--debug", "Debug output");
|
||||
|
||||
// Add rule: --verbose requires --debug
|
||||
cmd.add_dependency("--verbose ==> --debug");
|
||||
|
||||
cxxoptspp::CommandParser parser(cmd);
|
||||
|
||||
// This should fail because --verbose is used without --debug
|
||||
int argc = 2;
|
||||
const char* argv[] = {"test", "--verbose"};
|
||||
|
||||
try {
|
||||
auto result = parser.parse(argc, argv);
|
||||
std::cout << "✗ Dependency rule should have failed but passed" << std::endl;
|
||||
} catch (const cxxopts::exceptions::parsing& e) {
|
||||
std::cout << "✓ Dependency rule correctly caught violation: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "✗ Exception: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
// Test 3: Custom type IPAddress
|
||||
std::cout << std::endl << "Test 3: Custom type IPAddress" << std::endl;
|
||||
try {
|
||||
cxxoptspp::Command cmd("test", "IP Address test");
|
||||
cmd.add_option<cxxoptspp::IPAddress>("--ip", "An IP address");
|
||||
cmd.add_option<std::vector<cxxoptspp::IPAddress>>("--ips", "Multiple IP addresses");
|
||||
|
||||
cxxoptspp::CommandParser parser(cmd);
|
||||
|
||||
int argc = 3;
|
||||
const char* argv[] = {"test", "--ip=192.168.1.1", "--ips=10.0.0.1,10.0.0.2"};
|
||||
|
||||
auto result = parser.parse(argc, argv);
|
||||
|
||||
if (result.count("ip")) {
|
||||
auto ip = result["ip"].as<cxxoptspp::IPAddress>();
|
||||
std::cout << "✓ Parsed single IP: " << static_cast<int>(ip.octets[0]) << "."
|
||||
<< static_cast<int>(ip.octets[1]) << "."
|
||||
<< static_cast<int>(ip.octets[2]) << "."
|
||||
<< static_cast<int>(ip.octets[3]) << std::endl;
|
||||
} else {
|
||||
std::cout << "✗ Failed to parse single IP" << std::endl;
|
||||
}
|
||||
|
||||
if (result.count("ips")) {
|
||||
auto ips = result["ips"].as<std::vector<cxxoptspp::IPAddress>>();
|
||||
std::cout << "✓ Parsed multiple IPs (" << ips.size() << "):" << std::endl;
|
||||
for (const auto& ip : ips) {
|
||||
std::cout << " - " << static_cast<int>(ip.octets[0]) << "."
|
||||
<< static_cast<int>(ip.octets[1]) << "."
|
||||
<< static_cast<int>(ip.octets[2]) << "."
|
||||
<< static_cast<int>(ip.octets[3]) << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "✗ Failed to parse multiple IPs" << std::endl;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "✗ Exception: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
// Test 4: Reversible parsing
|
||||
std::cout << std::endl << "Test 4: Reversible parsing" << std::endl;
|
||||
try {
|
||||
cxxoptspp::Command cmd("test", "Reversible parsing test");
|
||||
cmd.add_option<bool>("--verbose", "Verbose output");
|
||||
cmd.add_option<int>("--count", "A count");
|
||||
cmd.add_option<std::string>("--name", "A name");
|
||||
|
||||
cxxoptspp::CommandParser parser(cmd);
|
||||
|
||||
int argc = 5;
|
||||
const char* argv[] = {"test", "--verbose", "--count=42", "--name=test_name"};
|
||||
|
||||
auto result = parser.parse(argc, argv);
|
||||
|
||||
// Get original inputs
|
||||
auto original = parser.original_inputs();
|
||||
std::cout << "✓ Original inputs:" << std::endl;
|
||||
for (const auto& [opt, val] : original) {
|
||||
std::cout << " - " << opt << "=" << val << std::endl;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "✗ Exception: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl << "All tests completed" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
#include <cxxoptspp.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <regex>
|
||||
|
||||
namespace cxxoptspp {
|
||||
|
||||
// ============================
|
||||
// Type System Implementation
|
||||
// ============================
|
||||
|
||||
AbstractType::AbstractType(std::string name)
|
||||
: m_name(std::move(name)) {}
|
||||
|
||||
AbstractType::~AbstractType() = default;
|
||||
|
||||
std::string AbstractType::name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Built-in Type Implementations
|
||||
// ============================
|
||||
|
||||
// StringType
|
||||
template <>
|
||||
std::string StringType::parse(const std::string& value) const {
|
||||
return value;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string StringType::serialize(const std::string& value) const {
|
||||
return value;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string StringType::to_string(const std::string& value) const {
|
||||
return value;
|
||||
}
|
||||
|
||||
// IntType
|
||||
template <>
|
||||
int IntType::parse(const std::string& value) const {
|
||||
return std::stoi(value);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string IntType::serialize(const int& value) const {
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string IntType::to_string(const int& value) const {
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
// BoolType
|
||||
template <>
|
||||
bool BoolType::parse(const std::string& value) const {
|
||||
std::string lower = value;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
||||
return lower == "true" || lower == "1" || lower == "on" || lower == "yes";
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string BoolType::serialize(const bool& value) const {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string BoolType::to_string(const bool& value) const {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
// FloatType
|
||||
template <>
|
||||
float FloatType::parse(const std::string& value) const {
|
||||
return std::stof(value);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string FloatType::serialize(const float& value) const {
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string FloatType::to_string(const float& value) const {
|
||||
return std::to_string(value);
|
||||
}
|
||||
|
||||
// IPAddressType
|
||||
IPAddressType::IPAddressType()
|
||||
: AbstractType("ip_address") {}
|
||||
|
||||
std::any IPAddressType::parse(const std::string& value) const {
|
||||
std::regex ip_regex(R"((\d+)\.(\d+)\.(\d+)\.(\d+))");
|
||||
std::smatch match;
|
||||
|
||||
if (!std::regex_match(value, match, ip_regex)) {
|
||||
throw std::invalid_argument("Invalid IP address: " + value);
|
||||
}
|
||||
|
||||
IPAddress ip;
|
||||
for (int i = 1; i <= 4; ++i) {
|
||||
int octet = std::stoi(match[i]);
|
||||
if (octet < 0 || octet > 255) {
|
||||
throw std::invalid_argument("Invalid octet in IP address: " + value);
|
||||
}
|
||||
ip.octets[i-1] = octet;
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
std::string IPAddressType::serialize(const std::any& value) const {
|
||||
const IPAddress& ip = std::any_cast<const IPAddress&>(value);
|
||||
std::ostringstream oss;
|
||||
oss << (int)ip.octets[0] << "." << (int)ip.octets[1] << "." << (int)ip.octets[2] << "." << (int)ip.octets[3];
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string IPAddressType::to_string(const std::any& value) const {
|
||||
return serialize(value);
|
||||
}
|
||||
|
||||
// DateTimeRangeType
|
||||
DateTimeRangeType::DateTimeRangeType()
|
||||
: AbstractType("datetime_range") {}
|
||||
|
||||
std::any DateTimeRangeType::parse(const std::string& value) const {
|
||||
// Simple implementation for demo
|
||||
size_t dash_pos = value.find("-");
|
||||
if (dash_pos == std::string::npos) {
|
||||
throw std::invalid_argument("Invalid date range: " + value);
|
||||
}
|
||||
|
||||
DateTimeRange range;
|
||||
range.start = value.substr(0, dash_pos);
|
||||
range.end = value.substr(dash_pos + 1);
|
||||
|
||||
// Validate dates (simplified)
|
||||
// In real implementation, would use std::chrono or similar
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
std::string DateTimeRangeType::serialize(const std::any& value) const {
|
||||
const DateTimeRange& range = std::any_cast<const DateTimeRange&>(value);
|
||||
return range.start + "-" + range.end;
|
||||
}
|
||||
|
||||
std::string DateTimeRangeType::to_string(const std::any& value) const {
|
||||
return serialize(value);
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Type Manager Implementation
|
||||
// ============================
|
||||
|
||||
TypeManager& TypeManager::instance() {
|
||||
static TypeManager manager;
|
||||
return manager;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TypeManager::register_type(const std::shared_ptr<AbstractType>& type) {
|
||||
m_types[typeid(T).hash_code()] = type;
|
||||
m_type_names[type->name()] = type;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<AbstractType> TypeManager::get_type() const {
|
||||
auto it = m_types.find(typeid(T).hash_code());
|
||||
if (it != m_types.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<AbstractType> TypeManager::get_type_by_name(const std::string& name) const {
|
||||
auto it = m_type_names.find(name);
|
||||
if (it != m_type_names.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Custom type parsers for cxxopts
|
||||
// ============================
|
||||
|
||||
bool parse_value(const std::string& text, IPAddress& value) {
|
||||
IPAddressType type;
|
||||
try {
|
||||
value = std::any_cast<IPAddress>(type.parse(text));
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_value(const std::string& text, std::vector<IPAddress>& value) {
|
||||
IPAddressType type;
|
||||
|
||||
// Split by comma
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
std::string copy = text;
|
||||
|
||||
while ((pos = copy.find(',')) != std::string::npos) {
|
||||
token = copy.substr(0, pos);
|
||||
try {
|
||||
value.push_back(std::any_cast<IPAddress>(type.parse(token)));
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
copy.erase(0, pos + 1);
|
||||
}
|
||||
|
||||
if (!copy.empty()) {
|
||||
try {
|
||||
value.push_back(std::any_cast<IPAddress>(type.parse(copy)));
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_value(const std::string& text, DateTimeRange& value) {
|
||||
DateTimeRangeType type;
|
||||
try {
|
||||
value = std::any_cast<DateTimeRange>(type.parse(text));
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_value(const std::string& text, std::vector<DateTimeRange>& value) {
|
||||
DateTimeRangeType type;
|
||||
|
||||
// Split by comma
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
std::string copy = text;
|
||||
|
||||
while ((pos = copy.find(',')) != std::string::npos) {
|
||||
token = copy.substr(0, pos);
|
||||
try {
|
||||
value.push_back(std::any_cast<DateTimeRange>(type.parse(token)));
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
copy.erase(0, pos + 1);
|
||||
}
|
||||
|
||||
if (!copy.empty()) {
|
||||
try {
|
||||
value.push_back(std::any_cast<DateTimeRange>(type.parse(copy)));
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Collection Parser
|
||||
// ============================
|
||||
|
||||
template <typename T>
|
||||
bool CollectionParser<T>::parse(const std::string& value, char delimiter, std::vector<T>& result) {
|
||||
result.clear();
|
||||
|
||||
std::stringstream ss(value);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(ss, token, delimiter)) {
|
||||
T parsed_value;
|
||||
if (!parse_value(token, parsed_value)) {
|
||||
return false;
|
||||
}
|
||||
result.push_back(std::move(parsed_value));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace cxxoptspp
|
||||
Loading…
Reference in New Issue