configfiles 0.0.2 → 0.1.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/README.rdoc +18 -0
- data/lib/configfiles/extensions/enumerable.rb +5 -0
- data/lib/configfiles.rb +216 -67
- metadata +26 -12
- data/configfiles-example.rb +0 -86
data/README.rdoc
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
=ConfigFiles
|
2
|
+
|
3
|
+
A simple library to specify the format of configuration files and the
|
4
|
+
way to turn their data into Ruby objects.
|
5
|
+
|
6
|
+
Ruby1.9 centric. "Lazy" to some extent and as needed.
|
7
|
+
|
8
|
+
No write support: it's strongly sugested to use ERB or other
|
9
|
+
templating systems for that.
|
10
|
+
|
11
|
+
A good example usage is found at http:/github.com/gderosa/dansguardian.rb .
|
12
|
+
|
13
|
+
==Author
|
14
|
+
|
15
|
+
Copyright 2010 Guido De Rosa
|
16
|
+
|
17
|
+
License: same of Ruby.
|
18
|
+
|
data/lib/configfiles.rb
CHANGED
@@ -1,110 +1,259 @@
|
|
1
1
|
# Copyright 2010, Guido De Rosa <guido.derosa*vemarsas.it>
|
2
2
|
# License: same of Ruby
|
3
3
|
|
4
|
+
require 'facets/enumerable/defer'
|
4
5
|
|
6
|
+
require 'configfiles/extensions/enumerable'
|
5
7
|
|
6
8
|
module ConfigFiles
|
7
9
|
|
8
|
-
VERSION = '0.0
|
9
|
-
|
10
|
-
module Parser
|
11
|
-
def self.read_file(path)
|
12
|
-
self.read File.open path
|
13
|
-
end
|
14
|
-
end
|
10
|
+
VERSION = '0.1.0'
|
15
11
|
|
12
|
+
# You should write a read(io) method,
|
13
|
+
# taking an IO object and returnig a key-value hash, where keys
|
14
|
+
# are symbols, and values are Strings or Enumerable yielding Strings
|
15
|
+
#
|
16
|
+
# This result will be passed to YourConfigClass#load,
|
17
|
+
# where YourConfigClass inherits from ConfigFiles::Base
|
16
18
|
class Base
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
20
|
+
CIRCUMSTANCES = [:unknown_parameter, :unknown_value]
|
21
|
+
PREDEFINED_ACTIONS = [:accept, :ignore, :fail]
|
22
|
+
|
23
|
+
class ArgumentError < ::ArgumentError; end
|
24
|
+
class RuntimeError < ::RuntimeError; end
|
25
|
+
class NoKeyError < ::NameError; end
|
26
|
+
class ValidationFailed < ::RuntimeError; end
|
27
|
+
class AlreadyDefinedParameter < ::Exception; end
|
28
|
+
class DefaultAlreadySet < ::Exception; end
|
29
|
+
|
30
|
+
AlreadyDefinedDefault = DefaultAlreadySet
|
31
|
+
|
32
|
+
@@parameters ||= {}
|
33
|
+
@@behavior ||= {
|
34
|
+
:unknown_parameter => :ignore,
|
35
|
+
:unknown_value => :fail # when the converter is a Hash,
|
36
|
+
# whose keys represents a fixed set
|
37
|
+
# of allowed strings, and values represents
|
38
|
+
# their "meaning", tipically as a Symbol
|
39
|
+
}
|
40
|
+
@@validate ||= lambda {|data| true}
|
41
|
+
|
42
|
+
class << self
|
43
|
+
|
44
|
+
# Examples:
|
45
|
+
# on :unknown_parameter, :fail # or :accept, or :ignore
|
46
|
+
# on :unknown_parameter, {|str| str.to_i}
|
47
|
+
#
|
48
|
+
# There's also :unknown_value, to specify behavior when the
|
49
|
+
# converter is an Hash and the value found if not among the
|
50
|
+
# hash keys. Usage is similar.
|
51
|
+
#
|
52
|
+
def on(circumstance, action=nil, &block)
|
53
|
+
actions = PREDEFINED_ACTIONS
|
54
|
+
circumstances = CIRCUMSTANCES
|
55
|
+
unless circumstances.include? circumstance
|
56
|
+
raise ArgumentError, "Invalid circumstance: #{circumstance.inspect}. Allowed values are #{circumstances.list_inspect}."
|
57
|
+
end
|
58
|
+
if block
|
59
|
+
@@behavior[circumstance] = block
|
60
|
+
elsif actions.include? action
|
61
|
+
@@behavior[circumstance] = action
|
62
|
+
elsif action
|
63
|
+
raise ArgumentError, "Invalid action: #{action}. Allowed values are #{actions.list_inspect}."
|
64
|
+
else
|
65
|
+
return @@behavior[circumstance]
|
66
|
+
end
|
35
67
|
end
|
36
|
-
end
|
37
68
|
|
38
|
-
|
39
|
-
|
40
|
-
|
69
|
+
# +circumstance+ must be an elements of +CIRCUMSTANCES+
|
70
|
+
#
|
71
|
+
# returns an elements of +PREDEFINED_ACTIONS+ or a user-defined Proc
|
72
|
+
def behavior_on(circumstance); on(circumstance); end
|
41
73
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
74
|
+
# Add a parameter.
|
75
|
+
#
|
76
|
+
# # keep as is
|
77
|
+
# parameter :myparam
|
78
|
+
#
|
79
|
+
# # convert to integer
|
80
|
+
# parameter :myparam, :to_i
|
81
|
+
#
|
82
|
+
# # do some computation
|
83
|
+
# parameter :myparam do |str|
|
84
|
+
# ...
|
85
|
+
# ...
|
86
|
+
# my_result
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# # map a set of possible/admitted values; you may call the
|
90
|
+
# # class method +on+(:unknown_value) to customize behavior
|
91
|
+
# parameter :myparam,
|
92
|
+
# '1' => :my_first_option,
|
93
|
+
# '2' => :my_second_one
|
94
|
+
#
|
95
|
+
def parameter(name, converter=nil, &converter_block)
|
96
|
+
if @@parameters[name] and @@parameters[name][:converter]
|
97
|
+
raise AlreadyDefinedParameter, "Already defined parameter \"#{name}\""
|
50
98
|
end
|
51
|
-
|
52
|
-
|
99
|
+
if converter
|
100
|
+
if converter_block
|
101
|
+
raise ArgumentError, 'you must either specify a symbol or a block'
|
102
|
+
elsif converter.is_a? Hash
|
103
|
+
|
104
|
+
converter_block = lambda do |x| # x is a String from conf file
|
105
|
+
if converter.keys.include? x
|
106
|
+
return converter[x] # returns from lambda, not from method
|
107
|
+
elsif @@behavior[:unknown_value] == :fail
|
108
|
+
raise ArgumentError, "Invalid value \"#{x}\" for parameter \"#{name}\". Allowed values are #{converter.keys.list_inspect}."
|
109
|
+
elsif @@behavior[:unknown_value] == :accept
|
110
|
+
return x
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
else #Symbol
|
115
|
+
converter_block = lambda {|x| x.method(converter).call}
|
116
|
+
end
|
117
|
+
else
|
118
|
+
converter_block ||= lambda {|x| x}
|
119
|
+
end
|
120
|
+
@@parameters[name] ||= {}
|
121
|
+
@@parameters[name][:converter] = converter_block
|
53
122
|
end
|
54
|
-
@@parameters[name] = {
|
55
|
-
:converter => converter_block
|
56
|
-
}
|
57
|
-
end
|
58
123
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
124
|
+
# set default value of a parameter
|
125
|
+
def default(name, value)
|
126
|
+
if @@parameters[name] and @@parameters[name][:default]
|
127
|
+
raise DefaultAlreadySet, "Default for \"#{name}\" has been already set (to value: #{@@parameters[name][:default]})"
|
128
|
+
end
|
129
|
+
@@parameters[name] ||= {}
|
130
|
+
@@parameters[name][:default] = value
|
131
|
+
end
|
132
|
+
|
133
|
+
# A special kind of parameter, with a special kind of converter,
|
134
|
+
# which in turn
|
135
|
+
# converts an Enumerable yielding Strings into an Enumerator of
|
136
|
+
# custom objects. You may work with Enumerators instead of
|
137
|
+
# Arrays , which is the right thing to do when you deal with very
|
138
|
+
# long list of names, IP adresses, URIs etc (lazy evaluation) .
|
139
|
+
def enumerator(name, converter=nil, &converter_block)
|
140
|
+
if block_given?
|
141
|
+
raise ArgumentError, 'you must either specify a symbol or a block' if
|
142
|
+
converter
|
143
|
+
else
|
144
|
+
if converter # converter may be :to_i etc.
|
145
|
+
converter_block = lambda {|x| x.method(converter).call}
|
146
|
+
else
|
147
|
+
converter_block = lambda {|x| x}
|
69
148
|
end
|
70
149
|
end
|
150
|
+
#parameter name do |enumerable|
|
151
|
+
# Enumerator.new do |yielder|
|
152
|
+
# enumerable.each do |string|
|
153
|
+
# yielder << converter_block.call(string)
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#end
|
157
|
+
#
|
158
|
+
# Use facets instead
|
159
|
+
parameter name do |enumerable|
|
160
|
+
enumerable.defer.map{|element| converter_block.call(element)}
|
161
|
+
end
|
71
162
|
end
|
72
|
-
end
|
73
163
|
|
74
|
-
|
75
|
-
|
76
|
-
|
164
|
+
# Set validaion rules. For example, if parameter 'a' must be
|
165
|
+
# smaller than 'b':
|
166
|
+
#
|
167
|
+
# validate do |confdata|
|
168
|
+
# raise ValidationFailed, "no good!" unless
|
169
|
+
# confdata[:a] <= confdata[:b]
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
def validate(&block)
|
173
|
+
@@validate = block
|
174
|
+
end
|
77
175
|
|
78
|
-
|
176
|
+
end # class << self
|
79
177
|
|
80
178
|
def initialize
|
81
|
-
@options = @@options.dup
|
82
179
|
@data = {}
|
83
|
-
def @data.missing_method(id); @data[id]; end
|
84
180
|
end
|
85
181
|
|
182
|
+
# Validate configuration object, according to what declared
|
183
|
+
# with the class method
|
86
184
|
def validate
|
87
|
-
@@validate.call(
|
185
|
+
@@validate.call(self)
|
88
186
|
end
|
89
187
|
|
188
|
+
# Load the Hash h onto the ConfigFiles object, carrying on conversions
|
189
|
+
# to Ruby objects, validation, and default actions
|
190
|
+
# if needed. h's keys are Symbols, h's values are typically Strings
|
191
|
+
# or Enumerables yielding Strings. See also ConfigFiles::Base.parameter
|
192
|
+
# and ConfigFiles::Base.on.
|
90
193
|
def load(h)
|
194
|
+
|
91
195
|
h.each_pair do |id, value|
|
92
|
-
if @@parameters[id][:converter]
|
196
|
+
if @@parameters[id] and @@parameters[id][:converter]
|
93
197
|
@data[id] = @@parameters[id][:converter].call(value)
|
94
|
-
elsif
|
198
|
+
elsif @@behavior[:unknown_parameter] == :fail
|
95
199
|
raise RuntimeError, "unknown parameter #{key}" # otherwise ignore
|
96
|
-
elsif
|
97
|
-
|
200
|
+
elsif @@behavior[:unknown_parameter] == :accept
|
201
|
+
@data[id] = value
|
202
|
+
elsif @@behavior[:unknown_parameter].respond_to? :call
|
203
|
+
block = @@behavior[:unknown_parameter]
|
98
204
|
@data[id] = block.call value
|
99
205
|
end
|
100
206
|
end
|
207
|
+
|
208
|
+
# assign default values to the remaining params
|
209
|
+
@@parameters.each_pair do |name, h|
|
210
|
+
if !@data[name] and @@parameters[name][:default]
|
211
|
+
@data[name] = @@parameters[name][:default]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
@data.merge! deferred_data
|
216
|
+
|
101
217
|
validate
|
102
218
|
end
|
103
219
|
|
104
|
-
|
105
|
-
|
220
|
+
# Like Hash#[], but more rigidly! Raise an Exception on unknown
|
221
|
+
# key, instead of returning nil.
|
222
|
+
def [](key)
|
223
|
+
if @data.keys.include? key
|
224
|
+
@data[key]
|
225
|
+
else
|
226
|
+
raise NoKeyError, "unknown key '#{key}' for #{self.class}"
|
227
|
+
end
|
106
228
|
end
|
107
229
|
|
108
|
-
|
230
|
+
# Like Hash#[]=, but more rigidly! New keys are not created
|
231
|
+
# automagically. You should have used ConfigFiles.parameter for that.
|
232
|
+
def []=(key, val)
|
233
|
+
if @data.keys.include? key
|
234
|
+
@data[key] = val
|
235
|
+
else
|
236
|
+
raise NoKeyError, "uknown key '#{key}' for #{self.class}"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# Like Hash#each, iterate over parameter names and values.
|
241
|
+
# conf.each{|name, value| puts "#{name} is set to #{value}"}
|
242
|
+
def each(&blk)
|
243
|
+
@data.each(&blk)
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
109
247
|
|
248
|
+
def deferred_data
|
249
|
+
results = {}
|
250
|
+
@data.each do |k, v|
|
251
|
+
if v.is_a? Proc
|
252
|
+
results[k] = v.call(@data)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
results
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
110
259
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.2
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Guido De Rosa
|
@@ -14,28 +14,42 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-14 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
|
-
dependencies:
|
20
|
-
|
21
|
-
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: facets
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description: "A simple library to specify the format of configuration files and the way to turn them into Ruby objects. Ruby1.9 centric. Uses some lazy evaluation. No write support: it's strongly sugested to use ERB or other templating systems for that."
|
22
34
|
email: guido.derosa@vemarsas.it
|
23
35
|
executables: []
|
24
36
|
|
25
37
|
extensions: []
|
26
38
|
|
27
|
-
extra_rdoc_files:
|
28
|
-
|
39
|
+
extra_rdoc_files:
|
40
|
+
- README.rdoc
|
29
41
|
files:
|
30
|
-
-
|
42
|
+
- README.rdoc
|
31
43
|
- lib/configfiles.rb
|
44
|
+
- lib/configfiles/extensions/enumerable.rb
|
32
45
|
has_rdoc: true
|
33
46
|
homepage: http://github.com/gderosa/configfiles
|
34
47
|
licenses: []
|
35
48
|
|
36
49
|
post_install_message:
|
37
|
-
rdoc_options:
|
38
|
-
|
50
|
+
rdoc_options:
|
51
|
+
- --main
|
52
|
+
- README.rdoc
|
39
53
|
require_paths:
|
40
54
|
- lib
|
41
55
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -60,6 +74,6 @@ rubyforge_project:
|
|
60
74
|
rubygems_version: 1.3.7
|
61
75
|
signing_key:
|
62
76
|
specification_version: 3
|
63
|
-
summary: A simple library to specify the format of configuration files and the way to turn
|
77
|
+
summary: A simple library to specify the format of configuration files and the way to turn their data into Ruby objects.
|
64
78
|
test_files: []
|
65
79
|
|
data/configfiles-example.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
$LOAD_PATH.unshift 'lib'
|
2
|
-
|
3
|
-
require 'pp'
|
4
|
-
require 'ipaddr'
|
5
|
-
require 'configfiles'
|
6
|
-
|
7
|
-
class MyConfig < ConfigFiles::Base
|
8
|
-
|
9
|
-
class MyException < Exception; end
|
10
|
-
class MyArgumentError < ArgumentError; end
|
11
|
-
|
12
|
-
# I find more rubystic defining a "conversion"
|
13
|
-
# rater than statically declaring classes ;)
|
14
|
-
parameter :par_integer, :to_i
|
15
|
-
parameter :par_str # no conversion needed
|
16
|
-
parameter :par_custom do |s|
|
17
|
-
s.length
|
18
|
-
end
|
19
|
-
|
20
|
-
# receive an Enumerator from Parser, and turn into another Enumerator
|
21
|
-
#
|
22
|
-
# NOTE: Enumerable#map_enum would be cool ;-)
|
23
|
-
#
|
24
|
-
# TODO: use facets Enumerable#defer? Mmm, let's try to depend
|
25
|
-
# just on the std lib if possible...
|
26
|
-
#
|
27
|
-
enumerator :iplist do |ipstr|
|
28
|
-
IPAddr.new(ipstr)
|
29
|
-
end
|
30
|
-
|
31
|
-
validate do |data|
|
32
|
-
raise MyArgumentError if data[:par_custom] > 100
|
33
|
-
raise MyException unless data[:par_str] =~ /\S+/
|
34
|
-
# or you may use standard exceptions....
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
class MyKeyValueParser
|
41
|
-
|
42
|
-
include ConfigFiles::Parser
|
43
|
-
|
44
|
-
def self.read(io, opt_h={})
|
45
|
-
h = {}
|
46
|
-
io.each_line do |line|
|
47
|
-
key_re = '[\w\d_\-\.]+'
|
48
|
-
value_re = '[\w\d_\-\.]+'
|
49
|
-
if line =~ /(#{key_re})\s*=\s*(#{value_re})/
|
50
|
-
h[$1.to_sym] = $2
|
51
|
-
end
|
52
|
-
end
|
53
|
-
return h
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
class MyListSlurper
|
59
|
-
|
60
|
-
include ConfigFiles::Parser
|
61
|
-
|
62
|
-
def self.read(io, opt_h={})
|
63
|
-
if block_given?
|
64
|
-
io.each_line do |line|
|
65
|
-
yield line.strip if line =~ /\S/
|
66
|
-
end
|
67
|
-
else
|
68
|
-
enum_for :read, io
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
c = MyConfig.new
|
77
|
-
|
78
|
-
parse_result = MyKeyValueParser.read(File.open 'keyval.conf')
|
79
|
-
|
80
|
-
c.load parse_result
|
81
|
-
|
82
|
-
pp c
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|