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,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.