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.
@@ -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