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
@@ -1,3 +1,12 @@
1
+ == 1.0.0 / 2011-07-12
2
+
3
+ Significant rewrite of all internals. Configurations are easier to declare and
4
+ have a more formal type system that allows almost everything to be inferred
5
+ from the default value. The rewrite is designed to be flexible and to handle
6
+ usage in a variety of interfaces, but only time will tell.
7
+
8
+ Currently passes all tests on: 1.8.6, 1.8.7, 1.9.2, rbx, jruby
9
+
1
10
  == 0.7.0 / 2010-05-02
2
11
 
3
12
  * Utils#load_file now properly raises error for non-existant files
@@ -1,6 +1,6 @@
1
1
  Copyright (c) 2008-2009, Regents of the University of Colorado.
2
2
 
3
- Copyright (c) 2009-2010, Simon Chiang.
3
+ Copyright (c) 2009-2011, Simon Chiang.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,144 @@
1
+ = Configurable
2
+
3
+ Class configurations for the command line and web.
4
+
5
+ == Description
6
+
7
+ Configurable adds methods to declare class configurations. Configurations are
8
+ inheritable, delegate to methods, and have hash-like access. Configurable
9
+ constructs configs such that they easily map to config files, web forms, and
10
+ the command line.
11
+
12
+ == Usage
13
+
14
+ Declare configurations using the config method. Config generates accessors
15
+ that initialize with the default value.
16
+
17
+ class ConfigClass
18
+ include Configurable
19
+ config :flag, false # a flag
20
+ config :switch, true # an on/off switch
21
+ config :num, 3.14 # a number
22
+ config :lst, [1,2,3] # a list of integers
23
+ config :str, 'one' # a string
24
+ end
25
+
26
+ c = ConfigClass.new
27
+ c.str # => 'one'
28
+
29
+ Configs may also be accessed through config (a kind of delegating hash):
30
+
31
+ c.str = 'two'
32
+ c.config[:str] # => 'two'
33
+ c.config[:str] = 'three'
34
+ c.str # => 'three'
35
+
36
+ c.config.to_hash
37
+ # => {
38
+ # :flag => false,
39
+ # :switch => true,
40
+ # :num => 3.14,
41
+ # :lst => [1, 2, 3],
42
+ # :str => 'three'
43
+ # }
44
+
45
+ Configs may be imported and exported as simple objects which easily translate
46
+ to and from user interfaces, be they config files, web forms, or the command
47
+ line.
48
+
49
+ Config files:
50
+
51
+ c.config.import(
52
+ 'flag' => true,
53
+ 'num' => 6.022
54
+ )
55
+
56
+ c.config.export
57
+ # => {
58
+ # 'flag' => true,
59
+ # 'switch' => true,
60
+ # 'num' => 6.022,
61
+ # 'lst' => [1, 2, 3],
62
+ # 'str' => 'three'
63
+ # }
64
+
65
+ Web forms:
66
+
67
+ params = {
68
+ 'flag' => 'true', # checkbox
69
+ 'switch' => 'true', # radio button
70
+ 'num' => '2.71', # text input
71
+ 'lst' => ['2', '6'] # list input (lst[]=2&lst[]=6)
72
+ }
73
+
74
+ c.config.import(params).to_hash
75
+ # => {
76
+ # :flag => true,
77
+ # :switch => true,
78
+ # :num => 2.71,
79
+ # :lst => [2, 6],
80
+ # :str => 'three'
81
+ # }
82
+
83
+ Command Line:
84
+
85
+ c.config.parse %w{a --flag --no-switch --num 6.022 --lst 7 --lst 8,9 b c}
86
+ # => ['a', 'b', 'c']
87
+
88
+ c.config.to_hash
89
+ # => {
90
+ # :flag => true,
91
+ # :switch => false,
92
+ # :num => 6.022,
93
+ # :lst => [7, 8, 9],
94
+ # :str => 'three'
95
+ # }
96
+
97
+ stdout = []
98
+ parser = c.config.parser do |psr|
99
+ psr.on '-h', '--help', 'print help' do
100
+ stdout << "options:"
101
+ stdout << psr
102
+ end
103
+ end
104
+
105
+ parser.parse('--help')
106
+ "\n" + stdout.join("\n")
107
+ # => %q{
108
+ # options:
109
+ # --flag a flag
110
+ # -h, --help print help
111
+ # --lst LST... a list of integers (1,2,3)
112
+ # --num NUM a number (3.14)
113
+ # --str STR a string (one)
114
+ # --[no-]switch an on/off switch
115
+ # }
116
+
117
+ Configurable supports custom data types, nested configs, and config modules.
118
+
119
+ See the help documentation for more details:
120
+
121
+ * {Command Line Usage}[link:files/Help/Command%20Line_rdoc.html]
122
+ * {Config Syntax}[link:files/Help/Config%20Syntax_rdoc.html]
123
+ * {Config Types}[link:files/Help/Config%20Types_rdoc.html]
124
+
125
+ == Installation
126
+
127
+ Configurable is available as a gem[http://rubygems.org/gems/configurable].
128
+
129
+ % gem install configurable
130
+
131
+ == Development
132
+
133
+ To get started, checkout the code from GitHub[http://github.com/thinkerbot/configurable] and run the tests:
134
+
135
+ git clone git://github.com/thinkerbot/configurable.git
136
+ cd configurable
137
+ rake test
138
+
139
+ Please report any issues {here}[http://github.com/thinkerbot/configurable/issues].
140
+
141
+ == Info
142
+
143
+ Developer:: {Simon Chiang}[http://github.com/thinkerbot]
144
+ License:: {MIT-Style}[link:files/MIT-LICENSE.html]
@@ -1,8 +1,7 @@
1
- require 'configurable/version'
2
1
  require 'configurable/module_methods'
3
2
 
4
3
  # Configurable enables the specification of configurations within a class
5
- # definition.
4
+ # definition. Include and declare configs as below.
6
5
  #
7
6
  # class ConfigClass
8
7
  # include Configurable
@@ -13,184 +12,10 @@ require 'configurable/module_methods'
13
12
  #
14
13
  # c = ConfigClass.new
15
14
  # c.config.class # => Configurable::ConfigHash
16
- # c.config # => {:one => 'one', :two => 'two', :three => 'three'}
17
- #
18
- # Instances have a <tt>config</tt> object that acts like a forwarding hash;
19
- # configuration keys delegate to accessors while undeclared key-value pairs
20
- # are stored internally:
21
- #
22
- # c.config[:one] = 'ONE'
23
- # c.one # => 'ONE'
24
- #
25
- # c.one = 1
26
- # c.config # => {:one => 1, :two => 'two', :three => 'three'}
27
- #
28
- # c.config[:undeclared] = 'value'
29
- # c.config.store # => {:undeclared => 'value'}
30
- #
31
- # The writer for a configuration can be defined by providing a block to config.
32
- # The Validation module provides a number of common validation/transform
33
- # blocks accessible through the class method 'c':
34
- #
35
- # class ValidationClass
36
- # include Configurable
37
- # config(:one, 'one') {|v| v.upcase }
38
- # config :two, 2, &c.integer
39
- # end
40
- #
41
- # c = ValidationClass.new
42
- # c.config # => {:one => 'ONE', :two => 2}
43
- #
44
- # c.one = 'aNothER'
45
- # c.one # => 'ANOTHER'
46
- #
47
- # c.two = -2
48
- # c.two # => -2
49
- # c.two = "3"
50
- # c.two # => 3
51
- # c.two = nil # !> ValidationError
52
- # c.two = 'str' # !> ValidationError
53
- #
54
- # Note that config blocks are defined in class-context and will have access
55
- # to variables outside the block (as you would expect). For instance, these
56
- # are analagous declarations:
57
- #
58
- # class ExampleClass
59
- # config :key, 'value' do |input|
60
- # input.upcase
61
- # end
62
- # end
63
- #
64
- # class AnalagousClass
65
- # block = lambda {|input| input.upcase}
66
- #
67
- # define_method(:key=) do |input|
68
- # @key = block.call(input)
69
- # end
70
- # end
71
- #
72
- # To have the block literally define the writer, use the config_attr method.
73
- # Blocks provided to config_attr will have instance context and must set
74
- # the instance variable themselves.
75
- #
76
- # class LiteralClass
77
- # config_attr :key, 'value' do |input|
78
- # @key = input.upcase
79
- # end
80
- # end
81
- #
82
- # Configurations are inherited and may be overridden in subclasses. They may
83
- # also be included from a module:
84
- #
85
- # module A
86
- # include Configurable
87
- # config :a, 'a'
88
- # config :b, 'b'
89
- # end
90
- #
91
- # class B
92
- # include A
93
- # end
94
- #
95
- # class C < B
96
- # config :b, 'B'
97
- # config :c, 'C'
98
- # end
99
- #
100
- # B.new.config.to_hash # => {:a => 'a', :b => 'b'}
101
- # C.new.config.to_hash # => {:a => 'a', :b => 'B', :c => 'C'}
102
- #
103
- # Lastly, configurable classes may be nested through the nest method. Nesting
104
- # creates a configurable class with the configs defined in the nest block;
105
- # nested configs may be accessed by chaining method calls, or through nested
106
- # calls to config.
107
- #
108
- # class NestingClass
109
- # include Configurable
110
- # config :one, 'one'
111
- # nest :two do
112
- # config :three, 'three'
113
- # end
114
- # end
115
- #
116
- # c = NestingClass.new
117
- # c.config.to_hash # => {:one => 'one', :two => {:three => 'three'}}
118
- #
119
- # c.two.three = 'THREE'
120
- # c.config[:two][:three] # => 'THREE'
121
- #
122
- # === Attributes
123
- #
124
- # Alternative reader and writer methods may be specified as config attributes.
125
- # When alternate methods are specified, Configurable assumes the methods are
126
- # declared elsewhere and will not define accessors.
127
- #
128
- # class AlternativeClass
129
- # include Configurable
130
- #
131
- # config_attr :sym, 'value', :reader => :get_sym, :writer => :set_sym
132
- #
133
- # def get_sym
134
- # @sym
135
- # end
136
- #
137
- # def set_sym(input)
138
- # @sym = input.to_sym
139
- # end
140
- # end
141
- #
142
- # alt = AlternativeClass.new
143
- # alt.respond_to?(:sym) # => false
144
- # alt.respond_to?(:sym=) # => false
145
- #
146
- # alt.config[:sym] = 'one'
147
- # alt.get_sym # => :one
148
- #
149
- # alt.set_sym('two')
150
- # alt.config[:sym] # => :two
151
- #
152
- # Idiosyncratically, true and false may also be provided as reader/writer
153
- # values.
154
- #
155
- # true:: Same as using the defaults, accessors are defined.
156
- # false:: Sets the default reader/writer but does not define
157
- # the accessors (think 'define reader/writer' => false).
158
- #
159
- # Nil is not allowed as a value.
160
- #
161
- # ==== Non-reader/writer attributes
162
- #
163
- # Attributes provide metadata for how to use configurations in various contexts.
164
- # In general, attributes can be used to set any metadata an application
165
- # needs. A few attributes are used internally by Configurable.
166
- #
167
- # Attribute:: Use::
168
- # init:: When set to false, the config will not initialize itself.
169
- # Specify when you manually initialize a config.
170
- # type:: Specifies the type of option ConfigParser generates for this
171
- # Config (ex: :switch, :flag, :list, :hidden)
172
- # desc:: The description string used in the ConfigParser help
173
- # long:: The long option (default: key)
174
- # short:: The short option.
175
- #
176
- # Validation blocks have default attributes already assigned to them (ex type).
177
- # In cases where a user-defined block gets used multiple times, it may be useful
178
- # to register default attributes for that block. To do so, use this pattern:
179
- #
180
- # class AttributesClass
181
- # include Configurable
182
- # block = c.register(:type => :upcase) {|v| v.upcase }
183
- #
184
- # config :a, 'A', &block
185
- # config :b, 'B', &block
186
- # end
187
- #
188
- # AttributesClass.configurations[:a][:type] # => :upcase
189
- # AttributesClass.configurations[:b][:type] # => :upcase
15
+ # c.config.to_hash # => {:one => 'one', :two => 'two', :three => 'three'}
190
16
  #
191
17
  module Configurable
192
- autoload(:Utils, 'configurable/utils')
193
-
18
+
194
19
  # A ConfigHash bound to self. Accessing configurations through config
195
20
  # is much slower (although sometimes more convenient) than through the
196
21
  # config accessors.
@@ -203,106 +28,18 @@ module Configurable
203
28
  initialize_config unless instance_variable_defined?(:@config)
204
29
  super
205
30
  end
206
-
207
- # Reconfigures self with the given overrides. Only the specified configs
208
- # are modified.
209
- #
210
- # Returns self.
211
- def reconfigure(overrides={})
212
- config.merge!(overrides)
213
- self
214
- end
215
-
31
+
216
32
  # Reinitializes configurations in the copy such that the new object has
217
33
  # it's own set of configurations, separate from the original object.
218
34
  def initialize_copy(orig)
219
35
  super
220
- @config = ConfigHash.new(self, orig.config.store.dup, false)
36
+ @config = ConfigHash.new(orig.config.store.dup, self)
221
37
  end
222
-
38
+
223
39
  protected
224
40
 
225
- # Opens the file specified by io and yield it to the block. If io is an
226
- # IO, it will be yielded immediately, and the mode is ignored. Nil io are
227
- # simply ignored.
228
- #
229
- # === Usage
230
- #
231
- # open_io is used to compliment the io validation, to ensure that if a file
232
- # is specified, it will be closed.
233
- #
234
- # class IoSample
235
- # include Configurable
236
- # config :output, $stdout, &c.io # can be an io or filepath
237
- #
238
- # def say_hello
239
- # open_io(output, 'w') do |io|
240
- # io << 'hello!'
241
- # end
242
- # end
243
- # end
244
- #
245
- # In short, this method provides a way to responsibly handle IO and file
246
- # configurations.
247
- def open_io(io, mode='r')
248
- case io
249
- when String
250
- dir = File.dirname(io)
251
- FileUtils.mkdir_p(dir) unless File.directory?(dir)
252
- File.open(io, mode) {|file| yield(file) }
253
- when Integer
254
- # note this does not close the io because, as far as I understand,
255
- # valid integer file descriptors point to files that are already
256
- # open and presumably managed elsewhere
257
- yield IO.open(io, mode)
258
- when nil then nil
259
- else yield(io)
260
- end
261
- end
262
-
263
41
  # Initializes config. Default config values are overridden as specified.
264
42
  def initialize_config(overrides={})
265
- @config = ConfigHash.new(self, overrides, false)
266
-
267
- # cache as configs (equivalent to self.class.configurations)
268
- # as an optimization
269
- configs = @config.configs
270
-
271
- # hash overrides by delegate so they may be set
272
- # in the correct order below
273
- initial_values = {}
274
- overrides.each_key do |key|
275
- if config = configs[key]
276
-
277
- # check that the config may be initialized
278
- unless config.init?
279
- key = configs.keys.find {|k| configs[k] == config }
280
- raise "initialization values are not allowed for: #{key.inspect}"
281
- end
282
-
283
- # check that multiple overrides are not specified for a
284
- # single config, as may happen with indifferent access
285
- # (ex 'key' and :key)
286
- if initial_values.has_key?(config)
287
- key = configs.keys.find {|k| configs[k] == config }
288
- raise "multiple values map to config: #{key.inspect}"
289
- end
290
-
291
- # since overrides are used as the ConfigHash store,
292
- # the overriding values must be removed, not read
293
- initial_values[config] = overrides.delete(key)
294
- end
295
- end
296
-
297
- # now initialize configs in order
298
- configs.each_pair do |key, config|
299
- next unless config.init?
300
-
301
- if initial_values.has_key?(config)
302
- config.set(self, initial_values[config])
303
- else
304
- config.init(self)
305
- end
306
- end
43
+ @config = ConfigHash.new(overrides).bind(self)
307
44
  end
308
45
  end