docopt-config
docopt-config
A Crystal library that extends docopt to support configuration files and environment variables, providing a unified way to handle command-line arguments, config files, and environment variables with proper precedence.
Features
- Combines docopt command-line parsing with config file and environment variable support
- YAML configuration file support
- Environment variable support with optional prefixing
- Proper precedence order: CLI arguments > environment variables > config file
- Seamless integration with existing docopt usage patterns
Installation
-
Add the dependency to your
shard.yml:dependencies: docopt-config: github: ralsina/docopt-config -
Run
shards install
Usage
Replace your standard Docopt.docopt calls with Docopt.docopt_config:
require "docopt-config"
doc = <<-DOC
My Awesome Application.
Usage:
myapp [--verbose=<level>] [--output=<file>] [--force]
Options:
--verbose=<level> Verbosity level [default: 1]
--output=<file> Output file path
--force Force operation
--help Show this help message
DOC
# Parse with combined config sources
options = Docopt.docopt_config(
doc,
config_file_path: "config.yml",
env_prefix: "MYAPP"
)
# Access options with normal docopt syntax
puts options["--verbose"] # CLI argument takes precedence
puts options["--output"] # Falls back to env var or config file
Configuration File Format
Create a YAML configuration file (e.g., config.yml) to set fallback values for your options:
# config.yml
verbose: 2
output: "results.txt"
force: true
input_file: "/path/to/input.txt"
count: 42
Example:
For the docopt usage pattern:
Usage: myapp [--verbose=<level>] [--output=<file>] [--force] [--input-file=<path>] [--count=<number>] [--tag=<tag>...]
Your configuration file would look like:
# Set fallback values for all options
verbose: "1" # String value (maps to --verbose)
output: "default_output.txt" # String with quotes (maps to --output)
force: true # Boolean value (maps to --force)
input_file: "/data/input.csv" # Path with quotes (maps to --input-file)
count: 10 # Numeric value (maps to --count)
tag: # Sequence (maps to repeatable --tag)
- "one"
- "two"
Values are coerced to the type docopt would produce: true/false become booleans, numbers become integers, sequences become arrays of strings (so repeatable options like --tag=<tag>... can be set from the config file), and anything else becomes a string.
Note: The library automatically maps configuration keys to docopt options by:
- Converting snake_case to kebab-case (
input_file→--input-file) - Adding
--prefix to option names (verbose→--verbose)
You can also use quoted keys if you prefer:
"--verbose": "1"
"--output": "default_output.txt"
Environment Variables
Environment variables are automatically converted from UPPER_SNAKE_CASE to --kebab-case options:
export MYAPP_VERBOSE=3
export MYAPP_OUTPUT_FILE="/path/to/output.txt"
export MYAPP_FORCE=true
Values are coerced to the type docopt would produce for the option: flags accept true/yes/1 and false/no/0 as booleans (anything else is kept as a string), repeatable flags like -v... accept a count (MYAPP_V=3 → 3), and repeatable options split comma-separated values into an array (MYAPP_TAGS="a, b" → ["a", "b"]). Short options like -v map from PREFIX_V.
Precedence Order
The library follows this precedence order (highest to lowest):
- Command-line arguments - Always take precedence when provided
- Environment variables - Used when CLI argument is not provided
- Configuration file - Used when neither CLI nor env vars are available
- Docopt defaults - Used as final fallback (specified in docopt usage documentation)
Important: The docopt documentation remains the single source of truth for option definitions. The library enhances docopt by providing additional fallback sources (environment variables and config files) that are consulted before docopt's own defaults.
Advanced Usage
# Custom config file path
options = Docopt.docopt_config(doc, config_file_path: "/etc/myapp/config.yml")
# No prefix: every environment variable is mapped (legacy default).
# Prefer a prefix to avoid collisions with unrelated variables.
options = Docopt.docopt_config(doc, env_prefix: nil)
# Empty prefix: do not use environment variables at all
options = Docopt.docopt_config(doc, config_file_path: "config.yml", env_prefix: "")
# Pass custom argv for testing
options = Docopt.docopt_config(doc, argv: ["--verbose", "5"])
# Library mode: raise exceptions instead of terminating the process,
# and write help/version output to a custom IO. With exit: false,
# help/version requests raise Docopt::ConfigExit, invalid usage raises
# Docopt::DocoptExit, and other errors propagate untouched.
# In the default exit mode, usage errors print the error message and
# usage summary to standard error and exit with status 1.
# Like plain docopt, --help/-h/--version are only recognized when
# declared in the doc, and are never triggered by tokens after "--".
io = IO::Memory.new
options = Docopt.docopt_config(doc, argv: ["--help"], exit: false, io: io)
Printing the effective configuration
Libraries can opt in to a --print-config-style flag that prints every option declared in the doc, resolved through the full precedence chain, as YAML with snake_case keys:
options = Docopt.docopt_config(
doc,
config_file_path: "config.yml",
env_prefix: "MYAPP",
print_config_option: "--print-config"
)
myapp --print-config > config.yml # the output is a working config file
The flag does not need to be declared in the doc, is only recognized before a -- separator, and when given the process prints the configuration and exits with status 0. With exit: false, the YAML is written to io and a Docopt::ConfigExit is raised instead.
Development
To run tests:
crystal spec
To install dependencies:
shards install
Contributing
- Fork it (https://github.com/your-github-user/docopt-config/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Roberto Alsina - creator and maintainer