configurable 0.7.0 → 1.0.0

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.
Files changed (38) hide show
  1. data/Help/Command Line.rdoc +141 -0
  2. data/Help/Config Syntax.rdoc +229 -0
  3. data/Help/Config Types.rdoc +143 -0
  4. data/{History → History.rdoc} +9 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.rdoc +144 -0
  7. data/lib/configurable.rb +7 -270
  8. data/lib/configurable/class_methods.rb +344 -367
  9. data/lib/configurable/config_classes.rb +3 -0
  10. data/lib/configurable/config_classes/list_config.rb +26 -0
  11. data/lib/configurable/config_classes/nest_config.rb +50 -0
  12. data/lib/configurable/config_classes/scalar_config.rb +91 -0
  13. data/lib/configurable/config_hash.rb +87 -112
  14. data/lib/configurable/config_types.rb +6 -0
  15. data/lib/configurable/config_types/boolean_type.rb +22 -0
  16. data/lib/configurable/config_types/float_type.rb +11 -0
  17. data/lib/configurable/config_types/integer_type.rb +11 -0
  18. data/lib/configurable/config_types/nest_type.rb +39 -0
  19. data/lib/configurable/config_types/object_type.rb +58 -0
  20. data/lib/configurable/config_types/string_type.rb +15 -0
  21. data/lib/configurable/conversions.rb +91 -0
  22. data/lib/configurable/module_methods.rb +0 -1
  23. data/lib/configurable/version.rb +1 -5
  24. metadata +73 -30
  25. data/README +0 -207
  26. data/lib/cdoc.rb +0 -413
  27. data/lib/cdoc/cdoc_html_generator.rb +0 -38
  28. data/lib/cdoc/cdoc_html_template.rb +0 -42
  29. data/lib/config_parser.rb +0 -563
  30. data/lib/config_parser/option.rb +0 -108
  31. data/lib/config_parser/switch.rb +0 -44
  32. data/lib/config_parser/utils.rb +0 -177
  33. data/lib/configurable/config.rb +0 -97
  34. data/lib/configurable/indifferent_access.rb +0 -35
  35. data/lib/configurable/nest_config.rb +0 -78
  36. data/lib/configurable/ordered_hash_patch.rb +0 -85
  37. data/lib/configurable/utils.rb +0 -186
  38. data/lib/configurable/validation.rb +0 -768
