Introduction
simple_config provides JSON-based configuration file management for Eiffel applications. It features dot-notation path access for nested values, environment variable fallback, and config file merging.
Installation
1. Set Environment Variable
export SIMPLE_EIFFEL=/d/prod
2. Add to ECF
<library name="simple_config" location="$SIMPLE_EIFFEL/simple_config/simple_config.ecf"/>
3. Dependencies
simple_config uses simple_json internally. With SIMPLE_EIFFEL set, all dependencies are automatically resolved.
Configuration Files
JSON Format
Configuration files are standard JSON:
{
"app": {
"name": "MyApplication",
"version": "1.0.0",
"debug": false
},
"database": {
"host": "localhost",
"port": 5432,
"name": "mydb",
"pool_size": 10
},
"features": {
"caching": true,
"logging": true,
"metrics": false
},
"allowed_hosts": ["localhost", "127.0.0.1", "example.com"]
}
Dot Notation
Access nested values using dot notation:
local
config: SIMPLE_CONFIG
do
create config.make_with_file ("config.json")
-- Direct access with dot notation
print (config.string_value ("database.host")) -- "localhost"
print (config.integer_value ("database.port")) -- 5432
print (config.boolean_value ("features.caching")) -- True
end
Loading Configuration
From File
local
config: SIMPLE_CONFIG
do
-- Load on creation
create config.make_with_file ("config.json")
end
Empty Configuration
local
config: SIMPLE_CONFIG
do
-- Create empty, build programmatically
create config.make
config.set_string ("app.name", "MyApp")
config.set_integer ("port", 8080)
end
Reloading
-- Reload from disk (useful for hot-reload)
config.load
Accessing Values
String Values
local
config: SIMPLE_CONFIG
host: STRING
do
create config.make_with_file ("config.json")
-- Basic access (may be Void)
if attached config.string_value ("database.host") as h then
host := h
end
-- With default value
host := config.string_value_or_default ("database.host", "localhost")
-- From config or environment variable
host := config.string_value_or_env ("database.host", "DB_HOST")
-- Config, then env, then default
host := config.string_value_or_env_or_default ("database.host", "DB_HOST", "localhost")
end
Integer Values
local
port: INTEGER
do
-- Basic (0 if not found)
port := config.integer_value ("database.port")
-- With default
port := config.integer_value_or_default ("database.port", 5432)
-- From config or env or default
port := config.integer_value_or_env ("database.port", "DB_PORT", 5432)
end
Boolean Values
local
debug_mode: BOOLEAN
do
-- Basic (False if not found)
debug_mode := config.boolean_value ("app.debug")
-- With default
debug_mode := config.boolean_value_or_default ("app.debug", False)
end
Real/Double Values
local
timeout: DOUBLE
do
-- Basic (0.0 if not found)
timeout := config.real_value ("network.timeout")
-- With default
timeout := config.real_value_or_default ("network.timeout", 30.0)
end
Array Values
local
hosts: ARRAYED_LIST [STRING]
ports: ARRAYED_LIST [INTEGER]
thresholds: ARRAYED_LIST [DOUBLE]
do
-- String arrays
hosts := config.string_list ("allowed_hosts")
across hosts as h loop
print (h + "%N")
end
-- Integer arrays
ports := config.integer_list ("service.ports")
-- Real arrays
thresholds := config.real_list ("metrics.thresholds")
end
Working with Sections
Getting Sections
local
config, db_config: SIMPLE_CONFIG
do
create config.make_with_file ("config.json")
-- Get nested section as separate config
if attached config.section ("database") as db then
-- Access values directly without prefix
print (db.string_value ("host"))
print (db.integer_value ("port"))
end
end
Deep Sections
-- With config like: {"logging": {"handlers": {"file": {"path": "/var/log"}}}}
if attached config.section ("logging.handlers.file") as file_handler then
print (file_handler.string_value ("path"))
end
Environment Variable Fallback
Priority Chain
The _or_env features check in order:
- Configuration file value
- Environment variable
- Default value (if provided)
local
config: SIMPLE_CONFIG
db_host: STRING
do
create config.make_with_file ("config.json")
-- If "database.host" exists in config, use it
-- Otherwise, check $DB_HOST environment variable
-- Finally, use "localhost" as default
db_host := config.string_value_or_env_or_default (
"database.host",
"DB_HOST",
"localhost"
)
end
Use Cases
- Development: Use config file values
- Production: Override with environment variables
- Containers: Pass config via environment
Modifying Configuration
Setting Values
local
config: SIMPLE_CONFIG
do
create config.make
-- Set various types
config.set_string ("app.name", "MyApp")
config.set_integer ("port", 8080)
config.set_boolean ("debug", True)
config.set_real ("timeout", 30.5)
-- Check modification status
if config.is_modified then
print ("Config has unsaved changes%N")
end
end
Setting Nested Sections
local
config, db_section: SIMPLE_CONFIG
do
create config.make
-- Create a section
create db_section.make
db_section.set_string ("host", "localhost")
db_section.set_integer ("port", 5432)
-- Add section to main config
config.set_section ("database", db_section)
end
Removing Values
-- Remove a key
config.remove ("debug")
Saving Configuration
Save to Original File
local
config: SIMPLE_CONFIG
do
create config.make_with_file ("config.json")
config.set_boolean ("debug", True)
-- Save back to config.json
config.save
end
Save to New File
-- Save to a different file
config.save_to ("config.production.json")
Export as JSON String
-- Compact JSON
print (config.to_json)
-- Pretty-printed JSON
print (config.to_json_pretty)
Merging Configuration Files
Environment Overrides
local
config: SIMPLE_CONFIG
do
-- Load base configuration
create config.make_with_file ("config.json")
-- Merge environment-specific overrides
config.merge_file ("config.production.json")
-- Values from production override base values
end
Merge Strategy
The merge operation:
- Overwrites existing keys with new values
- Adds new keys that don't exist
- Preserves keys not in the merged file
Checking for Keys
local
config: SIMPLE_CONFIG
do
create config.make_with_file ("config.json")
-- Check if key exists (supports dot notation)
if config.has_key ("database.host") then
print ("Database host is configured%N")
end
if not config.has_key ("cache.enabled") then
print ("Cache not configured, using defaults%N")
end
end
Best Practices
1. Use Environment Fallback for Sensitive Data
-- Never store passwords in config files
password := config.string_value_or_env_or_default (
"database.password",
"DB_PASSWORD",
""
)
2. Provide Sensible Defaults
-- Always have fallback values for optional settings
timeout := config.integer_value_or_default ("network.timeout", 30)
retries := config.integer_value_or_default ("network.retries", 3)
3. Validate Required Configuration
if not config.has_key ("database.host") then
print ("Error: database.host is required%N")
die (1)
end
4. Use Sections for Organization
-- Pass sections to subsystems
if attached config.section ("database") as db_config then
database.configure (db_config)
end