CF (Configuration File) support for the D Programming Language
Find a file
2026-04-23 19:07:59 +01:00
demo Trailing EOL 2026-04-15 13:15:54 +01:00
doc Fully working, idependent CF reader/writer in D 2026-04-10 20:56:48 +01:00
files Unicode identifiers 2026-04-10 21:13:38 +01:00
src/dex/cf Now we properly handle tripple single and double quotes 2026-04-23 19:07:59 +01:00
.editorconfig Fully working, idependent CF reader/writer in D 2026-04-10 20:56:48 +01:00
.gitignore Fully working, idependent CF reader/writer in D 2026-04-10 20:56:48 +01:00
CODE_STYLE.md Fully working, idependent CF reader/writer in D 2026-04-10 20:56:48 +01:00
dub.sdl Updated description 2026-04-10 21:15:30 +01:00
LICENSE Fully working, idependent CF reader/writer in D 2026-04-10 20:56:48 +01:00
README.md Installation 2026-04-10 21:34:08 +01:00
RULES.md Fully working, idependent CF reader/writer in D 2026-04-10 20:56:48 +01:00

dex-cf

A D language implementation of the CF (Configuration File) v1.0 specification.

CF is a modern, human-friendly configuration file format that combines the best features of JSON, YAML, TOML, and HCL while maintaining simplicity and unambiguous parsing.

