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,141 @@
1
+ = Command Line Usage
2
+
3
+ Configurable guesses a command-line option for each config, based on the
4
+ config name (ex: --name for :name, -n for :n) and the default value. Flags,
5
+ switches, list configs, and nested configs are all supported.
6
+
7
+ class ConfigClass
8
+ include Configurable
9
+ config :flag, false # a flag
10
+ config :switch, true # an on/off switch
11
+ config :num, 3.14 # a number
12
+ config :lst, [1,2,3] # a list of integers
13
+ config :nest do
14
+ config :str, 'one' # a string
15
+ end
16
+ end
17
+
18
+ Options can be parsed at the class level:
19
+
20
+ parser = ConfigClass.configs.to_parser
21
+ parser.parse("a --flag --no-switch --num 6.02 b c") do |args, config|
22
+ args
23
+ # => ['a', 'b', 'c']
24
+
25
+ config
26
+ # => {
27
+ # :flag => true,
28
+ # :switch => false,
29
+ # :num => 6.02,
30
+ # :lst => [1, 2, 3],
31
+ # :nest => {:str => 'one'}
32
+ # }
33
+ end
34
+
35
+ Or at the instance level:
36
+
37
+ c = ConfigClass.new
38
+ c.config.parse('a --lst 7 --lst 8,9 --nest:str=two b c')
39
+ # => ['a', 'b', 'c']
40
+
41
+ c.config.to_hash
42
+ # => {
43
+ # :flag => false,
44
+ # :switch => true,
45
+ # :num => 3.14,
46
+ # :lst => [7, 8, 9],
47
+ # :nest => {:str => 'two'}
48
+ # }
49
+
50
+ A description string is extracted from the comment trailing a config
51
+ declaration, such that a help is readily available.
52
+
53
+ stdout = []
54
+ c.config.parse('--help') do |psr|
55
+ psr.on('--help', 'print this help') do
56
+ stdout << "options:"
57
+ stdout << psr
58
+ end
59
+ end
60
+
61
+ "\n" + stdout.join("\n")
62
+ # => %q{
63
+ # options:
64
+ # --flag a flag
65
+ # --help print this help
66
+ # --lst LST... a list of integers (1,2,3)
67
+ # --nest:str STR a string (one)
68
+ # --num NUM a number (3.14)
69
+ # --[no-]switch an on/off switch
70
+ # }
71
+
72
+ To specify alternative long/short options, or a different argument name, use a
73
+ prefix section in the description.
74
+
75
+ class AltClass
76
+ include Configurable
77
+ config :a, nil # -a, --aaa ARGNAME : cmdline options may be
78
+ config :b, nil # -b : declared in the docs
79
+ config :c, nil # --ccc : using a prefix
80
+ config :d, nil # : an empty prefix implies 'hidden'
81
+ config :e, nil # no prefix uses the defaults
82
+ config :f, nil # -f [OPTIONAL] : bracket argname means 'optional'
83
+
84
+ config :g, [] # -g, --ggg LIST : same rules for list opts
85
+ config :nest do
86
+ config :i, nil # -i, --iii NEST : and same for nested opts
87
+ end
88
+ end
89
+
90
+ stdout = []
91
+ AltClass.configs.to_parser do |psr|
92
+ psr.on('-h', '--help', 'print this help') do
93
+ stdout << "options:"
94
+ stdout << psr
95
+ end
96
+ end.parse('--help')
97
+
98
+ "\n" + stdout.join("\n")
99
+ # => %q{
100
+ # options:
101
+ # -a, --aaa ARGNAME cmdline options may be
102
+ # -b B declared in the docs
103
+ # --ccc C using a prefix
104
+ # -e E no prefix uses the defaults
105
+ # -f [OPTIONAL] bracket argname means 'optional'
106
+ # -g, --ggg LIST... same rules for list opts
107
+ # -h, --help print this help
108
+ # -i, --iii NEST and same for nested opts
109
+ # }
110
+
111
+ If necessary you can specify all aspects of an option manually:
112
+
113
+ class ManualClass
114
+ include Configurable
115
+ config :key, nil, {
116
+ :long => 'long',
117
+ :short => 's',
118
+ :arg_name => 'ARGNAME',
119
+ :desc => 'summary',
120
+ :hint => 'hint',
121
+ :optional => false,
122
+ :hidden => false
123
+ }
124
+ end
125
+
126
+ stdout = []
127
+ ManualClass.configs.to_parser do |psr|
128
+ psr.on('-h', '--help', 'print this help') do
129
+ stdout << "options:"
130
+ stdout << psr
131
+ end
132
+ end.parse('--help')
133
+
134
+ "\n" + stdout.join("\n")
135
+ # => %q{
136
+ # options:
137
+ # -h, --help print this help
138
+ # -s, --long ARGNAME summary (hint)
139
+ # }
140
+
141
+ See the {ConfigParser}[http://rubygems.org/gems/config_parser] documentation for more details.
@@ -0,0 +1,229 @@
1
+ = Config Syntax
2
+
3
+ Declare configurations by key, and provide a default value. Declaring a config
4
+ generates accessors with the same name and sets up access through config.
5
+
6
+ class ConfigClass
7
+ include Configurable
8
+ config :str, 'one'
9
+ end
10
+
11
+ c = ConfigClass.new
12
+ c.str # => 'one'
13
+ c.str = 'two'
14
+ c.config[:str] # => 'two'
15
+ c.config[:str] = 'three'
16
+ c.str # => 'three'
17
+ c.config.to_hash # => {:str => 'three'}
18
+
19
+ == Initialization
20
+
21
+ Call initialize_config during initialization to setup config with non-default
22
+ values.
23
+
24
+ class InitClass
25
+ include Configurable
26
+ config :str, 'one'
27
+
28
+ def initialize(configs={})
29
+ initialize_config(configs)
30
+ end
31
+ end
32
+
33
+ c = InitClass.new(:str => 'two')
34
+ c.str # => 'two'
35
+
36
+ == Key/Name
37
+
38
+ Configs have both a key and a name. The key may be any object and provides
39
+ access to a configuration in config. The name must be a string and an ordinary
40
+ word. Name used gets used wherever a word-based-identifier is needed, for
41
+ instance when creating accessors.
42
+
43
+ By default name is key.to_s. When key converts into an illegal name, or when
44
+ you want a different name, specify it manually.
45
+
46
+ class KeyNameClass
47
+ include Configurable
48
+ config :key, 'val', :name => 'name'
49
+ end
50
+
51
+ c = KeyNameClass.new
52
+ c.name # => 'val'
53
+ c.config[:key] # => 'val'
54
+
55
+ == Reader/Writer
56
+
57
+ Specify a reader and/or writer to map configs to alternative accessors. When
58
+ you specify a reader or writer you must define the corresponding method
59
+ yourself, even if you specify default accessors (ie 'name' and 'name='). The
60
+ reader takes no arguments and the writer takes the input value.
61
+
62
+ class ReaderWriterClass
63
+ include Configurable
64
+ config :str, 'one', :reader => :get, :writer => :set
65
+
66
+ def get
67
+ @ivar
68
+ end
69
+
70
+ def set(value)
71
+ @ivar = value
72
+ end
73
+ end
74
+
75
+ c = ReaderWriterClass.new
76
+ c.get # => 'one'
77
+ c.set 'two'
78
+ c.config[:str] # => 'two'
79
+ c.config[:str] = 'three'
80
+ c.get # => 'three'
81
+ c.config.to_hash # => {:str => 'three'}
82
+
83
+ Note that Configurable doesn't care how the data is stored on an instance.
84
+
85
+ == Import/Export
86
+
87
+ Configs may be imported/exported between the active objects used by an
88
+ instance and the static data used by textual interfaces like config files, the
89
+ command-line, and web forms.
90
+
91
+ After import, configs are composed of key-value pairs of arbitrary class (ex a
92
+ symbol key and a string value). After export, configs are composed of
93
+ name-input pairs which must be directly serializable as JSON (ex a string name
94
+ and a string input).
95
+
96
+ Import/export can occur at the class level:
97
+
98
+ configs = KeyNameClass.configs
99
+ defaults = configs.to_default
100
+ defaults # => {:key => 'val'}
101
+
102
+ static_data = configs.export(defaults)
103
+ static_data # => {'name' => 'val'}
104
+
105
+ active_hash = configs.import({'name' => 'VAL'})
106
+ active_hash # => {:key => 'VAL'}
107
+
108
+ Or the instance level:
109
+
110
+ c = KeyNameClass.new
111
+ c.config.to_hash # => {:key => 'val'}
112
+ c.config.export # => {'name' => 'val'}
113
+
114
+ c.config.import({'name' => 'VAL'})
115
+ c.config.to_hash # => {:key => 'VAL'}
116
+
117
+ Configurable supports import/export of basic types (boolean, integer, float,
118
+ string, array, and hash). See later documentation to set up custom config
119
+ types.
120
+
121
+ == Lists
122
+
123
+ When an array of values is given as the default value, config will construct a
124
+ list-style configuration. The syntax and usage is no different than any other
125
+ config, except that type information is expected to be preserved across all
126
+ members of the array (ie an array-of-strings, array-of-integers, etc).
127
+
128
+ class ListClass
129
+ include Configurable
130
+ config :integers, [1, 2, 3]
131
+ end
132
+
133
+ c = ListClass.new
134
+ c.integers # => [1, 2, 3]
135
+ c.config.import('integers' => ['7', '8'])
136
+ c.config[:integers] # => [7, 8]
137
+
138
+ == Nesting
139
+
140
+ Configurable classes may be nested to represent a hash within a hash. To
141
+ construct a nested class, provide a hash default of key-value pairs, a block
142
+ defining the nested class, or an instance of the nested class.
143
+
144
+ class Parent
145
+ include Configurable
146
+
147
+ config :a, {:key => 'hash'}
148
+
149
+ config :b do
150
+ config :key, 'block'
151
+ end
152
+
153
+ class Child
154
+ include Configurable
155
+ config :key, 'instance'
156
+ end
157
+ config :c, Child.new
158
+ end
159
+
160
+ c = Parent.new
161
+ c.config.to_hash
162
+ # => {
163
+ # :a => {:key => 'hash'},
164
+ # :b => {:key => 'block'},
165
+ # :c => {:key => 'instance'}
166
+ # }
167
+
168
+ Nest configs are structured to provide clean access to the nested configurable
169
+ through the accessors and config:
170
+
171
+ c.a.key # => 'hash'
172
+ c.config[:a][:key] # => 'hash'
173
+ c.config[:a][:key] = 'HASH'
174
+ c.a.key # => 'HASH'
175
+ c.a.config.to_hash # => {:key => 'HASH'}
176
+
177
+ Instances of the nested class can be directly assigned, or they can be
178
+ initialized via config. Nested classes generated by the config method are
179
+ assigned to a constant based on the config name.
180
+
181
+ c.a = Parent::A.new
182
+ c.config[:a] # => {:key => 'hash'}
183
+ c.config[:a] = {:key => 'HASH'}
184
+ c.a.config.to_hash # => {:key => 'HASH'}
185
+
186
+ Import/export of nested classes occurs seamlessly:
187
+
188
+ c.config.import('b' => {'key' => 'BLOCK'})
189
+ c.config.export
190
+ # => {
191
+ # 'a' => {'key' => 'HASH'},
192
+ # 'b' => {'key' => 'BLOCK'},
193
+ # 'c' => {'key' => 'instance'}
194
+ # }
195
+
196
+ == Inheritance
197
+
198
+ Configurations can be inherited, overridden, declared in modules, and
199
+ generally treated like methods.
200
+
201
+ class A
202
+ include Configurable
203
+ config :a, 'one'
204
+ end
205
+
206
+ module B
207
+ include Configurable
208
+ config :b, 'two'
209
+ end
210
+
211
+ class C < A
212
+ include B
213
+ config :c, 'three'
214
+ end
215
+
216
+ c = C.new
217
+ c.a # => 'one'
218
+ c.b # => 'two'
219
+ c.c # => 'three'
220
+ c.config.to_hash # => {:a => 'one', :b => 'two', :c => 'three'}
221
+
222
+ class D < C
223
+ config :a, 'ONE'
224
+ undef_config :c
225
+ end
226
+
227
+ d = D.new
228
+ d.respond_to?(:c) # => false
229
+ d.config.to_hash # => {:a => 'ONE', :b => 'two'}
@@ -0,0 +1,143 @@
1
+ == Config Types
2
+
3
+ Configs have two conceptual parts; a config class that determines how to map
4
+ config values between various contexts, and a config type that determines how
5
+ to cast config values. Configurable provides support for basic types (ex
6
+ booleans, numbers, strings) and a syntax to declare custom types.
7
+
8
+ Custom types are declared using the config_type method. The config method then
9
+ matches the default against all available config types to guess the type for
10
+ the new configuration.
11
+
12
+ require 'time'
13
+
14
+ now = Time.now
15
+ class TimeExample
16
+ include Configurable
17
+
18
+ config_type :time, Time do |input|
19
+ Time.parse(input)
20
+ end.uncast do |value|
21
+ time.strftime('%Y-%m-%d %H:%M:%S')
22
+ end
23
+
24
+ config :obj, now
25
+ end
26
+
27
+ c = TimeExample.new
28
+ c.obj # => now
29
+
30
+ c.config.import('obj' => 'Sun Dec 05 16:52:19 -0700 2010')
31
+ c.obj.strftime('%Y-%m-%d') # => '2010-12-05'
32
+
33
+ c.config.export
34
+ # => {'obj' => '2010-12-05 16:52:19'}
35
+
36
+ Config types define how to cast/uncast values during import/export. The type
37
+ is free to determine how to do so, so long as the uncast value can be cleanly
38
+ serialized as JSON. This is also possible:
39
+
40
+ class RangeExample
41
+ include Configurable
42
+
43
+ config_type(:range, Range) do |input|
44
+ Range.new(
45
+ input['begin'],
46
+ input['end'],
47
+ input['exclusive']
48
+ )
49
+ end.uncast do |value|
50
+ {
51
+ 'begin' => value.begin,
52
+ 'end' => value.end,
53
+ 'exclusive' => value.exclude_end?
54
+ }
55
+ end
56
+
57
+ config :obj, 1..10
58
+ end
59
+
60
+ c = RangeExample.new
61
+ c.obj # => 1..10
62
+
63
+ c.config.import('obj' => {'begin' => 2, 'end' => 20, 'exclusive' => false})
64
+ c.obj # => 2..20
65
+
66
+ c.obj = 3...30
67
+ c.config.export
68
+ # => {'obj' => {'begin' => 3, 'end' => 30, 'exclusive' => true}}
69
+
70
+ The config_type method defines a type class, which is set to a constant
71
+ according to the type name.
72
+
73
+ config = RangeExample.configs[:obj]
74
+ config.type.class # => RangeExample::RangeType
75
+
76
+ == Matching/Inheritance
77
+
78
+ Upon declaration the config type for a config is guessed by matching the
79
+ default against all available types. Matching walks up the inheritance
80
+ hierarchy to find a match if necessary, and stops when a match is found. An
81
+ error is raised if more than one type matches for a given ancestor; in that
82
+ case the type must be specified manually.
83
+
84
+ class A
85
+ include Configurable
86
+ config_type :datetime, Date, Time, DateTime
87
+ config :a, Time.now # matches :datetime type
88
+ end
89
+
90
+ class B < A
91
+ config_type :time, Time
92
+
93
+ config :b, Time.now # matches :time type
94
+ config :c, Date.today # skips :time, walks up to match :datetime
95
+ end
96
+
97
+ class C < B
98
+ config_type :date1, Date
99
+ config_type :date2, Date
100
+
101
+ config :d, Date.today, :type => :date1 # manually specify which to match;
102
+ config :e, Date.today, :type => :date2 # ambiguous type will raise error
103
+ end
104
+
105
+ configs = C.configs
106
+ configs[:a].type.class # => A::DatetimeType
107
+ configs[:b].type.class # => B::TimeType
108
+ configs[:c].type.class # => A::DatetimeType
109
+ configs[:d].type.class # => C::Date1Type
110
+ configs[:e].type.class # => C::Date2Type
111
+
112
+ For nest configs defined from a block or hash, config types are guessed in the
113
+ context of the parent (although config_types defined in a nested child do not
114
+ bleed back up).
115
+
116
+ class Parent
117
+ include Configurable
118
+ config_type :datetime, Time, Date
119
+
120
+ config :a, {:b => Time.now}
121
+ config :c do
122
+ config_type :time, Time
123
+
124
+ config :d, Time.now
125
+ config :e, Date.today
126
+ end
127
+ config :f, Date.today
128
+ end
129
+
130
+ configs = Parent.configs
131
+ configs[:a].type.class # => Configurable::ConfigTypes::NestType
132
+ configs[:c].type.class # => Configurable::ConfigTypes::NestType
133
+ configs[:f].type.class # => Parent::DatetimeType
134
+
135
+ a_configs = configs[:a].type.configurable.class.configs
136
+ a_configs[:b].type.class # => Parent::DatetimeType
137
+
138
+ c_configs = configs[:c].type.configurable.class.configs
139
+ c_configs[:d].type.class # => Parent::C::TimeType
140
+ c_configs[:e].type.class # => Parent::DatetimeType
141
+
142
+ Aside from direct inheritance, config_types may be overridden, declared in
143
+ modules, and handled as if they were methods.