configliere 0.3.4 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.document +3 -0
  2. data/.watchr +20 -0
  3. data/CHANGELOG.textile +99 -3
  4. data/Gemfile +26 -0
  5. data/Gemfile.lock +54 -0
  6. data/README.textile +162 -138
  7. data/Rakefile +30 -21
  8. data/VERSION +1 -1
  9. data/bin/configliere +77 -77
  10. data/bin/configliere-decrypt +85 -0
  11. data/bin/configliere-delete +85 -0
  12. data/bin/configliere-dump +85 -0
  13. data/bin/configliere-encrypt +85 -0
  14. data/bin/configliere-list +85 -0
  15. data/bin/configliere-set +85 -0
  16. data/configliere.gemspec +53 -23
  17. data/examples/config_block_script.rb +9 -2
  18. data/examples/encrypted_script.rb +28 -16
  19. data/examples/env_var_script.rb +2 -2
  20. data/examples/help_message_demo.rb +16 -0
  21. data/examples/independent_config.rb +28 -0
  22. data/examples/prompt.rb +23 -0
  23. data/examples/simple_script.rb +28 -15
  24. data/examples/simple_script.yaml +1 -1
  25. data/lib/configliere.rb +22 -24
  26. data/lib/configliere/commandline.rb +135 -116
  27. data/lib/configliere/commands.rb +38 -54
  28. data/lib/configliere/config_block.rb +4 -2
  29. data/lib/configliere/config_file.rb +30 -52
  30. data/lib/configliere/crypter.rb +8 -5
  31. data/lib/configliere/deep_hash.rb +368 -0
  32. data/lib/configliere/define.rb +83 -89
  33. data/lib/configliere/encrypted.rb +17 -18
  34. data/lib/configliere/env_var.rb +5 -7
  35. data/lib/configliere/param.rb +37 -64
  36. data/lib/configliere/prompt.rb +23 -0
  37. data/spec/configliere/commandline_spec.rb +156 -57
  38. data/spec/configliere/commands_spec.rb +75 -30
  39. data/spec/configliere/config_block_spec.rb +10 -1
  40. data/spec/configliere/config_file_spec.rb +83 -55
  41. data/spec/configliere/crypter_spec.rb +3 -2
  42. data/spec/configliere/deep_hash_spec.rb +401 -0
  43. data/spec/configliere/define_spec.rb +121 -42
  44. data/spec/configliere/encrypted_spec.rb +53 -20
  45. data/spec/configliere/env_var_spec.rb +24 -4
  46. data/spec/configliere/param_spec.rb +25 -27
  47. data/spec/configliere/prompt_spec.rb +50 -0
  48. data/spec/configliere_spec.rb +3 -9
  49. data/spec/spec_helper.rb +17 -6
  50. metadata +110 -35
  51. data/lib/configliere/core_ext.rb +0 -2
  52. data/lib/configliere/core_ext/blank.rb +0 -93
  53. data/lib/configliere/core_ext/hash.rb +0 -108
  54. data/lib/configliere/core_ext/sash.rb +0 -170
  55. data/spec/configliere/core_ext/hash_spec.rb +0 -78
  56. data/spec/configliere/core_ext/sash_spec.rb +0 -312
@@ -1,8 +1,16 @@
1
1
  module Configliere
2
2
  module Define
3
- # Definitions for params: :description, :type, :encrypted, etc.
4
- attr_writer :param_definitions
5
3
 
4
+ # Define arbitrary attributes of a param, notably:
5
+ #
6
+ # [:description] Documentation for the param, used in the --help message
7
+ # [:default] Sets a default value (applied immediately)
8
+ # [:env_var] Environment variable to adopt (applied immediately, and after +:default+)
9
+ # [:encrypted] Obscures/Extracts the contents of this param when serialized
10
+ # [:type] Converts param's value to the given type, just before the finally block is called
11
+ # [:finally] Block of code to postprocess settings or handle complex configuration.
12
+ # [:required] Raises an error if, at the end of calling resolve!, the param's value is nil.
13
+ #
6
14
  # @param param the setting to describe. Either a simple symbol or a dotted param string.