Features

  • Full CF v1.0 Compliance — Implements the complete CF specification
  • Idiomatic D API — Natural [] chaining, type-safe .as!T conversion
  • All Value Types — Strings, integers, floats, booleans, null, dates, times, datetimes
  • Multiple Number Formats — Decimal, hex (0xFF), octal (0o77), binary (0b1010)
  • Flexible Strings — Double/single quoted, triple-quoted multiline, raw strings
  • Environment Variables${VAR}, ${VAR:-default}, ${VAR:?error}
  • Include Directives — Modular configuration with include "file.cf"
  • Comments — Hash (#), double-slash (//), and block (/* */) styles
  • Roundtrip Preservation — Document model preserves formatting and comments
  • Safe by Default — Null-safe access, nesting depth limits, include path validation

Installation

Add to your dub.sdl:

dependency "dex-cf" version="~>1.0.3"

Or dub.json:

"dependencies": {
    "dex-cf": "~>1.0.3"
}

Quick Start

import dex.cf;
import std.stdio;

void main() {
    auto doc = parseCFDocument(`
        app {
            name = "MyApp"
            version = "1.0.0"
            
            server {
                host = "localhost"
                port = 8080
            }
            
            features = ["auth", "api", "cache"]
        }
    `);

    // Natural [] chaining
    writeln(doc["app"]["name"].as!string);           // "MyApp"
    writeln(doc["app"]["server"]["port"].as!long);   // 8080
    
    // Array access
    writeln(doc["app"]["features"][0].as!string);    // "auth"
    writeln(doc["app"]["features"].length);          // 3
    
    // Safe access - missing keys don't crash
    writeln(doc["missing"]["key"].isValid);          // false
    writeln(doc["app"].getOr!long("timeout", 30));   // 30 (default)
}

This example is available as a runnable file: demo/quick_start.d

dub run --single demo/quick_start.d

Running the Full Demo

dub run --single demo/example.d

This comprehensive demo showcases all API features including nested access, arrays, temporal types, safe access patterns, and type checking.

API Overview

Parsing

// Parse from string
auto doc = parseCFDocument(source);
auto doc = parseCFDocument(source, "filename.cf");

// Parse from file
auto doc = parseCFDocumentFile("config.cf");

// Parse with environment variable substitution
auto doc = parseCFDocumentWithEnv(source);
auto doc = parseCFDocumentFileWithEnv("config.cf");

Accessing Values

// Chain [] operators naturally
doc["server"]["port"].as!long

// Type conversion
node.as!string    // String value
node.as!long      // Integer value
node.as!double    // Float value  
node.as!bool      // Boolean value

// Type checking
node.isObject     // true if object
node.isArray      // true if array
node.isString     // true if string
node.isInteger    // true if integer
node.isNull       // true if null or invalid reference

// Collection properties
node.length       // Number of elements/members
node.keys         // Array of object keys
node.hasKey("x")  // Check if key exists

// Safe access with defaults
node.getOr!long("timeout", 30)
node.getOr!string("host", "localhost")

Iteration

// Iterate over object members
foreach (key, value; doc["settings"]) {
    writefln("%s = %s", key, value.as!string);
}

// Iterate over array elements
foreach (item; doc["items"]) {
    writeln(item.as!string);
}

Writing

// Serialize document back to CF format
string output = toCF(doc);

// With configuration
CfWriterConfig cfg;
cfg.minified = true;
cfg.indent = "  ";
string output = toCF(doc, cfg);

Include Directives

CfParserConfig config;
config.enableIncludes = true;
config.fileReader = (string path) @safe {
    return std.file.readText(path);
};

auto doc = parseCFDocument(source, "main.cf", config);

Environment Variables

CfParserConfig config;
config.enableEnvSubstitution = true;
config.envReader = (string name) @safe {
    import core.stdc.stdlib : getenv;
    auto result = getenv(name.toStringz);
    return result ? fromStringz(result).idup : null;
};

// Or use the convenience function
auto doc = parseCFDocumentWithEnv(`
    host = ${DB_HOST:-localhost}
    port = ${DB_PORT:-5432}
`);

CF Format Examples

Basic Key-Value

name = "MyApp"
version = "1.0.0"
port = 8080
debug = false

Objects (HCL-style)

server {
    host = "localhost"
    port = 8080
}

# Or with equals sign
database = {
    driver = "postgres"
    host = "db.example.com"
}

Arrays

ports = [80, 443, 8080]

users = [
    { name = "Alice", role = "admin" }
    { name = "Bob", role = "user" }
]

All Number Formats

decimal = 1000
hex = 0xFF
octal = 0o755
binary = 0b1010
with_underscores = 1_000_000
float = 3.14159
scientific = 1.5e-10
infinity = inf
not_a_number = nan

String Varieties

double_quoted = "Hello, World!\n"
single_quoted = 'No escape processing'
raw_string = r"C:\path\to\file"

Triple-Quoted Strings (Multiline with Indentation Stripping)

Triple-quoted strings automatically strip leading indentation based on the position of the closing """. This allows you to indent multiline strings naturally within your configuration without including that indentation in the value:

# Indentation is stripped based on closing """ position
description = """
    This is a multiline string.
    The leading indentation is removed.
    All lines align with the closing quotes.
    """
# Result: "This is a multiline string.\nThe leading indentation..."

# Extra indentation beyond the base is preserved (useful for code)
script = """
    def hello():
        print("Hello")
        if True:
            print("World")
    """
# Result: "def hello():\n    print(\"Hello\")\n    if True:\n        print(\"World\")"

# Inline triple-quoted (no stripping needed)
inline = """no indentation here"""

Temporal Types

date = 2024-01-15
time = 10:30:00
datetime = 2024-01-15T10:30:00Z
with_offset = 2024-01-15T10:30:00+05:30

Environment Variables

# Basic substitution
host = ${DB_HOST}

# With default value
port = ${DB_PORT:-5432}

# Required (error if not set)
secret = ${API_KEY:?API key is required}

# In strings
url = "postgres://${DB_HOST}:${DB_PORT}/mydb"

# Escaped (literal ${)
pattern = "Use \${VAR} syntax"

Comments

# Hash comment
// Double-slash comment
/* Block
   comment */

key = "value"  # Inline comment

Separators

# All equivalent:
a = 1, b = 2, c = 3
a = 1; b = 2; c = 3
a = 1
b = 2
c = 3

Specification

dex-cf implements the CF v1.0 specification. For the complete specification, see:

Repository

License

BSD-3-Clause

See Also