configurable 0.7.0 → 1.0.0

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