feat(CLI): CLI integration with CLI11
libconfig can automatically generate command line interfaces using CLI11 based on some schema
This commit is contained in:
2
build-config/CLI11/meson.build
Normal file
2
build-config/CLI11/meson.build
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
cli11_proj = subproject('cli11')
|
||||||
|
cli11_dep = cli11_proj.get_variable('CLI11_dep')
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
cmake = import('cmake')
|
cmake = import('cmake')
|
||||||
subdir('reflect-cpp')
|
subdir('reflect-cpp')
|
||||||
|
subdir('cli11')
|
||||||
|
|||||||
33
examples/cli_example.cpp
Normal file
33
examples/cli_example.cpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#include "CLI/CLI.hpp"
|
||||||
|
#include "fourdst/config/config.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <print>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
struct SubConfig{
|
||||||
|
int a = 0;
|
||||||
|
double b = 1.0;
|
||||||
|
std::string c = "default";
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MainConfig {
|
||||||
|
std::string name = "example";
|
||||||
|
SubConfig subconfig;
|
||||||
|
double value = 3.14;
|
||||||
|
std::optional<std::string> help = std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int main(const int argc, char** argv) {
|
||||||
|
fourdst::config::Config<MainConfig> config;
|
||||||
|
CLI::App app("Example CLI Application with Config");
|
||||||
|
|
||||||
|
fourdst::config::register_as_cli(config, app, "cfg");
|
||||||
|
|
||||||
|
app.parse(argc, argv);
|
||||||
|
|
||||||
|
std::println("Configuration: \n{}", config);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
executable('simple_config_test', 'simple.cpp', dependencies: [config_dep])
|
executable('simple_config_test', 'simple.cpp', dependencies: [config_dep])
|
||||||
|
executable('cli_example', 'cli_example.cpp', dependencies: [config_dep, cli11_dep])
|
||||||
|
|
||||||
if meson.is_cross_build() and host_machine.system() == 'wasm'
|
if meson.is_cross_build() and host_machine.system() == 'wasm'
|
||||||
executable('simple_config_wasm_test', 'wasm.cpp', dependencies: [config_dep])
|
executable('simple_config_wasm_test', 'wasm.cpp', dependencies: [config_dep])
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
#
|
#
|
||||||
# *********************************************************************** #
|
# *********************************************************************** #
|
||||||
project('libconfig', ['cpp', 'c'], version: 'v2.0.5', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
|
project('libconfig', ['cpp', 'c'], version: 'v2.1.0', default_options: ['cpp_std=c++23'], meson_version: '>=1.5.0')
|
||||||
|
|
||||||
# Add default visibility for all C++ targets
|
# Add default visibility for all C++ targets
|
||||||
add_project_arguments('-fvisibility=default', language: 'cpp')
|
add_project_arguments('-fvisibility=default', language: 'cpp')
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @file base.h
|
||||||
|
* @brief Core configuration management classes and concepts.
|
||||||
|
*
|
||||||
|
* This file defines the `Config` template class which serves as the primary interface
|
||||||
|
* for managing typed configuration structures. It handles serialization (save), deserialization (load),
|
||||||
|
* and schema generation using the `reflect-cpp` library.
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
|
||||||
#include <format>
|
#include <format>
|
||||||
|
#include <vector>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "fourdst/config/exceptions/exceptions.h"
|
#include "fourdst/config/exceptions/exceptions.h"
|
||||||
|
|
||||||
@@ -11,24 +21,146 @@
|
|||||||
#include "rfl/toml.hpp"
|
#include "rfl/toml.hpp"
|
||||||
#include "rfl/json.hpp"
|
#include "rfl/json.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace fourdst::config {
|
namespace fourdst::config {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concept ensuring a type is suitable for configuration schema.
|
||||||
|
*
|
||||||
|
* A valid configuration schema must be:
|
||||||
|
* - A class or struct (`std::is_class_v`)
|
||||||
|
* - An aggregate type (`std::is_aggregate_v`), i.e., strict POD-like structure without user-declared constructors.
|
||||||
|
* - Not a `std::string`.
|
||||||
|
*
|
||||||
|
* @tparam T The type to check.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
concept IsConfigSchema =
|
||||||
|
std::is_class_v<std::decay_t<T>> && // Must be a class/struct
|
||||||
|
std::is_aggregate_v<std::decay_t<T>> && // Must be an aggregate (POD-like)
|
||||||
|
!std::same_as<std::decay_t<T>, std::string>; // Explicitly exclude strings
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Policies for handling the root name during configuration loading.
|
||||||
|
*/
|
||||||
enum class RootNameLoadPolicy {
|
enum class RootNameLoadPolicy {
|
||||||
|
/**
|
||||||
|
* @brief Updates the internal root name to match what is found in the file.
|
||||||
|
*/
|
||||||
FROM_FILE,
|
FROM_FILE,
|
||||||
|
/**
|
||||||
|
* @brief Enforces the current internal root name; loading fails if the file's root name differs.
|
||||||
|
*/
|
||||||
KEEP_CURRENT
|
KEEP_CURRENT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents the current state of a Config object.
|
||||||
|
*/
|
||||||
enum class ConfigState {
|
enum class ConfigState {
|
||||||
|
/**
|
||||||
|
* @brief Configuration contains default values and has not been loaded from a file.
|
||||||
|
*/
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
/**
|
||||||
|
* @brief Configuration has been successfully populated from a file.
|
||||||
|
*/
|
||||||
LOADED_FROM_FILE
|
LOADED_FROM_FILE
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wrapper class for managing strongly-typed configuration structures.
|
||||||
|
*
|
||||||
|
* The `Config` class wraps a user-defined aggregate struct `T` and provides methods
|
||||||
|
* to save/load it to/from TOML files, as well as generate JSON schemas.
|
||||||
|
*
|
||||||
|
* It uses `reflect-cpp` to automatically inspect the fields of `T`.
|
||||||
|
*
|
||||||
|
* @tparam T The configuration structure type. Must satisfy `IsConfigSchema`.
|
||||||
|
*
|
||||||
|
* @par Examples
|
||||||
|
* Defining a config struct and using `Config`:
|
||||||
|
* @code
|
||||||
|
* #include "fourdst/config/config.h"
|
||||||
|
*
|
||||||
|
* struct MySettings {
|
||||||
|
* int threads = 4;
|
||||||
|
* double timeout = 30.5;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* int main() {
|
||||||
|
* fourdst::config::Config<MySettings> cfg;
|
||||||
|
*
|
||||||
|
* // Access values (default)
|
||||||
|
* std::cout << "Threads: " << cfg->threads << "\n";
|
||||||
|
*
|
||||||
|
* // Save default config
|
||||||
|
* cfg.save("settings.toml");
|
||||||
|
*
|
||||||
|
* // Load from file
|
||||||
|
* cfg.load("settings.toml");
|
||||||
|
*
|
||||||
|
* // Save JSON Schema for editors
|
||||||
|
* cfg.save_schema("schema.json");
|
||||||
|
*
|
||||||
|
* return 0;
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
template <IsConfigSchema T>
|
||||||
class Config {
|
class Config {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Default constructor. Initializes the inner content with default values.
|
||||||
|
*/
|
||||||
Config() = default;
|
Config() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Access member of the underlying configuration struct.
|
||||||
|
* @return Pointer to the constant configuration content.
|
||||||
|
*/
|
||||||
const T* operator->() const { return &m_content; }
|
const T* operator->() const { return &m_content; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a mutable pointer to the configuration content.
|
||||||
|
* @return Pointer to the mutable configuration content.
|
||||||
|
*/
|
||||||
|
T* write() const { return &m_content; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dereference operator to access the underlying configuration struct.
|
||||||
|
* @return Reference to the mutable configuration content.
|
||||||
|
*/
|
||||||
|
T& operator*() { return m_content; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dereference operator to access the underlying configuration struct.
|
||||||
|
* @return Reference to the constant configuration content.
|
||||||
|
*/
|
||||||
|
const T& operator*() const { return m_content; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Explicit accessor for the main configuration content.
|
||||||
|
* @return Reference to the constant configuration content.
|
||||||
|
*/
|
||||||
const T& main() const { return m_content; }
|
const T& main() const { return m_content; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Saves the current configuration to a TOML file.
|
||||||
|
*
|
||||||
|
* Wraps the configuration content under the current root name (default "main")
|
||||||
|
* and writes it to the specified path.
|
||||||
|
*
|
||||||
|
* @param path The file path to write to.
|
||||||
|
* @throws exceptions::ConfigSaveError If the file cannot be opened.
|
||||||
|
*
|
||||||
|
* @par Examples
|
||||||
|
* @code
|
||||||
|
* cfg.save("config.toml");
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
void save(std::string_view path) const {
|
void save(std::string_view path) const {
|
||||||
T default_instance{};
|
T default_instance{};
|
||||||
std::unordered_map<std::string, T> wrapper;
|
std::unordered_map<std::string, T> wrapper;
|
||||||
@@ -46,23 +178,46 @@ namespace fourdst::config {
|
|||||||
ofs.close();
|
ofs.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the root name/key used in the TOML file.
|
||||||
|
*
|
||||||
|
* The default root name is "main". This name appears as the top-level table in the TOML file (e.g., `[main]`).
|
||||||
|
*
|
||||||
|
* @param name The new root name.
|
||||||
|
*/
|
||||||
void set_root_name(const std::string_view name) {
|
void set_root_name(const std::string_view name) {
|
||||||
m_root_name = name;
|
m_root_name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current root name.
|
||||||
|
* @return The root name string view.
|
||||||
|
*/
|
||||||
[[nodiscard]] std::string_view get_root_name() const {
|
[[nodiscard]] std::string_view get_root_name() const {
|
||||||
return m_root_name;
|
return m_root_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the policy for handling root name mismatches during load.
|
||||||
|
* @param policy The policy (FROM_FILE or KEEP_CURRENT).
|
||||||
|
*/
|
||||||
void set_root_name_load_policy(const RootNameLoadPolicy policy) {
|
void set_root_name_load_policy(const RootNameLoadPolicy policy) {
|
||||||
m_root_name_load_policy = policy;
|
m_root_name_load_policy = policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
RootNameLoadPolicy get_root_name_load_policy() const {
|
/**
|
||||||
|
* @brief Gets the current root name load policy.
|
||||||
|
* @return The current policy.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] RootNameLoadPolicy get_root_name_load_policy() const {
|
||||||
return m_root_name_load_policy;
|
return m_root_name_load_policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string describe_root_name_load_policy() const {
|
/**
|
||||||
|
* @brief Returns a string description of the current root name load policy.
|
||||||
|
* @return "FROM_FILE", "KEEP_CURRENT", or "UNKNOWN".
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::string describe_root_name_load_policy() const {
|
||||||
switch (m_root_name_load_policy) {
|
switch (m_root_name_load_policy) {
|
||||||
case RootNameLoadPolicy::FROM_FILE:
|
case RootNameLoadPolicy::FROM_FILE:
|
||||||
return "FROM_FILE";
|
return "FROM_FILE";
|
||||||
@@ -73,6 +228,24 @@ namespace fourdst::config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads configuration from a TOML file.
|
||||||
|
*
|
||||||
|
* Reads the file, parses it, and updates the internal configuration state.
|
||||||
|
*
|
||||||
|
* @param path The file path to read from.
|
||||||
|
* @throws exceptions::ConfigLoadError If the config is already loaded, file doesn't exist, or root name mismatch (under KEEP_CURRENT policy).
|
||||||
|
* @throws exceptions::ConfigParseError If the file content is invalid TOML or doesn't match the schema.
|
||||||
|
*
|
||||||
|
* @par Examples
|
||||||
|
* @code
|
||||||
|
* try {
|
||||||
|
* cfg.load("config.toml");
|
||||||
|
* } catch (const fourdst::config::exceptions::ConfigError& e) {
|
||||||
|
* std::cerr << "Error loading config: " << e.what() << std::endl;
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
void load(const std::string_view path) {
|
void load(const std::string_view path) {
|
||||||
if (m_state == ConfigState::LOADED_FROM_FILE) {
|
if (m_state == ConfigState::LOADED_FROM_FILE) {
|
||||||
throw exceptions::ConfigLoadError(
|
throw exceptions::ConfigLoadError(
|
||||||
@@ -111,6 +284,19 @@ namespace fourdst::config {
|
|||||||
m_state = ConfigState::LOADED_FROM_FILE;
|
m_state = ConfigState::LOADED_FROM_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generates and saves a JSON schema for the configuration structure.
|
||||||
|
*
|
||||||
|
* Useful for enabling autocompletion and validation in editors (e.g., VS Code).
|
||||||
|
*
|
||||||
|
* @param path The path to save the schema file to.
|
||||||
|
* @throws exceptions::SchemaSaveError If the file cannot be opened.
|
||||||
|
*
|
||||||
|
* @par Examples
|
||||||
|
* @code
|
||||||
|
* Config<MyConfig>::save_schema("MyConfig.schema.json");
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
static void save_schema(const std::string& path) {
|
static void save_schema(const std::string& path) {
|
||||||
using wrapper = std::unordered_map<std::string, T>;
|
using wrapper = std::unordered_map<std::string, T>;
|
||||||
const std::string json_schema = rfl::json::to_schema<wrapper>(rfl::json::pretty);
|
const std::string json_schema = rfl::json::to_schema<wrapper>(rfl::json::pretty);
|
||||||
@@ -126,8 +312,16 @@ namespace fourdst::config {
|
|||||||
ofs.close();
|
ofs.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the current state of the configuration object.
|
||||||
|
* @return The current state (DEFAULT or LOADED_FROM_FILE).
|
||||||
|
*/
|
||||||
[[nodiscard]] ConfigState get_state() const { return m_state; }
|
[[nodiscard]] ConfigState get_state() const { return m_state; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a string description of the current configuration state.
|
||||||
|
* @return "DEFAULT", "LOADED_FROM_FILE", or "UNKNOWN".
|
||||||
|
*/
|
||||||
[[nodiscard]] std::string describe_state() const {
|
[[nodiscard]] std::string describe_state() const {
|
||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
case ConfigState::DEFAULT:
|
case ConfigState::DEFAULT:
|
||||||
@@ -147,16 +341,31 @@ namespace fourdst::config {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Formatter specialization for Config<T> to allow easy printing.
|
||||||
|
*
|
||||||
|
* This allows a Config object to be directly formatted/printed (e.g., via std::print or std::format).
|
||||||
|
* It outputs the configuration in its TOML representation.
|
||||||
|
*
|
||||||
|
* @par Example
|
||||||
|
* @code
|
||||||
|
* std::println("Current Setup:\n{}", config);
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
template <typename T, typename CharT>
|
template <typename T, typename CharT>
|
||||||
struct std::formatter<fourdst::config::Config<T>, CharT> {
|
struct std::formatter<fourdst::config::Config<T>, CharT> {
|
||||||
|
|
||||||
static constexpr auto parse(auto& ctx) { return ctx.begin(); }
|
static constexpr auto parse(auto& ctx) { return ctx.begin(); }
|
||||||
|
|
||||||
auto format(const fourdst::config::Config<T>& config, auto& ctx) const {
|
auto format(const fourdst::config::Config<T>& config, auto& ctx) const {
|
||||||
const T& inner_value = config.main();
|
// Create a wrapper map to preserve the root name in the output
|
||||||
std::map<std::string, T> wrapper;
|
std::map<std::string, T> wrapper;
|
||||||
wrapper[std::string(config.get_root_name())] = inner_value;
|
wrapper[std::string(config.get_root_name())] = config.main();
|
||||||
std::string buffer;
|
|
||||||
return buffer;
|
// Serialize to TOML using reflect-cpp
|
||||||
|
const std::string toml_string = rfl::toml::write(wrapper);
|
||||||
|
|
||||||
|
// Write to the formatter output
|
||||||
|
return std::format_to(ctx.out(), "{}", toml_string);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
144
src/config/include/fourdst/config/cli.h
Normal file
144
src/config/include/fourdst/config/cli.h
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* @file cli.h
|
||||||
|
* @brief Integration layer between libconfig and CLI applications.
|
||||||
|
*
|
||||||
|
* This file contains utilities for automatically mapping C++ configuration structures
|
||||||
|
* to command-line arguments, primarily supporting the CLI11 library.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include "fourdst/config/base.h"
|
||||||
|
#include "rfl.hpp"
|
||||||
|
|
||||||
|
namespace fourdst::config {
|
||||||
|
template <typename T>
|
||||||
|
struct InspectType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concept that defines the requirements for a CLI application class.
|
||||||
|
*
|
||||||
|
* This concept ensures that the CLI application class `T` has an `add_option` member function
|
||||||
|
* compatible with the signature expected by `register_as_cli`. It is satisfied by `CLI::App` from CLI11.
|
||||||
|
*
|
||||||
|
* @tparam T The type to check against the concept.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
concept IsCLIApp = requires(T app, std::string name, std::string description)
|
||||||
|
{
|
||||||
|
{app.add_option(name, std::declval<int&>(), description)};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type trait to determine if a type is a Config wrapper.
|
||||||
|
*
|
||||||
|
* This is the base case which inherits from `std::false_type`.
|
||||||
|
*
|
||||||
|
* @tparam T The type to inspect.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
struct is_config_wrapper : std::false_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Specialization of `is_config_wrapper` for `Config<T>`.
|
||||||
|
*
|
||||||
|
* This specialization inherits from `std::true_type`, identifying instances of the `Config` class.
|
||||||
|
*
|
||||||
|
* @tparam T The underlying configuration struct type.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
struct is_config_wrapper<Config<T>> : std::true_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Registers configuration structure fields as CLI options.
|
||||||
|
*
|
||||||
|
* This function iterates over the members of the provided configuration object using reflection
|
||||||
|
* and registers each member as a command-line option in the provided CLI application.
|
||||||
|
*
|
||||||
|
* If the configuration object contains nested structures, field names are flattened using dot notation
|
||||||
|
* (e.g., `parent.child.field`).
|
||||||
|
*
|
||||||
|
* If `T` is a `Config<U>` wrapper, it automatically unwraps the inner value and adds a footer note
|
||||||
|
* to the CLI application's help message indicating that options were auto-generated.
|
||||||
|
*
|
||||||
|
* @tparam T The type of the configuration object. Can be a raw struct or a `Config<Struct>` wrapper.
|
||||||
|
* @tparam CliApp The type of the CLI application object. Must satisfy the `IsCLIApp` concept (e.g., `CLI::App`).
|
||||||
|
* @param config The configuration object to register.
|
||||||
|
* @param app The CLI application instance to add options to.
|
||||||
|
* @param prefix Optional prefix for option names. Used internally for recursion; usually omitted by the caller.
|
||||||
|
*
|
||||||
|
* @par Examples
|
||||||
|
* Basic usage with CLI11:
|
||||||
|
* @code
|
||||||
|
* #include "CLI/CLI.hpp"
|
||||||
|
* #include "fourdst/config/config.h"
|
||||||
|
*
|
||||||
|
* struct MyOptions {
|
||||||
|
* int verbosity = 0;
|
||||||
|
* std::string input_file = "data.txt";
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* int main(int argc, char** argv) {
|
||||||
|
* fourdst::config::Config<MyOptions> cfg;
|
||||||
|
* CLI::App app{"My Application"};
|
||||||
|
*
|
||||||
|
* // Automatically adds flags: --verbosity, --input_file
|
||||||
|
* fourdst::config::register_as_cli(cfg, app);
|
||||||
|
*
|
||||||
|
* CLI11_PARSE(app, argc, argv);
|
||||||
|
*
|
||||||
|
* // cfg is now populated with values from CLI
|
||||||
|
* return 0;
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* Nested structures:
|
||||||
|
* @code
|
||||||
|
* struct Server {
|
||||||
|
* int port = 8080;
|
||||||
|
* std::string host = "localhost";
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* struct AppConfig {
|
||||||
|
* Server server;
|
||||||
|
* bool dry_run = false;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* // In main...
|
||||||
|
* fourdst::config::Config<AppConfig> cfg;
|
||||||
|
* // Registers: --server.port, --server.host, --dry_run
|
||||||
|
* fourdst::config::register_as_cli(cfg, app);
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
template <typename T, typename CliApp>
|
||||||
|
void register_as_cli(T& config, CliApp& app, const std::string& prefix="") {
|
||||||
|
if constexpr (is_config_wrapper<T>::value) {
|
||||||
|
app.footer("\nNOTE:\n"
|
||||||
|
"Configuration options were automatically generated from the config schema.\n"
|
||||||
|
"Use the --help flag to see all available options."
|
||||||
|
);
|
||||||
|
register_as_cli(*config, app, prefix);
|
||||||
|
} else {
|
||||||
|
auto view = rfl::to_view(config);
|
||||||
|
view.apply([&](auto f) {
|
||||||
|
auto& value = f.value();
|
||||||
|
using ValueType = std::remove_pointer_t<std::decay_t<decltype(value)>>;
|
||||||
|
|
||||||
|
const auto name = std::string(f.name());
|
||||||
|
std::string field_name = prefix.empty() ? name : prefix + "." + name;
|
||||||
|
|
||||||
|
if constexpr (IsConfigSchema<ValueType>) {
|
||||||
|
register_as_cli(*value, app, field_name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app.add_option(
|
||||||
|
"--" + field_name,
|
||||||
|
*value,
|
||||||
|
"Configuration option for " + field_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,8 +18,86 @@
|
|||||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
//
|
//
|
||||||
// *********************************************************************** */
|
// *********************************************************************** */
|
||||||
|
/**
|
||||||
|
* @file config.h
|
||||||
|
* @brief Main entry point for the fourdst::config library.
|
||||||
|
*
|
||||||
|
* This header includes all necessary components of the configuration library,
|
||||||
|
* providing a unified interface for defining, loading, saving, and integrating
|
||||||
|
* configuration structures.
|
||||||
|
*
|
||||||
|
* @section features Features
|
||||||
|
* - **Type-safe Configuration**: Define configs using standard C++ structs.
|
||||||
|
* - **Serialization**: Built-in support for TOML loading and saving via `reflect-cpp`.
|
||||||
|
* - **Schema Generation**: Generate JSON schemas for editor autocompletion (VS Code, etc.).
|
||||||
|
* - **CLI Integration**: Seamlessly expose config fields as command-line arguments (supports CLI11).
|
||||||
|
* - **Error Handling**: Comprehensive exception hierarchy for parsing and I/O errors.
|
||||||
|
*
|
||||||
|
* @par Examples
|
||||||
|
*
|
||||||
|
* **1. Basic Definition and I/O**
|
||||||
|
* @code
|
||||||
|
* #include "fourdst/config/config.h"
|
||||||
|
*
|
||||||
|
* struct Physics {
|
||||||
|
* double gravity = 9.81;
|
||||||
|
* bool enable_drag = true;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* struct AppConfig {
|
||||||
|
* std::string name = "My Simulation";
|
||||||
|
* int max_steps = 1000;
|
||||||
|
* Physics physics;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* int main() {
|
||||||
|
* fourdst::config::Config<AppConfig> cfg;
|
||||||
|
*
|
||||||
|
* // Access defaults
|
||||||
|
* if (cfg->physics.enable_drag) { ... }
|
||||||
|
*
|
||||||
|
* // Save to file
|
||||||
|
* cfg.save("config.toml");
|
||||||
|
*
|
||||||
|
* // Load from file
|
||||||
|
* cfg.load("config.toml");
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* **2. CLI Integration (CLI11)**
|
||||||
|
* @code
|
||||||
|
* #include "CLI/CLI.hpp"
|
||||||
|
* #include "fourdst/config/config.h"
|
||||||
|
*
|
||||||
|
* int main(int argc, char** argv) {
|
||||||
|
* CLI::App app("Simulation App");
|
||||||
|
* fourdst::config::Config<AppConfig> cfg;
|
||||||
|
*
|
||||||
|
* // Automatically registers flags like --name, --max_steps, --physics.gravity
|
||||||
|
* fourdst::config::register_as_cli(cfg, app);
|
||||||
|
*
|
||||||
|
* CLI11_PARSE(app, argc, argv);
|
||||||
|
*
|
||||||
|
* std::cout << "Starting simulation: " << cfg->name << "\n";
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* **3. Error Handling**
|
||||||
|
* @code
|
||||||
|
* try {
|
||||||
|
* cfg.load("missing_file.toml");
|
||||||
|
* } catch (const fourdst::config::exceptions::ConfigLoadError& e) {
|
||||||
|
* std::cerr << "Could not load config: " << e.what() << "\n";
|
||||||
|
* // Falls back to default values
|
||||||
|
* } catch (const fourdst::config::exceptions::ConfigParseError& e) {
|
||||||
|
* std::cerr << "Invalid config file format: " << e.what() << "\n";
|
||||||
|
* return 1;
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "fourdst/config/base.h"
|
#include "fourdst/config/base.h"
|
||||||
#include "fourdst/config/exceptions/exceptions.h"
|
#include "fourdst/config/exceptions/exceptions.h"
|
||||||
|
#include "fourdst/config/cli.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @file exceptions.h
|
||||||
|
* @brief Exception classes for the configuration library.
|
||||||
|
*
|
||||||
|
* This file defines the hierarchy of exceptions thrown by the `fourdst::config` library.
|
||||||
|
* All exceptions inherit from `ConfigError`, which in turn inherits from `std::exception`.
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace fourdst::config::exceptions {
|
namespace fourdst::config::exceptions {
|
||||||
|
/**
|
||||||
|
* @brief Base exception class for all configuration-related errors.
|
||||||
|
*
|
||||||
|
* Provides a standard way to catch any error originating from the configuration library.
|
||||||
|
* Stores a string message describing the error.
|
||||||
|
*/
|
||||||
class ConfigError : public std::exception {
|
class ConfigError : public std::exception {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a ConfigError with a specific message.
|
||||||
|
* @param what The error message.
|
||||||
|
*/
|
||||||
explicit ConfigError(const std::string & what): m_msg(what) {}
|
explicit ConfigError(const std::string & what): m_msg(what) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the error message.
|
||||||
|
* @return C-style string containing the error message.
|
||||||
|
*/
|
||||||
[[nodiscard]] const char* what() const noexcept override {
|
[[nodiscard]] const char* what() const noexcept override {
|
||||||
return m_msg.c_str();
|
return m_msg.c_str();
|
||||||
}
|
}
|
||||||
@@ -15,18 +36,40 @@ namespace fourdst::config::exceptions {
|
|||||||
std::string m_msg;
|
std::string m_msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thrown when saving the configuration to a file fails.
|
||||||
|
*
|
||||||
|
* This usually indicates file I/O errors (e.g., permission denied, disk full).
|
||||||
|
*/
|
||||||
class ConfigSaveError final : public ConfigError {
|
class ConfigSaveError final : public ConfigError {
|
||||||
using ConfigError::ConfigError;
|
using ConfigError::ConfigError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thrown when loading the configuration from a file fails.
|
||||||
|
*
|
||||||
|
* This can occur if the file does not exist, or if there are policy violations
|
||||||
|
* (e.g., root name mismatch when `KEEP_CURRENT` is set).
|
||||||
|
*/
|
||||||
class ConfigLoadError final : public ConfigError {
|
class ConfigLoadError final : public ConfigError {
|
||||||
using ConfigError::ConfigError;
|
using ConfigError::ConfigError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thrown when parsing the configuration file fails.
|
||||||
|
*
|
||||||
|
* This indicates that the file exists but contains invalid TOML syntax or
|
||||||
|
* data that does not match the expected schema type.
|
||||||
|
*/
|
||||||
class ConfigParseError final : public ConfigError {
|
class ConfigParseError final : public ConfigError {
|
||||||
using ConfigError::ConfigError;
|
using ConfigError::ConfigError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thrown when generating or saving the JSON schema fails.
|
||||||
|
*
|
||||||
|
* This typically indicates file I/O errors when writing the schema file.
|
||||||
|
*/
|
||||||
class SchemaSaveError final : public ConfigError {
|
class SchemaSaveError final : public ConfigError {
|
||||||
using ConfigError::ConfigError;
|
using ConfigError::ConfigError;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ config_headers = files(
|
|||||||
'include/fourdst/config/config.h',
|
'include/fourdst/config/config.h',
|
||||||
'include/fourdst/config/exceptions/exceptions.h',
|
'include/fourdst/config/exceptions/exceptions.h',
|
||||||
'include/fourdst/config/base.h',
|
'include/fourdst/config/base.h',
|
||||||
|
'include/fourdst/config/cli.h'
|
||||||
)
|
)
|
||||||
install_headers(config_headers, subdir : 'fourdst/fourdst/config')
|
install_headers(config_headers, subdir : 'fourdst/fourdst/config')
|
||||||
|
|||||||
10
subprojects/cli11.wrap
Normal file
10
subprojects/cli11.wrap
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[wrap-file]
|
||||||
|
directory = CLI11-2.6.1
|
||||||
|
source_url = https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.6.1.tar.gz
|
||||||
|
source_filename = CLI11-2.6.1.tar.gz
|
||||||
|
source_hash = 377691f3fac2b340f12a2f79f523c780564578ba3d6eaf5238e9f35895d5ba95
|
||||||
|
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/cli11_2.6.1-1/CLI11-2.6.1.tar.gz
|
||||||
|
wrapdb_version = 2.6.1-1
|
||||||
|
|
||||||
|
[provide]
|
||||||
|
dependency_names = CLI11
|
||||||
Reference in New Issue
Block a user