configurable 0.7.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Help/Command Line.rdoc +141 -0
- data/Help/Config Syntax.rdoc +229 -0
- data/Help/Config Types.rdoc +143 -0
- data/{History → History.rdoc} +9 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +144 -0
- data/lib/configurable.rb +7 -270
- data/lib/configurable/class_methods.rb +344 -367
- data/lib/configurable/config_classes.rb +3 -0
- data/lib/configurable/config_classes/list_config.rb +26 -0
- data/lib/configurable/config_classes/nest_config.rb +50 -0
- data/lib/configurable/config_classes/scalar_config.rb +91 -0
- data/lib/configurable/config_hash.rb +87 -112
- data/lib/configurable/config_types.rb +6 -0
- data/lib/configurable/config_types/boolean_type.rb +22 -0
- data/lib/configurable/config_types/float_type.rb +11 -0
- data/lib/configurable/config_types/integer_type.rb +11 -0
- data/lib/configurable/config_types/nest_type.rb +39 -0
- data/lib/configurable/config_types/object_type.rb +58 -0
- data/lib/configurable/config_types/string_type.rb +15 -0
- data/lib/configurable/conversions.rb +91 -0
- data/lib/configurable/module_methods.rb +0 -1
- data/lib/configurable/version.rb +1 -5
- metadata +73 -30
- data/README +0 -207
- data/lib/cdoc.rb +0 -413
- data/lib/cdoc/cdoc_html_generator.rb +0 -38
- data/lib/cdoc/cdoc_html_template.rb +0 -42
- data/lib/config_parser.rb +0 -563
- data/lib/config_parser/option.rb +0 -108
- data/lib/config_parser/switch.rb +0 -44
- data/lib/config_parser/utils.rb +0 -177
- data/lib/configurable/config.rb +0 -97
- data/lib/configurable/indifferent_access.rb +0 -35
- data/lib/configurable/nest_config.rb +0 -78
- data/lib/configurable/ordered_hash_patch.rb +0 -85
- data/lib/configurable/utils.rb +0 -186
- 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.
|