configliere 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'
@@ -0,0 +1,8 @@
1
+ ---
2
+ :encrypted_password: !binary |
3
+ el58XhZZYLJ+IZZ1/Q1ab/JSyI7KuYyj8+dgf2vW9Fw=
4
+
5
+ :things:
6
+ - thing_1
7
+ - thing_2
8
+ :rate_per_hour: 10
@@ -0,0 +1,15 @@
1
+ ---
2
+ :horton:
3
+ :hears_a: who
4
+ :wocket: pocket
5
+ :encrypted_password: !binary |
6
+ BdwUvxy0xWh2oB5yWKm/BZ099E6pi+6Al+k09PoPik4=
7
+
8
+ :things:
9
+ - thing_1
10
+ - thing_2
11
+ :lorax: tree
12
+ :rate_per_hour: 10
13
+ :cat: :hat
14
+ :key: asdf
15
+ :places: go
@@ -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
@@ -0,0 +1,5 @@
1
+ ---
2
+ :things:
3
+ - thing_1
4
+ - thing_2
5
+ :rate_per_hour: 10
@@ -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,4 @@
1
+ module Configliere
2
+ class Client
3
+ end
4
+ 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