@@ -0,0 +1,3 @@
1
+ require 'configurable/config_classes/scalar_config'
2
+ require 'configurable/config_classes/list_config'
3
+ require 'configurable/config_classes/nest_config'
@@ -0,0 +1,26 @@
1
+ module Configurable
2
+ module ConfigClasses
3
+ class ListConfig < ScalarConfig
4
+
5
+ def initialize(key, attrs={})
6
+ unless attrs.has_key?(:default)
7
+ attrs[:default] = []
8
+ end
9
+
10
+ super
11
+ end
12
+
13
+ def cast(values)
14
+ results = []
15
+ values.each {|value| results << super(value) }
16
+ results
17
+ end
18
+
19
+ def uncast(values)
20
+ results = []
21
+ values.each {|value| results << super(value) }
22
+ results
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ module Configurable
2
+ module ConfigClasses
3
+
4
+ # Represents a config where the input is expected to be Configurable.
5
+ class NestConfig < ScalarConfig
6
+
7
+ def initialize(key, attrs={})
8
+ unless attrs.has_key?(:default)
9
+ attrs[:default] = {}
10
+ end
11
+
12
+ unless attrs.has_key?(:type)
13
+ attrs[:type] = NestType.new(attrs)
14
+ end
15
+
16
+ super
17
+
18
+ unless type.respond_to?(:init)
19
+ raise "invalid type for #{self}: #{type.inspect}"
20
+ end
21
+ end
22
+
23
+ # Calls the reader on the reciever to retreive an instance of the
24
+ # configurable_class and returns it's config. Returns nil if the reader
25
+ # returns nil.
26
+ def get(receiver)
27
+ if configurable = receiver.send(reader)
28
+ configurable.config
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+ # Calls the reader on the reciever to retrieve an instance of the
35
+ # configurable_class, and reconfigures it with value. The instance will
36
+ # be initialized by init if necessary.
37
+ #
38
+ # If value is an instance of the configurable_class, then it will be set
39
+ # by calling writer.
40
+ def set(receiver, value)
41
+ unless value.respond_to?(:config)
42
+ value = default.merge(value)
43
+ value = type.init(value)
44
+ end
45
+
46
+ receiver.send(writer, value)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,91 @@
1
+ require 'configurable/config_types'
2
+
3
+ module Configurable
4
+ module ConfigClasses
5
+ # ConfigClasses are used by ConfigHash to delegate get/set configs on a
6
+ # receiver and to map configs between user interfaces.
7
+ class ScalarConfig
8
+ include ConfigTypes
9
+
10
+ # The config key, used as a hash key for access.
11
+ attr_reader :key
12
+
13
+ # The config name used in interfaces where only word-based names are
14
+ # appropriate. Names are strings consisting of only word characters.
15
+ attr_reader :name
16
+
17
+ # The reader method called on a receiver during get.
18
+ attr_reader :reader
19
+
20
+ # The writer method called on a receiver during set.
21
+ attr_reader :writer
22
+
23
+ # The default config value.
24
+ attr_reader :default
25
+
26
+ # The config type for self (defaults to an ObjectType)
27
+ attr_reader :type
28
+
29
+ # A hash of information used to render self in various contexts.
30
+ attr_reader :metadata
31
+
32
+ # Initializes a new Config. Specify attributes like default, reader,
33
+ # writer, type, etc. within attrs.
34
+ def initialize(key, attrs={})
35
+ @key = key
36
+ @name = attrs[:name] || @key.to_s
37
+ check_name(@name)
38
+
39
+ @default = attrs[:default]
40
+ @type = attrs[:type] || ObjectType.new
41
+ check_default(@default)
42
+
43
+ @reader = (attrs[:reader] || name).to_sym
44
+ @writer = (attrs[:writer] || "#{name}=").to_sym
45
+ @metadata = attrs[:metadata] || {}
46
+ end
47
+
48
+ def [](key)
49
+ metadata[key]
50
+ end
51
+
52
+ # Calls reader on the receiver and returns the result.
53
+ def get(receiver)
54
+ receiver.send(reader)
55
+ end
56
+
57
+ # Calls writer on the receiver with the value.
58
+ def set(receiver, value)
59
+ receiver.send(writer, value)
60
+ end
61
+
62
+ def cast(input)
63
+ type.cast(input)
64
+ end
65
+
66
+ def uncast(value)
67
+ type.uncast(value)
68
+ end
69
+
70
+ # Returns an inspect string.
71
+ def inspect
72
+ "#<#{self.class}:#{object_id} key=#{key} name=#{name} default=#{default.inspect} reader=#{reader} writer=#{writer} >"
73
+ end
74
+
75
+ protected
76
+
77
+ def check_name(name) # :nodoc
78
+ unless name.kind_of?(String)
79
+ raise "invalid name: #{name.inspect} (not a String)"
80
+ end
81
+
82
+ unless name =~ /\A\w+\z/
83
+ raise NameError.new("invalid name: #{name.inspect} (includes non-word characters)")
84
+ end
85
+ end
86
+
87
+ def check_default(default) # :nodoc:
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,110 +1,72 @@
1
- require 'configurable/nest_config'
2
-
3
1
  module Configurable
4
2
 
5
3
  # ConfigHash acts like a hash that maps get and set operations as specified
