configfiles 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -0,0 +1,5 @@
1
+ module Enumerable
2
+ def list_inspect(separator=', ')
3
+ map{|x| x.inspect}.join(separator)
4
+ end
5
+ end
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.2'
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
- class ArgumentError < ::ArgumentError; end
19
- class RuntimeError < ::RuntimeError; end
20
-
21
- @@parameters ||= {}
22
- @@options ||= {}
23
-
24
- # examples:
25
- # on :unknown_parameter, :fail | :accept | :ignore
26
- # on :unknown_parameter, {|str| str.to_i}
27
- #
28
- def self.on(name, value=nil, &block)
29
- if block
30
- @@options[name] = block
31
- elsif name == :unknown_parameter and value == :accept
32
- @@options[name] = lambda {|x| x}
33
- else
34
- @@options[name] = value
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
- def self.option(name)
39
- @@options[name]
40
- end
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
- def self.parameter(name, converter=nil, &converter_block)
43
- if converter
44
- if converter_block
45
- raise ArgumentError, 'you must either specify a symbol or a block'
46
- elsif converter.is_a? Hash
47
- converter_block = lambda {|x| converter[x]} # x is a String from conf file
48
- else #Symbol
49
- converter_block = lambda {|x| x.method(converter).call}
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
- else
52
- converter_block ||= lambda {|x| x}
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
- # A special kind of parameter, with a special kind of converter, which in turn
60
- # converts an Enumerator of Strings into an Enumerator of custom objects.
61
- # Working with Enumerators instead of
62
- # Arrays is the right thing to do when you deal with very long list of
63
- # names, IP adresses, URIs etc.
64
- def self.enumerator(name, &block)
65
- parameter name do |enum|
66
- Enumerator.new do |yielder|
67
- enum.each do |string|
68
- yielder << block.call(string)
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
- def self.validate(&block)
75
- @@validate = block
76
- end
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
- attr_accessor :options, :data
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(@data)
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 @options[:unknown_parameter] == :fail
198
+ elsif @@behavior[:unknown_parameter] == :fail
95
199
  raise RuntimeError, "unknown parameter #{key}" # otherwise ignore
96
- elsif @options[:unknown_parameter].respond_to? :call
97
- block = @options[:unknown_parameter]
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
- def flush
105
- @data = {}
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
- end
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
- - 2
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-02 00:00:00 +02:00
17
+ date: 2010-09-14 00:00:00 +02:00
18
18
  default_executable:
19
- dependencies: []
20
-
21
- description: "A simple library to specify the format of configuration files and the way to turn them into Ruby objects. Ruby1.9 centric. It supports lazy lists. No write support: it's strongly sugested to use ERB or other templating systems for that."
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
- - configfiles-example.rb
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 them into Ruby objects.
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
 
@@ -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
-