7
15
  # @param definitions the defineables to set (:description, :type, :encrypted, etc.)
8
16
  #
@@ -10,30 +18,28 @@ module Configliere
10
18
  # 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.'
11
19
  # Settings.define 'delorean.power_source', :description => 'Delorean subsytem supplying power to the Flux Capacitor.'
12
20
  # Settings.define :password, :required => true, :obscure => true
13
- #
14
- def define param, definitions={}, &block
15
- self.param_definitions[param].merge! definitions
16
- self.use(:env_var) if definitions.include?(:env_var)
17
- self.use(:encrypted) if definitions.include?(:encrypted)
18
- self.use(:config_block) if definitions.include?(:finally)
19
- self[param] = definitions[:default] if definitions.include?(:default)
20
- self.env_vars param => definitions[:env_var] if definitions.include?(:env_var)
21
- self.finally(&definitions[:finally]) if definitions.include?(:finally)
21
+ # Settings.define :danger, :finally => lambda{|c| if c[:delorean][:power_source] == 'plutonium' than c.danger = 'high' }
22
+ #
23
+ def define param, pdefs={}, &block
24
+ param = param.to_sym
25
+ definitions[param].merge! pdefs
26
+ self.use(:env_var) if pdefs.include?(:env_var)
27
+ self.use(:encrypted) if pdefs.include?(:encrypted)
28
+ self.use(:config_block) if pdefs.include?(:finally)
29
+ self[param] = pdefs[:default] if pdefs.include?(:default)
30
+ self.env_vars param => pdefs[:env_var] if pdefs.include?(:env_var)
31
+ self.finally(&pdefs[:finally]) if pdefs.include?(:finally)
22
32
  self.finally(&block) if block
23
33
  end
24
34
 
25
- def param_definitions
26
- # initialize the param_definitions as an auto-vivifying hash if it's never been set
27
- @param_definitions ||= Sash.new{|hsh, key| hsh[key] = Sash.new }
28
- end
29
-
30
- # performs type coercion
35
+ # performs type coercion, continues up the resolve! chain
31
36
  def resolve!
32
37
  resolve_types!
33
38
  super()
34
39
  self
35
40
  end
36
41
 
42
+ # ensures required types are defined, continues up the validate! chain
37
43
  def validate!
38
44
  validate_requireds!
39
45
  super()
@@ -42,25 +48,55 @@ module Configliere
42
48
 
43
49
  # ===========================================================================
44
50
  #
45
- # Describe params with
46
- #
47
- # Settings.define :param, :description => '...'
51
+ # Helpers for retrieving definitions
48
52
  #
49
53
 
50
- # gets the description (if any) for the param
51
- # @param param the setting to describe. Either a simple symbol or a dotted param string.
52
- def description_for param
53
- param_definitions[param][:description]
54
+ private
55
+ def definitions
56
+ @definitions ||= Hash.new{|hsh, key| hsh[key.to_sym] = Hash.new }
54
57
  end
58
+ public
55
59
 
56
- # All described params with their descriptions
57
- def descriptions
58
- definitions_for(:description).reject{ |param, desc| param_definitions[param][:no_help] }
60
+ # Is the param defined?
61
+ def has_definition? param
62
+ definitions.has_key?(param.to_sym)
63
+ end
64
+
65
+ # all params with a value for the given aspect
66
+ #
67
+ # @example
68
+ # @config.define :has_description, :description => 'desc 1', :foo => 'bar'
69
+ # #
70
+ # definition_of(:has_description)
71
+ # # => {:description => 'desc 1', :foo => 'bar'}
72
+ # definition_of(:has_description, :description)
73
+ # # => 'desc 1'
74
+ #
75
+ # @param aspect [Symbol] the aspect to list (:description, :type, :encrypted, etc.)
76
+ # @return [Hash, Object]
77
+ def definition_of(param, attr=nil)
78
+ attr ? definitions[param.to_sym][attr] : definitions[param.to_sym]
59
79
  end
