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