configliere 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|