60
80
 
61
- # List of params that have descriptions
62
- def described_params
63
- params_with(:description)
81
+ # a hash holding every param with that aspect and its definition
82
+ #
83
+ # @example
84
+ # @config.define :has_description, :description => 'desc 1'
85
+ # @config.define :also_has_description, :description => 'desc 2'
86
+ # @config.define :no_description, :something_else => 'foo'
87
+ # #
88
+ # params_with(:description)
89
+ # # => { :has_description => 'desc 1', :also_has_description => 'desc 2' }
90
+ #
91
+ # @param aspect [Symbol] the aspect to list (:description, :type, :encrypted, etc.)
92
+ # @return [Hash]
93
+ def params_with(aspect)
94
+ hsh = {}
95
+ definitions.each do |param_name, param_def|
96
+ next unless param_def.has_key?(aspect)
97
+ hsh[param_name] = definition_of(param_name, aspect)
98
+ end
99
+ hsh
64
100
  end
65
101
 
66
102
  # ===========================================================================
@@ -72,44 +108,28 @@ module Configliere
72
108
  # Settings.define :param, :type => Date
73
109
  #
74
110
 
75
- def type_for param
76
- param_definitions[param][:type]
77
- end
78
-
79
- # All typed params with their descriptions
80
- def typed_params
81
- definitions_for(:type)
82
- end
83
-
84
- # List of params that have descriptions
85
- def typed_param_names
86
- params_with(:type)
87
- end
88
-
89
- require 'date'
90
-
91
111
  # Coerce all params with types defined to their proper form
92
112
  def resolve_types!
93
- typed_params.each do |param, type|
94
- val = self[param]
113
+ params_with(:type).each do |param, type|
114
+ val = self[param]
95
115
  case
96
116
  when val.nil? then val = nil
97
117
  when (type == :boolean) then
98
118
  if ['false', false, 0, '0', ''].include?(val) then val = false else val = true end
99
- when ((type == Array) && val.is_a?(String))
100
- val = val.split(",") rescue nil
101
- # following types map blank to nil
102
- when (val.blank?) then val = nil
119
+ when (type == Array)
120
+ if val.is_a?(String) then val = val.split(",") rescue nil ; end
121
+ # for all following types, map blank/empty to nil
122
+ when (val.respond_to?(:empty?) && val.empty?) then val = nil
103
123
  when (type == :filename) then val = File.expand_path(val)
104
124
  when (type == Float) then val = val.to_f
105
125
  when (type == Integer) then val = val.to_i
106
126
  when (type == Symbol) then val = val.to_s.to_sym rescue nil
127
+ when (type == Regexp) then val = Regexp.new(val) rescue nil
107
128
  when ((val.to_s == 'now') && (type == Date)) then val = Date.today
108
129
  when ((val.to_s == 'now') && (type == DateTime)) then val = DateTime.now
109
- when (type == Date) then val = Date.parse(val) rescue nil
110
- when (type == DateTime) then val = DateTime.parse(val) rescue nil
111
- when (type == Regexp) then val = Regexp.new(val) rescue nil
112
- else # nothing
130
+ when ((val.to_s == 'now') && (type == Time)) then val = Time.now
131
+ when [Date, Time, DateTime].include?(type) then val = type.parse(val) rescue nil
132
+ else true # nothing
113
133
  end
114
134
  self[param] = val
115
135
  end
@@ -124,41 +144,14 @@ module Configliere
124
144
  # Settings.define :param, :required => true
125
145
  #
126
146
 
127
- # List of params that are required
128
- # @return [Array] list of required params
129
- def required_params
130
- params_with(:required)
131
- end
132
-
133
147
  # Check that all required params are present.
