superconf

0.3.0 released

superconf

A small, framework-agnostic, process-wide configuration registry for Crystal.

Every tunable — in a library or the app using it — is registered once and becomes reachable from four synchronized surfaces, each value carrying the source it came from:

| Surface | Form (key screen.resize_interval) | |---|---| | Config key | screen.resize_interval (YAML/JSON) | | Environment variable | <PREFIX>SCREEN_RESIZE_INTERVAL | | Command-line option | --screen-resize-interval | | Runtime | Superconf.screen_resize_interval (typed accessor) |

Because the registry is a single process-wide singleton, several independent components that all register into it — e.g. a terminal library and the application built on top of it — appear together in one combined, dumpable list.

Quick start

require "superconf"

module Superconf
  option "myapp.refresh", 1.second, description: "Refresh interval"
  option "myapp.workers", 4, validate: ->(n : Int32) { n > 0 }
end

Superconf.app_name   = "myapp"      # ~/.config/myapp/config.yml
Superconf.env_prefix = "MYAPP_"     # MYAPP_MYAPP_REFRESH, ...

Superconf.configure!                # file (if present) + env + CLI, in precedence order

Superconf.myapp_refresh             # => Time::Span (typed, cached read)
Superconf.myapp_refresh = 5.seconds

Precedence, low → high: default < config file < env var < command-line < runtime assignment.

Declaring options

option key, default, … infers the value type from default. Built-in parsing covers Bool, Int32, Int64, Float64, String, Char, Time::Span, and any Enum (including @[Flags]). For other types pass a parse: proc. Add a validate: predicate to reject out-of-range values from any source.

register is the same thing without the typed accessor (for keys known only at runtime). get(key, T) / set(key, v) are the dynamic, string-keyed API used by the loaders.

Overriding and aliasing lower-level options

A library registers an option; the app on top can adjust it without touching the library. Two ways:

  • set_default key, value — change the baseline while keeping it overridable (a config file / env var / CLI flag / runtime assignment still wins). Use it when the app wants a different default than the library's.

  • register_alias alias_key, target_key (or the typed option_alias macro) — promote an option under a second name. The alias shares the one value, type, default, parsing and validation, but gains its own config key, env var and CLI flag. Reading or writing either name affects the same value.

module Superconf
  option "screen.resize_interval", 0.2.seconds   # declared by a library

  # The app considers this important enough to surface under its own name:
  option_alias "myapp.refresh", "screen.resize_interval", Time::Span
end

Superconf.myapp_refresh = 1.second                # writes the shared value
Superconf.screen_resize_interval                  # => 1.second
# MYAPP_REFRESH / --myapp-refresh / `myapp.refresh:` now work too

Aliasing an alias resolves to the same underlying option. Aliases stay out of the re-loadable value dumps (the canonical name already carries the value) but appear in the report dump with an alias_of field.

Dumping

Superconf.dump(io, format) (or --dump-config[ FORMAT] once configure! / load_args runs) emits:

  • yaml (default) and json — valid, re-loadable config files;
  • env — a sourceable export …='value' script;
  • pretty — an aligned table showing each value's source;
  • report — rich JSON with full per-option metadata.

Changing options while the program runs

An option is a live value, not just a parsed setting, so a program can read one into a control, let the user change it, and save the result:

  • Superconf.on_change key { … } (or Superconf[key].on_change) runs the block after the option's value changes — from a file, an env var, a flag, a runtime assignment or a reset; a block registered on an alias hears changes through either name. off_change takes the returned handle back.
  • Superconf.reset key puts an option back to its registered default, at default precedence, so a later load from any source applies again.
  • load_file path, Superconf::Source::Runtime (and load_yaml's source:) applies a file the user picked now, over values an env var, a flag or an earlier assignment set — which a load at the usual file precedence would leave standing.
  • parse_yaml text, origin parses a document as load_yaml would (a malformed one raising Superconf::Error) without applying it, and load_yaml document, origin applies the parsed YAML::Any — for a program that also reads the file's entries itself and parses it once.
  • Superconf.dump(io, format) { |opt| … } (and to_yaml / to_json with a block) dumps only the options the block selects, re-loadably — e.g. { |opt| !opt.at_default? } for a settings file that pins what the user chose and leaves the rest at the defaults.
  • load_args argv, defer_dump: true notes a --dump-config in Superconf.pending_dump instead of dumping and exiting at once, for a library that reads the arguments as it loads, before the application has registered its own options; Superconf.dump_pending performs it later, once every option is in.

Errors

A single Superconf::Error covers unknown keys, type mismatches, unparseable values, and failed validation — rescue Superconf::Error to handle all of them.

superconf:
  github: crystallabs/superconf.cr
  version: ~> 0.3.0
License AGPLv3
Crystal >= 1.0.0

Authors

Dependencies 0

Development Dependencies 1

  • ameba master
    {'branch' => 'master', 'github' => 'crystal-ameba/ameba'}

Dependents 3

Last synced .
search fire star recently