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