134
148
  def validate_requireds!
135
149
  missing = []
136
- required_params.each do |param|
137
- missing << param if self[param].nil?
150
+ params_with(:required).each do |param, is_required|
151
+ missing << param if self[param].nil? && is_required
138
152
  end
139
- raise "Missing values for:\n #{missing.map{|s| " --" + s.to_s + (description_for(s) ? (" (" + description_for(s).to_s + ") ") : '') }.sort.join("\n")}" if (! missing.empty?)
140
- end
141
-
142
- # all params with a value for the definable aspect
143
- #
144
- # @param definable the aspect to list (:description, :type, :encrypted, etc.)
145
- def params_with defineable
146
- param_definitions.keys.find_all{|param| param_definitions[param][defineable] } || []
147
- end
148
-
149
- # all params without a value for the definable aspect
150
- #
151
- # @param definable the aspect to reject (:description, :type, :encrypted, etc.)
152
- def params_without defineable
153
- param_definitions.keys.reject{|param| param_definitions[param].include?(defineable) } || []
154
- end
155
-
156
- def definitions_for defineable
157
- hsh = {}
158
- param_definitions.each do |param, defs|
159
- hsh[param] = defs[defineable] if defs[defineable]
160
- end
161
- hsh
153
+ return if missing.empty?
154
+ raise "Missing values for: #{missing.map{|pn| d = definition_of(pn, :description) ; (d ? "#{pn} (#{d})" : pn.to_s) }.sort.join(", ")}"
162
155
  end
163
156
 
164
157
  # Pretend that any #define'd parameter is a method
@@ -170,7 +163,7 @@ module Configliere
170
163
  def method_missing meth, *args
171
164
  meth.to_s =~ /^(\w+)(=)?$/ or return super
172
165
  name, setter = [$1.to_sym, $2]
173
- return(super) unless param_definitions.include?(name)
166
+ return(super) unless has_definition?(name)
174
167
  if setter && (args.size == 1)
175
168
  self[name] = args.first
176
169
  elsif (!setter) && args.empty?
@@ -179,8 +172,9 @@ module Configliere
179
172
  end
180
173
 
181
174
  end
175
+
176
+ # Define is included by default
182
177
  Param.class_eval do
183
178
  include Configliere::Define
184
179
  end
185
180
  end
186
-
@@ -1,9 +1,6 @@
1
- Configliere.use :config_file, :define, :crypter
2
-
1
+ require 'configliere/crypter'
3
2
  module Configliere
4
3
  module EncryptedParam
5
- # The password used in encrypting params during serialization
6
- attr_accessor :encrypt_pass
7
4
 
8
5
  # decrypts any encrypted params
9
6
  # then calls the next step in the resolve! chain.
@@ -17,7 +14,7 @@ module Configliere
17
14
  def resolve_encrypted!
18
15
  remove_and_adopt_encrypt_pass_param_if_any!
19
16
  encrypted_params.each do |param|
20
- encrypted_val = deep_delete(*dotted_to_encrypted_keys(param)) or next
17
+ encrypted_val = deep_delete(*encrypted_key_path(param)) or next
21
18
  self[param] = self.decrypted(encrypted_val)
22
19
  end
23
20
  end
@@ -27,52 +24,54 @@ module Configliere
27
24
  # @example
28
25
  # Settings.defaults :username=>"mysql_username", :password=>"mysql_password"
29
26
  # Settings.define :password, :encrypted => true
30
- # Settings.exportable
27
+ # Settings.export
31
28
  # #=> {:username => 'mysql_username', :password=>"\345?r`\222\021"\210\312\331\256\356\351\037\367\326" }
32
29
  def export
30
+ remove_and_adopt_encrypt_pass_param_if_any!
33
31
  hsh = super()
34
32
  encrypted_params.each do |param|
35
33
  val = hsh.deep_delete(*convert_key(param)) or next