6
- # in a Configurable's class configurations.
7
- #
8
- # class Sample
9
- # include Configurable
10
- # config :key
11
- # end
12
- #
13
- # sample = Sample.new
14
- # sample.config.class # => ConfigHash
15
- #
16
- # sample.key = 'value'
17
- # sample.config[:key] # => 'value'
18
- #
19
- # sample.config[:key] = 'another'
20
- # sample.key # => 'another'
21
- #
22
- # Non-configuration keys are sent to an underlying data store:
23
- #
24
- # sample.config[:not_delegated] = 'value'
25
- # sample.config[:not_delegated] # => 'value'
26
- #
27
- # sample.config.store # => {:not_delegated => 'value'}
28
- # sample.config.to_hash # => {:key => 'another', :not_delegated => 'value'}
29
- #
30
- # ==== IndifferentAccess
31
- #
32
- # A ConfigHash uses the receiver class configurations to determine when and
33
- # how to map get/set operations. In cases where multiple keys need to map
34
- # in the same way (for example when you want indifferent access for strings
35
- # and symbols), simply extend the class configurations so that the AGET ([])
36
- # method returns the correct Config in all cases.
37
- #
38
- # ==== Inconsistency
39
- #
40
- # ConfigHashes can fall into an inconsistent state if you manually add values
41
- # to store that would normally be mapped to the receiver. This is both easy
42
- # to avoid and easy to repair.
43
- #
44
- # To avoid inconsistency, don't manually add values to the store and set
45
- # import_store to true during initialization. To repair inconsistency,
46
- # import the current store to self.
47
- #
48
- # config_hash = Sample.new.config
49
- # config_hash[:key] = 'a'
50
- # config_hash.store[:key] = 'b'
51
- #
52
- # config_hash[:key] # => 'a'
53
- # config_hash.to_hash # => {:key => 'b'}
54
- # config_hash.inconsistent? # => true
55
- #
56
- # config_hash.import(config_hash.store)
57
- #
58
- # config_hash[:key] # => 'b'
59
- # config_hash.to_hash # => {:key => 'b'}
60
- # config_hash.inconsistent? # => false
61
- #
4
+ # in by a receiver's class configurations. Non-configuration keys are sent
5
+ # to an underlying data store.
62
6
  class ConfigHash
63
-
7
+ # A frozen empty hash returned by configs for unbound config hashes.
8
+ EMPTY_HASH = {}.freeze
9
+
64
10
  # The bound receiver
65
11
  attr_reader :receiver
66
-
67
- # The underlying data store; setting values in store directly
68
- # can result in an inconsistent state. Use []= instead.
12
+
13
+ # The underlying data store; setting values in store directly can result
14
+ # in an inconsistent state. Use []= instead.
69
15
  attr_reader :store
70
-
71
- # Initializes a new ConfigHash. Initialize normally imports values from
72
- # store to ensure it doesn't contain entries that could be stored on the
73
- # receiver.
74
- #
75
- # Setting import_store to false allows quick initialization but can result
76
- # in an inconsistent state.
77
- def initialize(receiver, store={}, import_store=true)
78
- @receiver = receiver
16
+
17
+ # Initializes a new ConfigHash.
18
+ def initialize(store={}, receiver=nil)
79
19
  @store = store
80
-
81
- import(store) if import_store
82
- end
83
-
84
- # Returns receiver.class.configurations.
85
- def configs
86
- receiver.class.configurations
20
+ @receiver = receiver
87
21
  end
88
22
 
89
- # Imports stored values that can be mapped to the receiver. The values
90
- # are removed from store in the process. Returns self.
23
+ # Binds the configs in store to the receiver by setting each on the
24
+ # receiver (via config.set). Bound configs are removed from store.
91
25
  #
92
- # Primarily used to create a consistent state for self (see above).
93
- def import(store)
94
- configs = self.configs # cache as an optimization
95
- store.keys.each do |key|
96
- next unless config = configs[key]
97
- config.set(receiver, store.delete(key))
26
+ # Unbinds self from the current receiver, if needed.
27
+ def bind(receiver)
28
+ unbind if bound?
29
+
30
+ @receiver = receiver
31
+ configs.each_pair do |key, config|
32
+ value = store.has_key?(key) ? store.delete(key) : config.default
33
+ config.set(receiver, value)
98
34
  end
99
35
 
100
36
  self
101
37
  end
102
38
 
103
- # Returns true if the store has entries that can be stored on the
104
- # receiver.
105
- def inconsistent?
106
- configs = self.configs # cache as an optimization
107
- store.keys.any? {|key| configs[key] }
39
+ # Unbinds the configs set on receiver by getting each value (via
40
+ # config.get) and setting the result into store. Does nothing if no
41
+ # receiver is set.
42
+ def unbind
43
+ if bound?
44
+ configs.each_pair do |key, config|
45
+ store[key] = config.get(receiver)
46
+ end
47
+ @receiver = nil
48
+ end
49
+ self
50
+ end
51
+
52
+ # Returns true if bound to a receiver.
53
+ def bound?
54
+ @receiver ? true : false
55
+ end
56
+
57
+ # Returns the class configs for the bound receiver, or an empty hash if
58
+ # unbound (specifically EMPTY_HASH).
59
+ def configs
60
+ # Caching here is not necessary or preferred as configurations are
61
+ # cached on the class (which allows late inclusion of configurable
62
+ # modules to work properly).
63
+ bound? ? receiver.class.configs : EMPTY_HASH
64
+ end
65
+
66
+ # Returns true if bound to a receiver and no configs values are set in
67
+ # store (ie all config values are stored on the receiver).
68
+ def consistent?
69
+ bound? && (store.keys & configs.keys).empty?
108
70
  end
