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