Configliere
Configliere provides discreet configuration for ruby scripts.
So, Consigliere of mine, I think you should tell your Don what everyone knows. — Don Corleone
You’ve got a script. It’s got some settings. Some settings are for this module, some are for that module. Most of them don’t change. Except on your laptop, where the paths are different. Or when you’re in production mode. Or when you’re testing from the command line.
Configliere manages settings from many sources: static constants, simple config files, environment variables, commandline options, straight ruby. You don’t have to predefine anything, but you can ask configliere to type-convert, require, document or password-obscure any of its fields. Modules can define config settings independently of each other and the main program.
Example
Here’s a simple example, using params from a config file and the command line. In the script:
#/usr/bin/env ruby
require 'configliere'
Settings.use :commandline, :config_file
Settings({
:dest_time => '1955-11-05',
:delorean => {
:power_source => 'plutonium',
:roads_needed => true,
},
:username => 'marty',
})
Settings.read 'my_script.yaml' # reads ~/.configliere/my_script.yaml
Settings.resolve!
p Settings
We’ll override some of the defaults with a config file, in this case ~/.configliere/simple_script.yaml
# Settings for return
:dest_time: 1985-11-05
:delorean:
:power_source: 1.21 jiggawatts
Output, when run with commandline parameters as shown:
./time_machine.rb --username=doc_brown --delorean.roads_needed="" --delorean.power_source="Mr. Fusion"
{:dest_time=>"1985-11-05", :username=>"doc_brown", :delorean=>{:power_source=>"Mr. Fusion", :roads_needed=>nil}}
For an extensive usage in production, see the wukong gem.
Design goals:
- Don’t go outside the family. Requires almost no external resources and almost no code in your script.
- Don’t mess with my crew. Settings for a model over here can be done independently of settings for a model over there, and don’t require asking the boss to set something up.
- Be willing to sit down with the Five Families. Takes settings from (at your option):
- Pre-defined defaults from constants
- Simple config files
- Environment variables
- Commandline options
- Ruby block called when all other options are in place
- Code of Silence. Most commandline parsers force you to pre-define all your parameters in a centralized and wordy syntax. In configliere, you pre-define nothing — commandline parameters map directly to values in the Configliere hash.
- Can hide your assets. Rather than storing passwords and API keys in plain sight, configliere has a protection racket that can obscure values when stored to disk.
fuhgeddaboudit.
Settings structure
Configliere settings are just a plain old normal hash.
You can define static defaults in your module
Settings({
:dest_time => '1955-11-05',
:fluxcapacitor => {
:speed => 88,
},
:delorean => {
:power_source => 'plutonium',
:roads_needed => true,
},
:username => 'marty',
:password => '',
})
(Note that all simple keys should be symbols, with an exception you’re about to see.) Retrieve the settings as:
# hash keys
Settings[:dest_time] #=> '1955-11-05'
# deep keys
Settings[:delorean][:power_source] #=> 'plutonium'
Settings[:delorean][:missing] #=> nil
Settings[:delorean][:missing][:fail] #=> raises an error
# dotted keys resolve to deep keys
Settings['delorean.power_source'] #=> 'plutonium'
Settings['delorean.missing'] #=> nil
Settings['delorean.missing.fail'] #=> nil
# method-like (no deep keys tho, and you have to #define the param; see below)
Settings.dest_time #=> '1955-11-05'
Shortcut syntax for deep keys
You can use a ‘dotted key’ like ‘delorean.power_source’ as simple notation for a deep key: Settings['delorean.power_source']
is equivalent to Settings[:delorean][:power_source]
. You can use a dotted key in any simple reference:
Settings['delorean.power_source'] = "Mr. Fusion"
Settings[:delorean][:power_source]
#=> "Mr. Fusion"
Settings.delete('delorean.power_source')
#=> "Mr. Fusion"
Settings
#=> { :delorean => {} }
Intermediate keys “auto-vivify” (automatically create any intervening hashes):
Settings['one.two.three'] = "To tha Fo'"
# Settings is { :one => { :two => { :three => "To tha Fo'" } }, :delorean => { :power_source => "Mr. Fusion" }
Do not use a dotted key except as a simple reference. You’ll ruin Christmas:
Settings.defaults :this => "that", "made.of.fail" => "oops"
#=> { :this => "that", :"made.of.fail" => "oops" } # !!! BROKEN !!!
This may change once we figure out how to handle it all more cleanly, and how to balance “Keep it Simple, Stupid” with “Keep it Convenient, Kilroy”.
Only basic functionality loaded by default
Configliere doesn’t load any other functionality by default — you may not want to load config files, or environment variable handling, and so forth. You can require each file directly, or call Configliere.use
with a list of mixins (:all to load all functionality).
Configliere.use :config_file, :define # Load config files and pre-define
Configliere.use :all # all of them
Configuration files
Call Settings.read(:my_settings_group)
to read a param group from the YAML global config file (Configliere::DEFAULT_CONFIG_FILE
— normally ~/.configliere.yaml)
# Settings for version II.
:time_machine:
:dest_time: 2015-11-05
:delorean:
:power_source: Mr. Fusion
:roads_needed: ~
You can instead supply a path to a config file. If a bare filename (no ‘/’) is given, configliere looks for the file in Configliere::DEFAULT_CONFIG_DIR
(normally ~/.configliere). Otherwise it loads the given file.
Settings.read(:time_machine) # looks in ~/.configliere.yaml, and extracts the :time_machine group
Settings.read('/etc/time_machine.yaml') # looks in /etc/time_machine.yaml
Settings.read('time_machine.yaml') # looks in ~/.configliere/time_machine.yaml
When you read directly from a file you should leave off the top-level settings group:
# Settings for version II.
:dest_time: 2015-11-05
:delorean:
:power_source: Mr. Fusion
:roads_needed: ~
Save defaults by inserting a line like:
Settings.save(:time_machine) # merges into ~/.configliere.yaml, under :time_machine
Settings.save('/etc/time_machine.yaml') # overwrites /etc/time_machine.yaml
Settings.save('time_machine.yaml') # overwrites ~/.configliere/time_machine.yaml
You’re free to use as many config files as you like. Loading a config file sets values immediately, so later-loaded files win out over earlier-loaded ones:
Settings.read('time_machine_global.yaml')
Settings.read('time_machine_site.yaml')
Environment Variables
Settings.env_vars 'DEST_TIME', :password => 'TM_PASS', 'delorean.power_source' => 'POWER_SOURCE'
As usual, dotted keys set the corresponeding nested key ('delorean.power_source'
sets Settings[:delorean][:power_source]
). As shown below, you may also use #define to set up environment variables: define 'delorean.power_source', :environment => 'POWER_SOURCE'
.
Environment variables are demonstrated in examples/simple_script.rb and examples/env_var_script.rb
NOTE: The interface to #env_vars has changed since v0.2, see CHANGELOG.textile
Command-line parameters
# Head back
time_machine --delorean.power_source='1.21 gigawatt lightning strike' --dest_time=1985-11-05
# (in the time_machine script:)
Settings.use :commandline
Settings.resolve!
Interpretation of command-line parameters:
- name-val params:
--param=val
setsConfigliere[:param]
to val. - boolean params:
--param
setsConfigliere[:param]
to be true.--param=""
setsConfigliere[:param]
to be nil. - scoped params:
--group-sub_group-param=val
setsConfigliere[:group][:subgroup][:param]
to val (and similarly for boolean parameters).- A dash or dot within a parameter name scopes that parameter:
--group.sub_group.param=val
and--group-sub_group-param=val
do the same thing. A _ within a parameter name is left as part of the segment. - Only
[\w\.\-]+
are accepted in parameter names.
- A dash or dot within a parameter name scopes that parameter:
- Settings.rest: anything else is stored, in order, in
Settings.rest
. - stop marker: a
--
alone stops parameter processing and tosses all remaining params (not including the--
) into Settings.rest.
Here are some things you don’t get:
- There are no short parameters (-r, etc).
- Apart from converting
''
(an explicit blank string) tonil
, no type coercion is performed on parameters unless requested explicitly (see below). - No validation is performed on parameters.
- No ordering or multiplicity is preserved (you can’t say
--file=this --file=that
).
If you want more, you might like the Trollop gem. If you enjoy wordy nightmares, use getoptlog from the ruby standard library.
Commandline parameters are demonstrated in examples/simple_script.rb and examples/env_var_script.rb":http://github.com/mrflip/configliere/tree/master/examples/env_var_script.rb
Fancy Parameters
You don’t have to pre-define parameters, but you can:
Settings.use :define
Settings.define :dest_time, :type => DateTime, :description => 'Arrival time'
Settings.define 'delorean.power_source', :env_var => 'POWER_SOURCE', :description => 'Delorean subsytem supplying power to the Flux Capacitor.'
Settings.define :password, :required => true, :encrypted => true
- :type: converts params to a desired form.
- :description: documents a param.
- :required: marks params required.
- :encrypted: marks params to be obscured when saved to disk. See [#Encrypted Parameters] below for caveats.
- :env_var: take param from given environment variable if set.
Defined parameters are demonstrated in most of the example scripts
Type Conversion
Settings.define :dest_time, :type => DateTime
Settings.define :fugeddaboudit, :type => Array
Settings :fugeddaboudit => 'badabing,badaboom,hey', :dest_time => '1955-11-05'
Settings.resolve!
Settings[:fugeddaboudit] #=> ['badabing', 'badaboom', 'hey']
Settings[:dest_time] #=> #<DateTime: 4870833/2,0,2299161>
Configliere can coerce parameter values to Integer, Float, :boolean, Symbol, Array, Date and DateTime. (Make sure to call Settings.resolve! in your script.)
- :boolean converts nil to nil ; false, ‘false’, 0, ‘0’ and ’’ to false; and everything else to true.
- Array just does a simple split on “,”. It doesn’t do any escaping or quoting.
- Date and DateTime convert unparseable inputs to nil.
Description
If you define a param’s description, besides nicely documenting it within your code the description will be stuffed into the output when the —help commandline option is invoked.
Required Parameters
Any required parameter found to be nil raises an error (listing all missing params). (Make sure to call Settings.resolve! in your script.)
Encrypted Parameters
Define a param to be encrypted and invoke Settings.save!
define 'amazon.api.key', :encrypted => true
Settings 'amazon.api.key' => 'fnord'
In this example, the hash saved to disk will contain { :amazon => { :api => { :encrypted_key => "...encrypted val..." } } }
. After reading from disk, #resolve! will recover its original value: { :amazon => { :api => { :key => "fnord" } } }
. The code currently doesn’t look for a collision between a :param and :encrypted_param, so be careful; you can preemptively call resolve_encrypted! to enforce order.
There are two kinds of cryptography in this world: cryptography that will stop your kid sister from reading your files, and cryptography that will stop major governments from reading your files. This book is about the latter. — Preface to Applied Cryptography by Bruce Schneier
Configliere provides the latter. Anyone with access to the script, its config files and the config file passphrase can recover the plaintext password. Still, there’s a difference between immediate access and having to find a paperclip and jimmy open your annoying older brother’s stupid journal.
Encrypted parameters are demonstrated in examples/encrypted_script.rb
Ruby Block
Settings.finally do |c|
c.dest_time = (Time.now + 60) if c.username == 'einstein'
# you can use hash syntax too
c[:dest_time] = (Time.now + 60) if c[:username] == 'einstein'
end
#
# ... rest of setup ...
#
Settings.resolve! # the finally blocks will be called in order
Configliere ‘finally’ blocks are invoked when you call #resolve!
. They’re guaranteed to be called at the end of the resolve chain, and before the validate chain.
Config blocks are demonstrated in examples/config_block.rb
Independent Settings
All of the above examples use the global variable Settings
, defined in configliere.rb. You’re free to define your own settings universe though:
class Wolfman
cattr_accessor :config
self.config = Configliere.new({
:moon => 'full',
:nards => true,
})
end
teen_wolf = proj.new
teen_wolf.config.defaults(:give_me => 'keg of beer')
teen_wolf.config #=> {:moon=>"full", :nards=>true, :give_me=>"keg of beer" }
Settings #=> {}
Values in here don’t overlap with the Settings object or any other settings universe. However, every one that pulls in commandline params gets a full copy of the commandline params.
Project info
Note on Patches/Pull Requests
- Fork the project.
- Make your feature addition or bug fix.
- Add tests for it. This is important so I don’t break it in a future version unintentionally.
- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
- Send a pull request to github.com/mrflip
- Drop a line to the mailing list for infochimps open-source projects, [email protected]
Copyright
Copyright © 2010 mrflip. See LICENSE for details.