Unified Configuration Management for C++
A minimal, flexible configuration management library that provides a unified, predictable way to manage application configuration across multiple sources. This is the C++ port of confy (Python), designed for 100% behavioral parity.
- Layered Precedence β Well-defined override order: defaults β file β .env β environment β overrides
- Dot-Notation Access β Intuitive nested access via
cfg.get("database.host") - Multiple Formats β Native support for JSON and TOML configuration files
- Environment Integration β Seamless override via environment variables with prefix filtering
- Smart Env Mapping β Automatic underscore transformation (
DATABASE_HOSTβdatabase.host) - Validation β Mandatory key enforcement with actionable error messages
- CLI Tool β Command-line inspection, mutation, and format conversion
- Cross-Platform β Linux, macOS, Windows (x86_64 and ARM64)
- Modern C++17 β Clean, well-documented codebase
- C++17 compiler: GCC 11+, Clang 14+, MSVC 2022, or Apple Clang 14+
- CMake 3.20+
# Clone the repository
git clone https://github.com/araray/confy-cpp.git
cd confy-cpp
# Create build directory
mkdir build && cd build
# Configure (dependencies are fetched automatically)
cmake -DCMAKE_BUILD_TYPE=Release ..
# Build
cmake --build . -j$(nproc)
# Run tests
ctest --output-on-failure
# (Optional) Install
sudo cmake --install .All dependencies are automatically fetched via CMake's FetchContent:
| Library | Version | Purpose |
|---|---|---|
| nlohmann/json | 3.11.3 | JSON parsing and value model |
| toml++ | 3.4.0 | TOML parsing |
| cxxopts | 3.2.0 | CLI argument parsing |
| GoogleTest | 1.14.0 | Testing framework |
#include <confy/Config.hpp>
#include <iostream>
int main() {
// Define loading options
confy::LoadOptions opts;
// Set defaults (lowest precedence)
opts.defaults = {
{"database", {
{"host", "localhost"},
{"port", 5432}
}},
{"logging", {
{"level", "INFO"}
}}
};
// Load from TOML file
opts.file_path = "config.toml";
// Enable environment variable overrides with prefix
opts.prefix = "MYAPP"; // Matches MYAPP_DATABASE_HOST, etc.
// Load .env file (default: true)
opts.load_dotenv_file = true;
// Require certain keys to exist
opts.mandatory = {"database.host"};
// Load configuration
try {
confy::Config cfg = confy::Config::load(opts);
// Access values with dot-notation
std::string host = cfg.get<std::string>("database.host", "localhost");
int port = cfg.get<int>("database.port", 5432);
std::cout << "Connecting to " << host << ":" << port << std::endl;
// Check if key exists
if (cfg.contains("logging.file")) {
std::string logfile = cfg.get<std::string>("logging.file");
std::cout << "Logging to: " << logfile << std::endl;
}
// Serialize to JSON or TOML
std::cout << cfg.to_json(2) << std::endl;
} catch (const confy::MissingMandatoryConfig& e) {
std::cerr << "Missing required config: " << e.what() << std::endl;
return 1;
} catch (const confy::FileNotFoundError& e) {
std::cerr << "Config file not found: " << e.path() << std::endl;
return 1;
}
return 0;
}# Get a value
confy-cpp -c config.toml get database.host
# Set a value (modifies file in-place)
confy-cpp -c config.toml set database.port 5433
# Check if key exists (exit code 0 = exists, 1 = missing)
confy-cpp -c config.toml exists database.ssl.enabled
# Search for keys/values
confy-cpp -c config.toml search --key "database.*"
confy-cpp -c config.toml search --val "localhost" -i
# Dump entire config as JSON
confy-cpp -c config.toml dump
# Convert between formats
confy-cpp -c config.toml convert --to json --out config.json
# Use environment variable prefix
confy-cpp -c config.toml -p MYAPP dump
# Specify overrides
confy-cpp -c config.toml --overrides "database.port:5433,debug:true" dumpConfiguration is loaded from multiple sources in this order (later sources override earlier):
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. DEFAULTS (lowest precedence) β
β Hardcoded fallback values in your application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 2. CONFIG FILE β
β JSON or TOML file (auto-detected by extension) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 3. .ENV FILE β
β Loaded into environment (does NOT override existing) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 4. ENVIRONMENT VARIABLES β
β Filtered by prefix, transformed with underscore rules β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 5. OVERRIDES (highest precedence) β
β Explicit overrides passed to Config::load() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Environment variables are transformed to dot-paths using these rules:
| Environment Variable | Transformed Path | Rule |
|---|---|---|
MYAPP_DATABASE_HOST |
database.host |
Single _ β . |
MYAPP_FEATURE_FLAGS__BETA |
feature_flags.beta |
Double __ β single _ |
MYAPP_A__B__C_D |
a_b_c.d |
Combined rules |
Prefix Filtering:
prefix = "MYAPP"β OnlyMYAPP_*variables are consideredprefix = ""β Most variables included (120+ system prefixes excluded)prefix = std::nulloptβ Environment loading disabled entirely
struct LoadOptions {
std::string file_path; // Config file path (empty = none)
std::optional<std::string> prefix = std::nullopt; // Env var prefix
bool load_dotenv_file = true; // Load .env file
std::string dotenv_path; // Explicit .env path
Value defaults = Value::object(); // Default values
std::unordered_map<std::string, Value> overrides; // Final overrides
std::vector<std::string> mandatory; // Required keys
};class Config {
public:
// Load from multiple sources
static Config load(const LoadOptions& opts);
// Value access (dot-notation)
template<typename T>
T get(const std::string& path, const T& default_val) const;
Value get(const std::string& path) const; // Throws if missing
std::optional<Value> get_optional(const std::string& path) const;
void set(const std::string& path, const Value& value,
bool create_missing = true);
bool contains(const std::string& path) const;
// Serialization
std::string to_json(int indent = 2) const;
std::string to_toml() const;
// Raw data access
const Value& data() const;
Value& data();
// Merging
void merge(const Config& other);
void merge(const Value& other);
};| Exception | When Thrown |
|---|---|
ConfigError |
Base class for all confy exceptions |
MissingMandatoryConfig |
Required keys missing after merge |
FileNotFoundError |
Config file doesn't exist |
ConfigParseError |
JSON/TOML syntax error |
KeyError |
Dot-path segment not found (strict get) |
TypeError |
Traversal into non-container type |
confy-cpp/
βββ CMakeLists.txt # Build configuration
βββ README.md # This file
βββ ROADMAP.md # Development plan
β
βββ include/confy/ # Public API headers
β βββ Config.hpp # Main configuration class
β βββ DotPath.hpp # Dot-path utilities
β βββ EnvMapper.hpp # Environment variable mapping
β βββ Errors.hpp # Exception types
β βββ Loader.hpp # File loading (JSON/TOML/.env)
β βββ Merge.hpp # Deep merge utilities
β βββ Parse.hpp # Type parsing
β βββ Value.hpp # Value type (nlohmann::json wrapper)
β
βββ src/ # Implementation
β βββ Config.cpp
β βββ DotPath.cpp
β βββ EnvMapper.cpp
β βββ Loader.cpp
β βββ Merge.cpp
β βββ Parse.cpp
β βββ Util.cpp
β βββ cli_main.cpp # CLI tool entry point
β
βββ tests/ # Test suite (GoogleTest)
βββ test_main.cpp
βββ test_dotpath.cpp # 200+ tests
βββ test_parse.cpp # 150+ tests
βββ test_merge.cpp # 100+ tests
βββ test_env_mapper.cpp
βββ test_loader.cpp
βββ test_config.cpp
βββ test_cli.cpp
The test suite contains 1000+ test cases covering all behavioral rules from the design specification.
# Run all tests
cd build
ctest --output-on-failure
# Run with verbose output
ctest -V
# Run specific test
./confy_tests --gtest_filter="ConfigPrecedence.*"
# List all tests
./confy_tests --gtest_list_testsTo verify C++ implementation matches Python behavior:
# Generate golden test files from Python
python -m confy.golden_generator tests/golden/
# Run C++ against golden files
./confy_tests --gtest_filter="GoldenParity.*"{
"database": {
"host": "localhost",
"port": 5432,
"ssl": {
"enabled": true,
"cert_path": "/etc/ssl/certs/db.pem"
}
},
"logging": {
"level": "INFO",
"handlers": ["console", "file"]
},
"feature_flags": {
"beta_features": false
}
}[database]
host = "localhost"
port = 5432
[database.ssl]
enabled = true
cert_path = "/etc/ssl/certs/db.pem"
[logging]
level = "INFO"
handlers = ["console", "file"]
[feature_flags]
beta_features = false# Database overrides
MYAPP_DATABASE_HOST=db.production.example.com
MYAPP_DATABASE_PORT=5433
# Feature flags (double underscore preserves underscore in key)
MYAPP_FEATURE_FLAGS__BETA_FEATURES=true
# Logging
MYAPP_LOGGING_LEVEL=DEBUG| Phase | Status | Description |
|---|---|---|
| Phase 1 | β Complete | Core infrastructure (Errors, Value, DotPath, Parse, Merge) |
| Phase 2 | β Complete | Source loaders (EnvMapper, Loader) |
| Phase 3 | β Complete | Config class with full precedence |
| Phase 4 | β Complete | CLI tool (get, set, exists, search, dump, convert) |
| Phase 5 | π‘ Partial | Polish & release (docs, CI/CD, packaging) |
All 27 rules from CONFY_DESIGN_SPECIFICATION.md:
- D1-D6: Dot-path access semantics
- E1-E7: Environment variable mapping
- F1-F8: File format behavior
- M1-M3: Mandatory key validation
- P1-P4: Precedence ordering
- T1-T7: Type parsing rules
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow existing code style and patterns
- Add tests for new functionality
- Ensure all tests pass (
ctest --output-on-failure) - Commit with clear messages (
git commit -m 'Add amazing feature') - Push to your branch (
git push origin feature/amazing-feature) - Open a Pull Request
- C++17 standard
- 4-space indentation
snake_casefor functions and variablesPascalCasefor classes and types- Comprehensive documentation (Doxygen-style comments)
This project is licensed under the MIT License - see the LICENSE file for details.
- Python Implementation: github.com/araray/confy
- Design Specification: CONFY_DESIGN_SPECIFICATION.md
- Development Roadmap: ROADMAP.md
- nlohmann/json β JSON for Modern C++
- toml++ β TOML parser for C++17
- cxxopts β Lightweight C++ option parser
- GoogleTest β Google Testing Framework
Configuration should be simple to define, predictable in behavior, and consistent across language boundaries.