simple_env

Cookbook

Recipes

  1. Application Configuration Manager
  2. Dynamic Path Resolver
  3. Feature Flags from Environment
  4. Library Path Manager
  5. Environment Dump Utility
  6. Simple .env File Loader
  7. Multi-Environment Configuration
  8. Database Connection String Builder

1. Application Configuration Manager

Centralized configuration with defaults and validation.

class APP_CONFIG

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
            load_config
        end

feature -- Configuration

    database_host: STRING_32
    database_port: INTEGER
    log_level: STRING_32
    debug_mode: BOOLEAN
    max_connections: INTEGER

feature -- Queries

    is_valid: BOOLEAN
        do
            Result := not database_host.is_empty and
                      database_port > 0 and
                      max_connections > 0
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

    load_config
        do
            -- Required with defaults
            database_host := get_or_default ("DB_HOST", "localhost")
            database_port := get_integer_or_default ("DB_PORT", 5432)
            log_level := get_or_default ("LOG_LEVEL", "INFO")
            max_connections := get_integer_or_default ("MAX_CONNECTIONS", 10)

            -- Boolean flag
            debug_mode := get_boolean ("DEBUG")
        end

    get_or_default (a_name, a_default: STRING_32): STRING_32
        do
            if attached env.get (a_name) as v then
                Result := v
            else
                Result := a_default
            end
        end

    get_integer_or_default (a_name: STRING_32; a_default: INTEGER): INTEGER
        do
            if attached env.get (a_name) as v and then v.is_integer then
                Result := v.to_integer
            else
                Result := a_default
            end
        end

    get_boolean (a_name: STRING_32): BOOLEAN
        do
            if attached env.get (a_name) as v then
                Result := v.same_string ("true") or
                          v.same_string ("1") or
                          v.same_string ("yes")
            end
        end

end

Usage

local
    config: APP_CONFIG
do
    create config.make

    if config.is_valid then
        print ("Database: " + config.database_host + ":" +
               config.database_port.out)

        if config.debug_mode then
            print ("Debug mode enabled%N")
        end
    else
        print ("Invalid configuration%N")
    end
end

2. Dynamic Path Resolver

Resolve application paths with environment variable expansion.

class PATH_RESOLVER

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
        end

