configliere 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +6 -0
- data/.gitignore +38 -0
- data/LICENSE +20 -0
- data/README.textile +196 -0
- data/Rakefile +91 -0
- data/VERSION +1 -0
- data/bin/configliere +85 -0
- data/configliere.gemspec +123 -0
- data/examples/commandline_script.rb +74 -0
- data/examples/commandline_script.yaml +8 -0
- data/examples/foo.yaml +15 -0
- data/examples/simple_script.rb +23 -0
- data/examples/simple_script.yaml +5 -0
- data/lib/configliere.rb +36 -0
- data/lib/configliere/commandline.rb +102 -0
- data/lib/configliere/commandline/commands.rb +30 -0
- data/lib/configliere/commandline/options.rb +4 -0
- data/lib/configliere/config_blocks.rb +41 -0
- data/lib/configliere/core_ext.rb +1 -0
- data/lib/configliere/core_ext/hash.rb +98 -0
- data/lib/configliere/crypter.rb +72 -0
- data/lib/configliere/define.rb +151 -0
- data/lib/configliere/encrypted.rb +78 -0
- data/lib/configliere/environment.rb +38 -0
- data/lib/configliere/param.rb +97 -0
- data/lib/configliere/param_store.rb +70 -0
- data/spec/configliere/commandline_spec.rb +62 -0
- data/spec/configliere/config_blocks_spec.rb +26 -0
- data/spec/configliere/crypter_spec.rb +18 -0
- data/spec/configliere/define_spec.rb +97 -0
- data/spec/configliere/encrypted_spec.rb +71 -0
- data/spec/configliere/environment_spec.rb +23 -0
- data/spec/configliere/param_spec.rb +43 -0
- data/spec/configliere/param_store_spec.rb +81 -0
- data/spec/configliere_spec.rb +21 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -0
- metadata +164 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems' ; $: << File.dirname(__FILE__)+'/../lib'
|
3
|
+
require 'pp'
|
4
|
+
# module Configliere ; DEFAULT_CONFIG_FILENAME = File.dirname(__FILE__)+'/commandline_script.yaml' end
|
5
|
+
require 'configliere'
|
6
|
+
|
7
|
+
Settings.use :all
|
8
|
+
|
9
|
+
puts %Q{
|
10
|
+
This is a demo of the Configliere interface. It parses all command line options to load keys, global options, etc.
|
11
|
+
|
12
|
+
Try running it as
|
13
|
+
|
14
|
+
PLACES='go' NOISES='who' ./examples/simple_script.rb --cat=hat
|
15
|
+
|
16
|
+
which should create
|
17
|
+
|
18
|
+
expect: {:password=>"zike_bike", :horton=>{:hears_a=>"who"}, :key=>"asdf", :cat=>"hat", :things=>["thing_1", "thing_2"], :rate_per_hour=>10, :places=>"go", :wocket=>"pocket"}
|
19
|
+
}
|
20
|
+
|
21
|
+
# describe and define params
|
22
|
+
Settings.define :cat, :description => 'The type of feline haberdashery to include in the story', :required => true, :type => Symbol
|
23
|
+
Settings.define :wocket, :description => 'where the wocket is residing'
|
24
|
+
Settings.define :password, :encrypted => true
|
25
|
+
|
26
|
+
# static settings
|
27
|
+
Settings :wocket => 'pocket', :key => 'asdf'
|
28
|
+
# from environment
|
29
|
+
Settings.environment_variables 'PLACES', 'NOISES' => 'horton.hears_a'
|
30
|
+
|
31
|
+
# from config file
|
32
|
+
Settings.read(File.dirname(__FILE__)+'/commandline_script.yaml')
|
33
|
+
|
34
|
+
# from finally block
|
35
|
+
Settings.finally do |c|
|
36
|
+
c.lorax = 'tree'
|
37
|
+
end
|
38
|
+
|
39
|
+
# bookkeeping
|
40
|
+
Settings.resolve!
|
41
|
+
# Get the value for param[:key] from the keyboard if missing
|
42
|
+
Settings.param_or_ask :key
|
43
|
+
|
44
|
+
# Print results
|
45
|
+
print ' actual: '
|
46
|
+
p Settings
|
47
|
+
|
48
|
+
|
49
|
+
fiddle = Configliere.new; fiddle.encrypt_pass = 'pass'
|
50
|
+
fiddle.define 'amazon.api.key', :encrypted => true
|
51
|
+
fiddle['amazon.api.encrypted_key'] = "{bo\335\256nt2Rc\016\244\216c\030\2627g\233%\300\035l\225\325\305z\207LR\333\035"
|
52
|
+
fiddle.resolve!
|
53
|
+
puts 'expect: [nil, "bite_me"]'
|
54
|
+
print "actual: "; p [fiddle['amazon.api.encrypted_key'], fiddle['amazon.api.key'], fiddle.send(:export)]
|
55
|
+
#
|
56
|
+
fiddle = Configliere.new; fiddle.encrypt_pass = 'pass'
|
57
|
+
fiddle.define 'amazon.api.key', :encrypted => true
|
58
|
+
fiddle['amazon.api.key'] = 'bite_me'
|
59
|
+
fiddle.resolve!
|
60
|
+
puts 'expect: [nil, "bite_me"]'
|
61
|
+
print "actual: "; p [fiddle['amazon.api.encrypted_key'], fiddle['amazon.api.key'], fiddle.send(:export)]
|
62
|
+
#
|
63
|
+
fiddle = Configliere.new; fiddle.encrypt_pass = 'pass'
|
64
|
+
fiddle.define 'amazon.api.key', :encrypted => true
|
65
|
+
fiddle['amazon.api.encrypted_key'] = "{bo\335\256nt2Rc\016\244\216c\030\2627g\233%\300\035l\225\325\305z\207LR\333\035"
|
66
|
+
# fiddle.resolve!
|
67
|
+
puts 'expect: ["{bo\335\256nt2Rc\016\244\216c\030\2627g\233%\300\035l\225\325\305z\207LR\333\035", nil]'
|
68
|
+
print "actual: "; p [fiddle['amazon.api.encrypted_key'], fiddle['amazon.api.key'], fiddle.send(:export)]
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
# save to disk
|
73
|
+
# you can check that :password and :api_key have been properly encrypted.
|
74
|
+
Settings.save! File.dirname(__FILE__)+'/foo.yaml'
|
data/examples/foo.yaml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems' ; $: << File.dirname(__FILE__)+'/../lib'
|
3
|
+
require 'configliere'
|
4
|
+
SCRIPT_DIR = File.dirname(__FILE__)
|
5
|
+
|
6
|
+
# Intro text
|
7
|
+
puts %Q{
|
8
|
+
This is a demo of the Configliere interface. It takse settings
|
9
|
+
Try running it as
|
10
|
+
./examples/simple_script.rb --cat=hat
|
11
|
+
with those args, we
|
12
|
+
expect: {:things=>["thing_1", "thing_2"], :rate_per_hour=>10, :cat=>"hat"}
|
13
|
+
}
|
14
|
+
|
15
|
+
# Configuration
|
16
|
+
Settings.use :commandline, :param_store, :config_blocks
|
17
|
+
Settings.read SCRIPT_DIR+'/simple_script.yaml'
|
18
|
+
|
19
|
+
Settings.resolve!
|
20
|
+
|
21
|
+
# Print results
|
22
|
+
print ' actual: '
|
23
|
+
p Settings
|
data/lib/configliere.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'configliere/core_ext'
|
2
|
+
require 'configliere/param'
|
3
|
+
|
4
|
+
module Configliere
|
5
|
+
# Where to load params given only a symbol
|
6
|
+
DEFAULT_CONFIG_FILE = ENV['HOME']+'/.configliere.yaml' unless defined?(DEFAULT_CONFIG_FILE)
|
7
|
+
# Where to load params given a bare filename
|
8
|
+
DEFAULT_CONFIG_DIR = ENV['HOME']+'/.configliere' unless defined?(DEFAULT_CONFIG_DIR)
|
9
|
+
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# delegates to Configliere::Param
|
13
|
+
def self.new *args, &block
|
14
|
+
Configliere::Param.new *args, &block
|
15
|
+
end
|
16
|
+
|
17
|
+
ALL_MIXINS = [:define, :encrypted, :environment, :param_store, :commandline, :config_blocks]
|
18
|
+
def self.use *mixins
|
19
|
+
mixins = ALL_MIXINS if mixins.include?(:all)
|
20
|
+
mixins.each do |mixin|
|
21
|
+
require "configliere/#{mixin}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Defines a global config object
|
27
|
+
Settings = Configliere.new unless defined?(Settings)
|
28
|
+
|
29
|
+
#
|
30
|
+
# Allows the
|
31
|
+
# Config :this => that, :cat => :hat
|
32
|
+
# pattern.
|
33
|
+
#
|
34
|
+
def Settings *args
|
35
|
+
Settings.defaults *args
|
36
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# Configliere.use :define
|
2
|
+
module Configliere
|
3
|
+
|
4
|
+
#
|
5
|
+
# Command line tool to manage param info
|
6
|
+
#
|
7
|
+
module Commandline
|
8
|
+
attr_accessor :rest
|
9
|
+
|
10
|
+
def resolve!
|
11
|
+
process_argv!
|
12
|
+
dump_help_if_requested
|
13
|
+
begin ; super() ; rescue NoMethodError ; nil ; end
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Parse the command-line args into the params hash.
|
18
|
+
#
|
19
|
+
# '--happy_flag' produces :happy_flag => true in the params hash
|
20
|
+
# '--foo=foo_val' produces :foo => 'foo_val' in the params hash.
|
21
|
+
# '--' Stop parsing; all remaining args are piled into :rest
|
22
|
+
#
|
23
|
+
# self.rest contains all arguments that don't start with a '--'
|
24
|
+
# and all args following the '--' sentinel if any.
|
25
|
+
#
|
26
|
+
def process_argv!
|
27
|
+
args = ARGV.dup
|
28
|
+
self.rest = []
|
29
|
+
until args.empty? do
|
30
|
+
arg = args.shift
|
31
|
+
case
|
32
|
+
when arg == '--'
|
33
|
+
self.rest += args
|
34
|
+
break
|
35
|
+
when arg =~ /\A--([\w\-\.]+)(?:=(.*))?\z/
|
36
|
+
param, val = [$1, $2]
|
37
|
+
param.gsub!(/\-/, '.')
|
38
|
+
if val == nil then val = true # --flag option on its own means 'set that option'
|
39
|
+
elsif val == '' then val = nil end # --flag='' the explicit empty string means nil
|
40
|
+
self[param] = val
|
41
|
+
else
|
42
|
+
self.rest << arg
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# If your script uses the 'script_name verb [...params...]'
|
48
|
+
# pattern, list the commands here:
|
49
|
+
COMMANDS= {}
|
50
|
+
|
51
|
+
# Configliere internal params
|
52
|
+
def define_special_params
|
53
|
+
Settings.define :encrypt_pass, :description => "Passphrase to extract encrypted config params.", :internal => true
|
54
|
+
end
|
55
|
+
|
56
|
+
# All commandline name-value params that aren't internal to configliere
|
57
|
+
def normal_params
|
58
|
+
reject{|param, val| param_definitions[param][:internal] }
|
59
|
+
end
|
60
|
+
|
61
|
+
# die with a warning
|
62
|
+
def die str
|
63
|
+
puts help
|
64
|
+
warn "\n****\n#{str}\n****"
|
65
|
+
exit -1
|
66
|
+
end
|
67
|
+
|
68
|
+
# Retrieve the given param, or prompt for it
|
69
|
+
def param_or_ask attr, hint=nil
|
70
|
+
return self[attr] if include?(attr)
|
71
|
+
require 'highline/import'
|
72
|
+
self[attr] = ask("#{attr}"+(hint ? " for #{hint}?" : '?'))
|
73
|
+
end
|
74
|
+
|
75
|
+
def help
|
76
|
+
help_str = [ usage ]
|
77
|
+
help_str += [ "\nParams:", descriptions.map{|param, desc| " %-20s %s"%[param.to_s+':', desc]}.join("\n"), ] if respond_to?(:descriptions)
|
78
|
+
# help_str += ["\nCommands", commands.map{|cmd, desc| " %-20s %s"%[cmd.to_s+':', desc]}.join("\n")] if respond_to?(:commands)
|
79
|
+
help_str += [ "\nEnvironment Variables:", params_from_environment.map{|param, env| " %-20s %s"%[env.to_s+':', param]}.join("\n"), ] if respond_to?(:params_from_environment)
|
80
|
+
help_str.join("\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
# Usage line
|
84
|
+
def usage
|
85
|
+
%Q{usage: #{File.basename($0)} [...--param=val...]}
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
# Ouput the help string if requested
|
91
|
+
def dump_help_if_requested
|
92
|
+
return unless self[:help]
|
93
|
+
$stderr.puts help
|
94
|
+
exit
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Param.class_eval do
|
99
|
+
# include read / save operations
|
100
|
+
include Commandline
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'logger'
|
2
|
+
Log = Logger.new(STDERR) unless defined?(Log)
|
3
|
+
module Configliere
|
4
|
+
class CommandClient < Client
|
5
|
+
attr_accessor :command
|
6
|
+
COMMANDS[:help] = "Show this usage info"
|
7
|
+
|
8
|
+
def usage
|
9
|
+
%Q{usage: #{File.basename($0)} command [...--option=val...]
|
10
|
+
where
|
11
|
+
command: One of: #{COMMANDS.keys[0..-2].join(', ')} or #{COMMANDS.keys.last}
|
12
|
+
|
13
|
+
Configuration taken from #{configliere_file} by default.}
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Run the command
|
18
|
+
#
|
19
|
+
def run
|
20
|
+
dump_help_if_requested
|
21
|
+
# Check options
|
22
|
+
die "Please give a command and the name of the configliere group to encrypt" unless command
|
23
|
+
die "Please give the name of the configliere group to encrypt" unless handle || ([:help, :list].include?(command))
|
24
|
+
die "\n**\nUnknown command\n**\n" unless COMMANDS.include?(command)
|
25
|
+
#
|
26
|
+
self.send(command)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
Configliere.use :define
|
2
|
+
module Configliere
|
3
|
+
module Block
|
4
|
+
# Config blocks to be executed at end of resolution (just before validation)
|
5
|
+
attr_accessor :final_blocks
|
6
|
+
def final_blocks
|
7
|
+
@final_blocks ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param param the setting to describe. Either a simple symbol or a dotted param string.
|
11
|
+
# @param definitions the defineables to set (:description, :type, :encrypted, etc.)
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# Settings.define :dest_time, :type => Date, :description => 'Arrival time. If only a date is given, the current time of day on that date is assumed.'
|
15
|
+
# Settings.define 'delorean.power_source', :description => 'Delorean subsytem supplying power to the Flux Capacitor.'
|
16
|
+
# Settings.define :password, :required => true, :obscure => true
|
17
|
+
#
|
18
|
+
def finally &block
|
19
|
+
self.final_blocks << block
|
20
|
+
end
|
21
|
+
|
22
|
+
# calls superclass resolution
|
23
|
+
def resolve!
|
24
|
+
begin ; super() ; rescue NoMethodError ; nil ; end
|
25
|
+
resolve_finally_blocks!
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
def resolve_finally_blocks!
|
31
|
+
final_blocks.each do |block|
|
32
|
+
block.call(self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
Param.class_eval do
|
39
|
+
include Configliere::Block
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'configliere/core_ext/hash'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#
|
2
|
+
# core_ext/hash.rb -- hash extensions
|
3
|
+
#
|
4
|
+
class Hash
|
5
|
+
|
6
|
+
# lambda for recursive merges
|
7
|
+
Hash::DEEP_MERGER = proc do |key,v1,v2|
|
8
|
+
(v1.respond_to?(:merge) && v2.respond_to?(:merge)) ? v1.merge(v2.compact, &Hash::DEEP_MERGER) : (v2.nil? ? v1 : v2)
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# Merge hashes recursively.
|
13
|
+
# Nothing special happens to array values
|
14
|
+
#
|
15
|
+
# x = { :subhash => { 1 => :val_from_x, 222 => :only_in_x, 333 => :only_in_x }, :scalar => :scalar_from_x}
|
16
|
+
# y = { :subhash => { 1 => :val_from_y, 999 => :only_in_y }, :scalar => :scalar_from_y }
|
17
|
+
# x.deep_merge y
|
18
|
+
# => {:subhash=>{1=>:val_from_y, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_y}
|
19
|
+
# y.deep_merge x
|
20
|
+
# => {:subhash=>{1=>:val_from_x, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_x}
|
21
|
+
#
|
22
|
+
# Nil values always lose.
|
23
|
+
#
|
24
|
+
# x = {:subhash=>{:nil_in_x=>nil, 1=>:val1,}, :nil_in_x=>nil}
|
25
|
+
# y = {:subhash=>{:nil_in_x=>5}, :nil_in_x=>5}
|
26
|
+
# y.deep_merge x
|
27
|
+
# => {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
|
28
|
+
# x.deep_merge y
|
29
|
+
# => {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
|
30
|
+
#
|
31
|
+
def deep_merge hsh2
|
32
|
+
merge hsh2, &Hash::DEEP_MERGER
|
33
|
+
end
|
34
|
+
|
35
|
+
def deep_merge! hsh2
|
36
|
+
merge! hsh2, &Hash::DEEP_MERGER
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Treat hash as tree of hashes:
|
41
|
+
#
|
42
|
+
# x = { 1 => :val, :subhash => { 1 => :val1 } }
|
43
|
+
# x.deep_set(:subhash, :cat, :hat)
|
44
|
+
# # => { 1 => :val, :subhash => { 1 => :val1, :cat => :hat } }
|
45
|
+
# x.deep_set(:subhash, 1, :newval)
|
46
|
+
# # => { 1 => :val, :subhash => { 1 => :newval, :cat => :hat } }
|
47
|
+
#
|
48
|
+
#
|
49
|
+
def deep_set *args
|
50
|
+
val = args.pop
|
51
|
+
last_key = args.pop
|
52
|
+
# dig down to last subtree (building out if necessary)
|
53
|
+
hsh = args.empty? ? self : args.inject(self){|hsh, key| hsh[key] ||= {} }
|
54
|
+
# set leaf value
|
55
|
+
hsh[last_key] = val
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Treat hash as tree of hashes:
|
60
|
+
#
|
61
|
+
# x = { 1 => :val, :subhash => { 1 => :val1 } }
|
62
|
+
# x.deep_get(:subhash, 1)
|
63
|
+
# # => :val
|
64
|
+
# x.deep_get(:subhash, 2)
|
65
|
+
# # => nil
|
66
|
+
# x.deep_get(:subhash, 2, 3)
|
67
|
+
# # => nil
|
68
|
+
# x.deep_get(:subhash, 2)
|
69
|
+
# # => nil
|
70
|
+
#
|
71
|
+
def deep_get *args
|
72
|
+
last_key = args.pop
|
73
|
+
# dig down to last subtree (building out if necessary)
|
74
|
+
hsh = args.inject(self){|hsh, key| hsh[key] || {} }
|
75
|
+
# get leaf value
|
76
|
+
hsh[last_key]
|
77
|
+
end
|
78
|
+
|
79
|
+
def deep_delete *args
|
80
|
+
last_key = args.pop
|
81
|
+
last_hsh = args.empty? ? self : (deep_get(*args)||{})
|
82
|
+
last_hsh.delete(last_key)
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# remove all key-value pairs where the value is nil
|
87
|
+
#
|
88
|
+
def compact
|
89
|
+
reject{|key,val| val.nil? }
|
90
|
+
end
|
91
|
+
#
|
92
|
+
# Replace the hash with its compacted self
|
93
|
+
#
|
94
|
+
def compact!
|
95
|
+
replace(compact)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|