cprop Clojure Configuration Library

repository·master·Indexed 18 days ago

https://github.com/tolitius/cprop

A Clojure library that converges multiple configuration sources—including EDN files, System properties, and environment variables—into a single immutable map. It supports hierarchical data structures, flexible merging mechanisms via :merge and :override-with, and automatic type conversion for environment variables. cprop provides utilities for loading from .env and .properties files, navigating configurations with cursors, and translating maps back into .properties or .env formats.

Tokens
3.3K
Snippets
16
Records
20
Agent score
13%

What's inside cprop

  1. Map environment variables to nested configuration keys

    master

    Since environment variables are flat, cprop uses specific character conventions to represent nested structures and special keyword characters:

    • Nesting: Use double underscores (__) to represent a level of nesting. A single underscore (_) is converted to a hyphen (-) to match Clojure keywords.
    • Namespaced Keywords: To represent a namespace separator /, use triple underscores (___).
    • Dotted Keywords: Dotted keywords are supported directly (e.g., DOTTED.KEY).

    Example: To override {:http {:pool {:socket-timeout 60000}}}: export HTTP__POOL__SOCKET_TIMEOUT=4242

  2. Automatic type conversion for environment variables

    master

    Environment variables are read as strings, but cprop automatically converts them to appropriate datatypes:

    • Numbers: Strings like 4242 become Long.
    • EDN Data: Strings formatted as EDN (e.g., '[1 2 3]') are converted to their corresponding structures (e.g., a vector).
    • Strings: To force a purely numeric string to remain a String (preventing it from becoming a Long), wrap it in double quotes.

    Examples:

    • export APP_NUMS='[1 2 3]' $\rightarrow$ [1 2 3] (vector)
    • export BAD_PASSWORD='123456789' $\rightarrow$ 123456789 (Long)
    • export BAD_PASSWORD='"123456789"' $\rightarrow$ "123456789" (String)
  3. Use cprop syntax for System properties

    master

    When overriding nested configuration properties via System properties (e.g., -D flags), cprop uses a specific transformation:

    1. Periods (.) in the property name are converted to dashes (-) in the map keys.
    2. Underscores (_) are used to represent nesting.

    Example Mapping: If your config is:

    {:http {:pool {:socket-timeout 60000}}}

    To override socket-timeout, use the system property: -Dhttp_pool_socket.timeout=4242

    # Example CLI usage
    java -Dhttp_pool_socket.timeout=4242 -jar my-app.jar
  4. Navigate configuration using Cursors

    master

    To avoid repeating long nested paths when accessing configuration, use cursor. A cursor allows you to focus on a specific sub-section of the configuration map.

    Composable Cursors: Cursors are composable. You can create a new cursor by passing an existing cursor and a new path to the cursor function.

    (require '[cprop.core :refer [load-config cursor]])
    
    (def conf (load-config))
    
    ;; Create a cursor for a specific path
    (def rabbit (cursor conf :source :account :rabbit))
    (rabbit :host) ;; returns the value at that path
    
    ;; Compose a new cursor from an existing one
    (def src (cursor conf :source))
    (def account (cursor conf src :account))
  5. Debug configuration loading and substitutions

    master

    To see which files were loaded and exactly which properties were substituted by cprop during the merge process, set the DEBUG environment variable to y or Y.

    export DEBUG=y
  6. Set the configuration file path via the 'conf' system property

    master

    You can specify which configuration file cprop should load by setting the conf system property using one of the following methods:

    Command Line:

    java -Dconf="../path/to/config.edn" -jar app.jar

    Clojure (boot/runtime):

    (System/setProperty "conf" "resources/config.edn")

    Leiningen (lein):

    :profiles {:dev {:jvm-opts ["-Dconf=resources/config.edn"]}}
  7. Merge configuration with environment variables

    master

    By default, cprop performs an intersection merge: it only merges environment variables that match keys already present in your configuration files.

    To perform a union merge (merging ALL environment variables into your configuration), use the :merge option with (from-env) from cprop.source.

    (require '[cprop.source :refer [from-env]])
    
    (load-config :merge [(from-env)])
  8. Merge configuration from a .env file

    master

    You can load environment variables from a .env file using (from-env-file path) from cprop.source.

    Syntax rules for .env files:

    • Format: VAR=VAL per line.
    • # denotes a comment.
    • Blank lines are ignored.
    • Quotation marks are treated as part of the value (no special handling).
    (require '[cprop.core :as cp]
             '[cprop.source :as cs])
    
    (cp/load-config :merge [(cs/from-env-file "dev-resources/.env")])
  9. Merge configuration from a .properties file

    master

    Use (from-props-file path) from cprop.source to convert Java-style .properties files into EDN maps and merge them into your configuration.

    When using (load-config :merge [(from-props-file "path")]), cprop merges:

    1. config.edn (from classpath)
    2. Matching system properties
    3. Matching ENV variables
    4. The specified .properties file
    (require '[cprop.source :refer [from-props-file]])
    
    (load-config :merge [(from-props-file "overrides.properties")])
  10. Translate EDN configuration to .properties or .env files

    master

    The cprop.tools namespace provides utilities to convert an existing Clojure map (typically your loaded config) into standard file formats for deployment or CI/CD environments.

    • t/map->props-file: Converts a map to a .properties formatted file. Returns the path to the temporary file created.
    • t/map->env-file: Converts a map to a .env formatted file (using export KEY=VALUE syntax). Returns the path to the temporary file created.
    (require '[cprop.tools :as t])
    
    ;; To .properties
    (t/map->props-file config)
    
    ;; To .env
    (t/map->env-file config)
  11. Load configuration with load-config

    master

    The primary way to use cprop is by calling (load-config). This function loads an EDN configuration from the classpath and/or the file system, merges it with system properties and ENV variables, and returns an immutable Clojure map.

    By default, cprop looks for:

    1. A config.edn resource on the classpath.
    2. A file on the file system at the path specified by the conf system property.

    If both exist, the file system source overrides the classpath source. The resulting map is then merged with matching System properties and ENV variables.

    (require '[cprop.core :refer [load-config]])
    
    (load-config)
  12. Read properties as raw strings using :as-is?

    master

    By default, cprop attempts to parse configuration values and environment variables using Clojure's EDN reader. This can cause errors or unexpected behavior if a value (like a date string or a non-EDN formatted string) is not valid EDN.

    To prevent conversion and treat values as raw strings, use the :as-is? true option. This option is available in:

    • Individual source functions (e.g., s/from-env, s/from-system-props, s/from-props-file).
    • The top-level load-config function to apply it to all sources.
    ;; For specific sources
    (require '[cprop.source :as s])
    (s/from-env {:as-is? true})
    
    ;; For the entire configuration
    (load-config :as-is? true)