36
- hsh.deep_set( *(dotted_to_encrypted_keys(param) | [encrypted(val)]) )
34
+ hsh.deep_set( *(encrypted_key_path(param) | [encrypted(val)]) )
37
35
  end
38
36
  hsh
39
37
  end
40
38
 
41
39
  # if :encrypted_pass was set as a param, remove it from the hash and set it as an attribute
42
40
  def remove_and_adopt_encrypt_pass_param_if_any!
43
- @encrypt_pass = self.delete(:encrypt_pass) if self[:encrypt_pass]
41
+ @encrypt_pass ||= self.delete(:encrypt_pass) if self[:encrypt_pass]
42
+ @encrypt_pass ||= ENV['ENCRYPT_PASS'] if ENV['ENCRYPT_PASS']
44
43
  end
45
44
 
46
45
  # the chain of symbol keys for a dotted path key,
47
46
  # prefixing the last one with "encrypted_"
48
47
  #
49
48
  # @example
50
- # dotted_to_encrypted_keys('amazon.api.key')
49
+ # encrypted_key_path('amazon.api.key')
51
50
  # #=> [:amazon, :api, :encrypted_key]
52
- def dotted_to_encrypted_keys param
53
- encrypted_path = convert_key(param).dup
51
+ def encrypted_key_path param
52
+ encrypted_path = Array(convert_key(param))
54
53
  encrypted_path[-1] = "encrypted_#{encrypted_path.last}".to_sym
55
54
  encrypted_path
56
55
  end
57
56
 
58
57
  # list of all params to encrypt on serialization
59
58
  def encrypted_params
60
- params_with(:encrypted)
59
+ params_with(:encrypted).keys.select{|p| definition_of(p, :encrypted) }
61
60
  end
62
61
 
63
62
  def decrypted val
64
- return val if val.blank?
65
- Configliere::Crypter.decrypt(val, encrypt_pass)
63
+ return val.to_s if val.to_s.empty?
64
+ Configliere::Crypter.decrypt(val, @encrypt_pass)
66
65
  end
67
66
 
68
67
  def encrypted val
69
68
  return unless val
70
- Configliere::Crypter.encrypt(val, encrypt_pass)
69
+ Configliere::Crypter.encrypt(val, @encrypt_pass)
71
70
  end
72
71
  end
73
72
 
74
- class Param
75
- include EncryptedParam
73
+ Param.on_use(:encrypted) do
74
+ use :config_file, :define
75
+ extend EncryptedParam
76
76
  end
77
77
  end
78
-
@@ -1,5 +1,3 @@
1
- require 'yaml'
2
- Configliere.use :define
3
1
  module Configliere
4
2
  #
5
3
  # EnvVar -- load configuration from environment variables
@@ -18,19 +16,19 @@ module Configliere
18
16
  end
19
17
  end
20
18
  end
21
-
19
+
22
20
  protected
23
21
  def adopt_env_var! param, env
24
22
  env = env.to_s
25
- param_definitions[param][:env_var] ||= env
23
+ definition_of(param)[:env_var] ||= env
26
24
  val = ENV[env]
27
25
  self[param] = val if val
28
26
  end
29
27
  end
30
28
 
31
- Param.class_eval do
32
- # include read / save operations
33
- include EnvVar
29
+ Param.on_use(:env_var) do
30
+ use :commandline
31
+ extend Configliere::EnvVar
34
32
  end
35
33
  end
36
34
 
@@ -1,15 +1,24 @@
1
- require 'configliere/core_ext/sash.rb'
2
1
  module Configliere
3
- class ParamParent < ::Hash
4
- # default export method: self
2
+ #
3
+ # We want to be able to call super() on these methods in all included models,
4
+ # so we define them in this parent shim class.
5
+ #
6
+ class ParamParent < DeepHash
7
+ # default export method: dup of self
5
8
  def export
6
- to_hash
9
+ dup.tap{|hsh| hsh.each{|k,v| hsh[k] = v.respond_to?(:export) ? v.export : v } }
7
10
  end
