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
data/{History → History.rdoc}
RENAMED
@@ -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
|
data/MIT-LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Copyright (c) 2008-2009, Regents of the University of Colorado.
|
2
2
|
|
3
|
-
Copyright (c) 2009-
|
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
|
data/README.rdoc
ADDED
@@ -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]
|
data/lib/configurable.rb
CHANGED
@@ -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
|
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
|
-
|
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(
|
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(
|
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
|