109
71
 
110
72
  # Retrieves the value for the key, either from the receiver or the store.
@@ -129,26 +91,23 @@ module Configurable
129
91
  def keys
130
92
  configs.keys | store.keys
131
93
  end
132
-
94
+
133
95
  # True if the key is a key in configs or store.
134
96
  def has_key?(key)
135
- configs[key] != nil || store.has_key?(key)
97
+ configs.has_key?(key) || store.has_key?(key)
136
98
  end
137
99
 
138
100
  # Merges another with self.
139
101
  def merge!(another)
140
- # cache configs and inline set as a significant optimization
141
102
  configs = self.configs
142
- (configs.keys | another.keys).each do |key|
143
- next unless another.has_key?(key)
144
-
145
- value = another[key]
103
+ another.each_pair do |key, value|
146
104
  if config = configs[key]
147
105
  config.set(receiver, value)
148
106
  else
149
107
  store[key] = value
150
108
  end
151
109
  end
110
+ self
152
111
  end
153
112
 
154
113
  # Calls block once for each key-value pair stored in self.
@@ -166,30 +125,46 @@ module Configurable
166
125
  def ==(another)
167
126
  another.respond_to?(:to_hash) && to_hash == another.to_hash
168
127
  end
169
-
128
+
170
129
  # Returns self as a hash. Any ConfigHash values are recursively
171
130
  # hashified, to account for nesting.
172
- def to_hash(scrub=false, &block)
131
+ def to_hash
173
132
  hash = {}
174
133
  each_pair do |key, value|
175
134
  if value.kind_of?(ConfigHash)
176
- value = value.to_hash(scrub, &block)
177
- end
178
-
179
- if scrub
180
- config = configs[key]
181
- next if config && config.default == value
135
+ value = value.to_hash
182
136
  end
183
137
 
184
- if block_given?
185
- yield(hash, key, value)
186
- else
187
- hash[key] = value
188
- end
138
+ hash[key] = value
189
139
  end
190
140
  hash
191
141
  end
192
-
142
+
143
+ # Returns self exported as a hash of raw configs (ie name keys and uncast
144
+ # values).
145
+ def export
146
+ configs.export(to_hash)
147
+ end
148
+
149
+ # Imports a hash of raw configs (ie name keys and uncast values) and
150
+ # merges the result with self.
151
+ def import(another)
152
+ merge! configs.import(another)
153
+ end
154
+
155
+ def parse(argv=ARGV, options={}, &block)
156
+ parse!(argv.dup, options, &block)
157
+ end
158
+
159
+ def parse!(argv=ARGV, options={}, &block)
160
+ parser(options, &block).parse!(argv)
161
+ end
162
+
163
+ def parser(options={}, &block)
164
+ options = {:assign_defaults => false}.merge(options)
165
+ configs.to_parser(self, options, &block)
166
+ end
167
+
193
168
  # Returns an inspection string.
194
169
  def inspect
195
170
  "#<#{self.class}:#{object_id} to_hash=#{to_hash.inspect}>"
@@ -0,0 +1,6 @@
1
+ require 'configurable/config_types/object_type'
2
+ require 'configurable/config_types/string_type'
3
+ require 'configurable/config_types/boolean_type'
4
+ require 'configurable/config_types/integer_type'
5
+ require 'configurable/config_types/float_type'
6
+ require 'configurable/config_types/nest_type'
@@ -0,0 +1,22 @@
1
+ module Configurable
2
+ module ConfigTypes
3
+ class BooleanType < ObjectType
4
+ matches TrueClass, FalseClass
5
+
6
+ # Casts the input to a boolean ie:
7
+ #
8
+ # true, 'true' => true
9
+ # false, 'false => false
10
+ #
11
+ # All other inputs raise an ArgumentError.
12
+ def cast(input)
13
+ case input
14
+ when true, false then input
15
+ when 'true' then true
16
+ when 'false' then false
17
+ else raise ArgumentError, "invalid value for boolean: #{input.inspect}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end