11
+
8
12
  # terminate resolution chain
13
+ # @return self
9
14
  def resolve!
15
+ self
10
16
  end
11
- # terminate validation chain
17
+
18
+ # terminate validation chain.
19
+ # @return self
12
20
  def validate!
21
+ self
13
22
  end
14
23
  end
15
24
 
@@ -21,26 +30,6 @@ module Configliere
21
30
  #
22
31
  class Param < Configliere::ParamParent
23
32
 
24
- # @param constructor<Object>
25
- # The default value for the mash. Defaults to an empty hash.
26
- #
27
- # @details [Alternatives]
28
- # If constructor is a Hash, a new mash will be created based on the keys of
29
- # the hash and no default value will be set.
30
- def initialize(constructor = {})
31
- if constructor.is_a?(Hash)
32
- super()
33
- update(constructor) unless constructor.empty?
34
- else
35
- super(constructor)
36
- end
37
- end
38
-
39
- # @return [Hash] The mash as a Hash with string keys.
40
- def to_hash
41
- Hash.new(default).merge(self)
42
- end
43
-
44
33
  #
45
34
  # Incorporates the given settings.
46
35
  # alias for deep_merge!
@@ -53,60 +42,44 @@ module Configliere
53
42
  # Settings.defaults :basket => :tasket, :moon => { :cow => :smiling }
54
43
  # Config #=> { :hat => :cat, :basket => :tasket, :moon => { :man => :smiling, :cow => :jumping } }
55
44
  #
45
+ # @return self
56
46
  def defaults hsh
57
47
  deep_merge! hsh
48
+ self
58
49
  end
59
50
 
60
- # Finalize and validate params
51
+ # Finalize and validate params. All include'd modules and subclasses *must* call super()
52
+ # @return self
61
53
  def resolve!
62
54
  super()
63
55
  validate!
56
+ self
64
57
  end
65
- # Check that all defined params are valid
58
+
59
+ # Check that all defined params are valid. All include'd modules and subclasses *must*call super()
60
+ # @return self
66
61
  def validate!
67
62
  super()
63
+ self
68
64
  end
69
65
 
70
- def []= param, val
71
- if param =~ /\./
72
- return deep_set( *(convert_key(param) | [val]) )
73
- else
74
- super param, val
66
+ def use *mws
67
+ hsh = mws.pop if mws.last.is_a?(Hash)
68
+ Configliere.use(*mws)
69
+ mws.each do |mw|
70
+ if (blk = USE_HANDLERS[mw])
71
+ instance_eval(&blk)
72
+ end
75
73
  end
74
+ self.deep_merge!(hsh) if hsh
75
+ self
76
76
  end
77
77
 
78
- def [] param
79
- if param =~ /\./
80
- return deep_get( *convert_key(param) )
81
- else
82
- super param
83
- end
84
- end
85
-
86
- def delete param
87
- if param =~ /\./
88
- return deep_delete( *convert_key(param) )
89
- else
90
- super param
91
- end
92
- end
93
-
94
- def use *args
95
- hsh = args.pop if args.last.is_a?(Hash)
96
- Configliere.use(*args)
97
- self.deep_merge!(hsh) unless hsh.nil?
98
- end
99
-
100
- protected
101
- # @param key<Object> The key to convert.
102
- #
103
- # @param [Object]
104
- # The converted key. A dotted param ('moon.cheese.type') becomes
105
- # an array of sequential keys for deep_set and deep_get
106
- #
107
- # @api private
108
- def convert_key dotted
109
- dotted.to_s.split(".").map{|key| key.to_sym }
78
+ # @private
79
+ USE_HANDLERS = {} unless defined?(USE_HANDLERS)
80
+ # Block executed when use is invoked
81
+ def self.on_use mw, &block
82
+ USE_HANDLERS[mw] = block
110
83
  end
111
84
 
112
85
  end