feature -- Path Resolution

    config_dir: STRING_32
        do
            Result := env.expand ("%APPDATA%\MyApp")
        end

    data_dir: STRING_32
        do
            Result := env.expand ("%LOCALAPPDATA%\MyApp\Data")
        end

    log_dir: STRING_32
        do
            if attached env.get ("MYAPP_LOG_DIR") as custom then
                Result := custom
            else
                Result := env.expand ("%TEMP%\MyApp\Logs")
            end
        end

    temp_file (a_name: STRING_32): STRING_32
        do
            Result := env.expand ("%TEMP%\") + a_name
        end

    user_document (a_name: STRING_32): STRING_32
        do
            Result := env.expand ("%USERPROFILE%\Documents\") + a_name
        end

    resolve (a_template: STRING_32): STRING_32
            -- Resolve any path with %VAR% references
        do
            Result := env.expand (a_template)
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

end

Usage

local
    paths: PATH_RESOLVER
do
    create paths.make

    -- Standard locations
    print ("Config: " + paths.config_dir + "%N")
    print ("Logs: " + paths.log_dir + "%N")

    -- Dynamic paths
    print ("Temp: " + paths.temp_file ("cache.tmp") + "%N")

    -- Custom templates
    print (paths.resolve ("%PROGRAMFILES%\MyApp\bin"))
end

3. Feature Flags from Environment

Control application features via environment variables.

class FEATURE_FLAGS

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
        end

feature -- Feature Queries

    is_enabled (a_feature: STRING_32): BOOLEAN
            -- Is feature enabled via FEATURE_{name}=true/1/yes?
        do
            if attached env.get ("FEATURE_" + a_feature.as_upper) as v then
                Result := v.same_string_general ("true") or
                          v.same_string_general ("1") or
                          v.same_string_general ("yes")
            end
        end

    is_disabled (a_feature: STRING_32): BOOLEAN
            -- Is feature explicitly disabled?
        do
            if attached env.get ("FEATURE_" + a_feature.as_upper) as v then
                Result := v.same_string_general ("false") or
                          v.same_string_general ("0") or
                          v.same_string_general ("no")
            end
        end

    enabled_features: ARRAYED_LIST [STRING_32]
            -- All currently enabled features
        local
            feature_vars: ARRAYED_LIST [STRING_32]
            name: STRING_32
        do
            create Result.make (10)
            feature_vars := env.names_with_prefix ("FEATURE_")

            across feature_vars as var loop
                name := var.substring (9, var.count)  -- Remove "FEATURE_"
                if is_enabled (name) then
                    Result.extend (name)
                end
            end
        end

feature -- Common Features

    new_ui_enabled: BOOLEAN
        do
            Result := is_enabled ("NEW_UI")
        end

    beta_features_enabled: BOOLEAN
        do
            Result := is_enabled ("BETA")
        end

    debug_logging_enabled: BOOLEAN
        do
            Result := is_enabled ("DEBUG_LOGGING")
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

end

Usage

-- Set in environment:
-- FEATURE_NEW_UI=true
-- FEATURE_BETA=yes
-- FEATURE_DEBUG_LOGGING=1

local
    flags: FEATURE_FLAGS
do
    create flags.make

    if flags.new_ui_enabled then
        show_new_ui
    else
        show_classic_ui
    end

    if flags.is_enabled ("EXPERIMENTAL_CACHE") then
        enable_experimental_cache
    end

    -- List all enabled features
    across flags.enabled_features as f loop
        print ("Enabled: " + f + "%N")
    end
end

4. Library Path Manager

Manage simple_* library paths for build systems.

class LIBRARY_PATHS

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
        end

feature -- Library Access

    simple_libraries: ARRAYED_LIST [TUPLE [name, path: STRING_32]]
            -- All configured SIMPLE_* libraries
        do
            create Result.make (20)
            across env.names_with_prefix ("SIMPLE_") as name loop
                if attached env.get (name) as path then
                    Result.extend ([name.twin, path.twin])
                end
            end
        end

    library_path (a_name: STRING_32): detachable STRING_32
            -- Get path for specific library (e.g., "json" -> SIMPLE_JSON path)
        do
            Result := env.get ("SIMPLE_" + a_name.as_upper)
        end

    is_configured (a_name: STRING_32): BOOLEAN
            -- Is library path configured?
        do
            Result := env.has ("SIMPLE_" + a_name.as_upper)
        end

feature -- Validation

    missing_libraries (a_required: ARRAY [STRING_32]): ARRAYED_LIST [STRING_32]
            -- Which required libraries are not configured?
        do
            create Result.make (a_required.count)
            across a_required as lib loop
                if not is_configured (lib) then
                    Result.extend (lib)
                end
            end
        end

    validate_required (a_required: ARRAY [STRING_32]): BOOLEAN
            -- Are all required libraries configured?
        do
            Result := missing_libraries (a_required).is_empty
        end

feature -- Output

    generate_env_script: STRING_32
            -- Generate shell script to set all library paths
        do
            create Result.make (1024)
            Result.append ("#!/bin/bash%N")
            across simple_libraries as lib loop
                Result.append ("export " + lib.name + "=" + lib.path + "%N")
            end
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

end

Usage

local
    libs: LIBRARY_PATHS
    required: ARRAY [STRING_32]
do
    create libs.make

    -- Check required libraries
    required := <<"JSON", "FILE", "ENV">>

    if not libs.validate_required (required) then
        print ("Missing libraries:%N")
        across libs.missing_libraries (required) as lib loop
            print ("  SIMPLE_" + lib + "%N")
        end
    end

    -- List all configured
    across libs.simple_libraries as lib loop
        print (lib.name + " = " + lib.path + "%N")
    end
end

5. Environment Dump Utility

Dump environment variables for debugging.

class ENV_DUMP

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
        end

feature -- Output

    dump_all: STRING_32
            -- All environment variables as NAME=VALUE
        do
            create Result.make (4096)
            across env.all_names as name loop
                if attached env.get (name) as value then
                    Result.append (name + "=" + value + "%N")
                end
            end
        end

    dump_filtered (a_prefix: STRING_32): STRING_32
            -- Variables matching prefix
        do
            create Result.make (1024)
            across env.names_with_prefix (a_prefix) as name loop
                if attached env.get (name) as value then
                    Result.append (name + "=" + value + "%N")
                end
            end
        end

    dump_sensitive_masked: STRING_32
            -- All variables with sensitive values masked
        local
            value: STRING_32
        do
            create Result.make (4096)
            across env.all_names as name loop
                if attached env.get (name) as v then
                    if is_sensitive (name) then
                        value := "********"
                    else
                        value := v
                    end
                    Result.append (name + "=" + value + "%N")
                end
            end
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

    sensitive_patterns: ARRAY [STRING_32]
        once
            Result := <<"PASSWORD", "SECRET", "KEY", "TOKEN", "CREDENTIAL">>
        end

    is_sensitive (a_name: STRING_32): BOOLEAN
        do
            across sensitive_patterns as pattern loop
                if a_name.as_upper.has_substring (pattern) then
                    Result := True
                end
            end
        end

end

6. Simple .env File Loader

Load environment variables from .env files.

class DOTENV_LOADER

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
        end

feature -- Loading

    load (a_path: STRING_32): BOOLEAN
            -- Load .env file. Returns True if successful.
        local
            file: SIMPLE_FILE
            lines: ARRAYED_LIST [STRING_32]
        do
            create file.make (a_path)
            if file.exists then
                lines := file.read_lines
                across lines as line loop
                    process_line (line)
                end
                Result := True
            end
        end

    load_if_exists (a_path: STRING_32)
            -- Load .env file if it exists (silent if not)
        do
            load (a_path).do_nothing
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

    process_line (a_line: STRING_32)
        local
            line: STRING_32
            eq_pos: INTEGER
            name, value: STRING_32
        do
            line := a_line.twin
            line.left_adjust
            line.right_adjust

            -- Skip empty lines and comments
            if not line.is_empty and not line.starts_with ("#") then
                eq_pos := line.index_of ('=', 1)
                if eq_pos > 1 then
                    name := line.substring (1, eq_pos - 1)
                    value := line.substring (eq_pos + 1, line.count)

                    -- Remove surrounding quotes if present
                    value := unquote (value)

                    -- Only set if not already defined (don't override)
                    if not env.has (name) then
                        env.set (name, value)
                    end
                end
            end
        end

    unquote (a_value: STRING_32): STRING_32
        do
            Result := a_value.twin
            if Result.count >= 2 then
                if (Result.starts_with ("%"") and Result.ends_with ("%"")) or
                   (Result.starts_with ("'") and Result.ends_with ("'")) then
                    Result := Result.substring (2, Result.count - 1)
                end
            end
        end

end

Usage

-- .env file:
-- DATABASE_URL=postgres://localhost/mydb
-- API_KEY="secret-key-12345"
-- DEBUG=true

local
    dotenv: DOTENV_LOADER
do
    create dotenv.make
    dotenv.load_if_exists (".env")

    -- Variables are now available via SIMPLE_ENV
end

7. Multi-Environment Configuration

Support development, staging, and production environments.

class MULTI_ENV_CONFIG

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
            detect_environment
        end

feature -- Environment

    environment: STRING_32
            -- Current environment: development, staging, production

    is_development: BOOLEAN
        do
            Result := environment.same_string ("development")
        end

    is_staging: BOOLEAN
        do
            Result := environment.same_string ("staging")
        end

    is_production: BOOLEAN
        do
            Result := environment.same_string ("production")
        end

feature -- Environment-Specific Values

    get (a_key: STRING_32): detachable STRING_32
            -- Get value, checking environment-specific first
        local
            env_key: STRING_32
        do
            -- Try environment-specific first: PROD_DATABASE_URL
            env_key := environment.as_upper.substring (1, 4) + "_" + a_key
            Result := env.get (env_key)

            -- Fall back to generic: DATABASE_URL
            if Result = Void then
                Result := env.get (a_key)
            end
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

    detect_environment
        do
            if attached env.get ("APP_ENV") as e then
                environment := e.as_lower
            elseif attached env.get ("ENVIRONMENT") as e then
                environment := e.as_lower
            else
                environment := "development"
            end
        end

end

8. Database Connection String Builder

Build connection strings from environment variables.

class DB_CONNECTION_BUILDER

create
    make

feature {NONE} -- Initialization

    make
        do
            create env
        end

feature -- Connection Strings

    postgres_url: detachable STRING_32
            -- Build PostgreSQL connection URL from env vars
        local
            host, port, user, pass, db: detachable STRING_32
        do
            host := env.get ("DB_HOST")
            port := env.get ("DB_PORT")
            user := env.get ("DB_USER")
            pass := env.get ("DB_PASSWORD")
            db := env.get ("DB_NAME")

            if host /= Void and db /= Void then
                create Result.make (100)
                Result.append ("postgresql://")

                if user /= Void then
                    Result.append (user)
                    if pass /= Void then
                        Result.append (":" + pass)
                    end
                    Result.append ("@")
                end

                Result.append (host)
                if port /= Void then
                    Result.append (":" + port)
                end
                Result.append ("/" + db)
            end
        end

    sqlite_path: STRING_32
            -- Get SQLite database path
        do
            if attached env.get ("SQLITE_PATH") as p then
                Result := env.expand (p)  -- Expand %APPDATA% etc.
            else
                Result := env.expand ("%LOCALAPPDATA%\MyApp\data.db")
            end
        end

feature {NONE} -- Implementation

    env: SIMPLE_ENV

end

Usage

-- Environment:
-- DB_HOST=localhost
-- DB_PORT=5432
-- DB_USER=myapp
-- DB_PASSWORD=secret
-- DB_NAME=myapp_dev

local
    builder: DB_CONNECTION_BUILDER
do
    create builder.make

    if attached builder.postgres_url as url then
        print ("Connecting to: " + url)
        -- postgresql://myapp:secret@localhost:5432/myapp_dev
    end
end