Skip to content

Minimal C++ configuration library with dot-notation, JSON/TOML, env-var and dict overrides. This is a cpp version of https://github.com/araray/confy

License

Notifications You must be signed in to change notification settings

araray/confy-cpp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

confy-cpp

Unified Configuration Management for C++

C++17 License: MIT Platform

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.


✨ Features

  • 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

πŸ“¦ Installation

Prerequisites

  • C++17 compiler: GCC 11+, Clang 14+, MSVC 2022, or Apple Clang 14+
  • CMake 3.20+

Build from Source

# 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 .

Dependencies

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

πŸš€ Quick Start

Library Usage

#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;
}

CLI Usage

# 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" dump

πŸ“‹ Configuration Sources & Precedence

Configuration 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 Variable Mapping

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" β†’ Only MYAPP_* variables are considered
  • prefix = "" β†’ Most variables included (120+ system prefixes excluded)
  • prefix = std::nullopt β†’ Environment loading disabled entirely

πŸ“– API Reference

LoadOptions

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
};

Config Class

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 Types

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

πŸ“ Project Structure

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

πŸ§ͺ Testing

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_tests

Parity Testing

To 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.*"

πŸ”§ Configuration Examples

JSON Configuration

{
  "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
  }
}

TOML Configuration

[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

.env File

# 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

πŸ—οΈ Development Status

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)

Behavioral Rules Implemented

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

🀝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Follow existing code style and patterns
  4. Add tests for new functionality
  5. Ensure all tests pass (ctest --output-on-failure)
  6. Commit with clear messages (git commit -m 'Add amazing feature')
  7. Push to your branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Code Style

  • C++17 standard
  • 4-space indentation
  • snake_case for functions and variables
  • PascalCase for classes and types
  • Comprehensive documentation (Doxygen-style comments)

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


πŸ”— References

Dependencies Documentation


Configuration should be simple to define, predictable in behavior, and consistent across language boundaries.

About

Minimal C++ configuration library with dot-notation, JSON/TOML, env-var and dict overrides. This is a cpp version of https://github.com/araray/confy

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published