funfig 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ require "funfig/group"
2
+
3
+ module Funfig
4
+ class ProxyParam < BasicObject
5
+ def initialize(group)
6
+ @group = group
7
+ end
8
+ def method_missing(name, value = NOT_SET, &block)
9
+ unless value.equal? NOT_SET
10
+ @group.param name, value
11
+ else
12
+ @group.param name, &block
13
+ end
14
+ end
15
+ end
16
+
17
+ class ProxyGroup < BasicObject
18
+ def initialize(group)
19
+ @group = group
20
+ end
21
+ def method_missing(name, &block)
22
+ @group.group name, &block
23
+ end
24
+ end
25
+
26
+ class Group
27
+ # syntax sugar proxy for declaring params
28
+ #
29
+ # :call-seq
30
+ # conf = Funfig.new do
31
+ # p.name_of_param :default_value
32
+ # p.other_param { calculate_default }
33
+ # end
34
+ def self.p
35
+ @proxy_param ||= ProxyParam.new(self)
36
+ end
37
+
38
+ # syntax sugar proxy for declaring group
39
+ #
40
+ # :call-seq
41
+ # conf = Funfig.new do
42
+ # g.name_of_group do
43
+ # end
44
+ # end
45
+ def self.g
46
+ @group_param ||= ProxyGroup.new(self)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,205 @@
1
+ module Funfig
2
+ NOT_SET = Object.new.freeze
3
+ class Group
4
+ def initialize(parent=nil) # :nodoc:
5
+ @parent = parent
6
+ end
7
+
8
+ # Get enclosing group
9
+ def _parent
10
+ @parent
11
+ end
12
+
13
+ # Get root of configuration
14
+ # :call-seq:
15
+ # _
16
+ # _root
17
+ def _root
18
+ @parent._root
19
+ end
20
+ alias _ _root
21
+
22
+ # Update config by hash
23
+ def update(hash)
24
+ if hash.respond_to?(:each)
25
+ hash.each{|k, v|
26
+ k = k.gsub('-', '_')
27
+ self.send("#{k}=", v)
28
+ }
29
+ end
30
+ self
31
+ end
32
+
33
+ # Iterate over parameter names
34
+ def each_param
35
+ return to_enum(:each_param) unless block_given?
36
+ self.class._params.each{|k, _|
37
+ yield k
38
+ }
39
+ end
40
+
41
+ # Iterate over parameters and values
42
+ # If called with parameter +true+, then iterate only over explicit setted parameters
43
+ #
44
+ # :call-seq:
45
+ # config.each{|name, value| puts "#{name} = #{value}"}
46
+ # config.each(true){|name, value| puts "#{name} = #{value}"}
47
+ def each(explicit=false)
48
+ return to_enum(:each, explicit) unless block_given?
49
+ self.class._params.each{|k, _|
50
+ yield k, send(k) unless explicit && !instance_variable_defined?("@#{k}")
51
+ }
52
+ end
53
+
54
+ # Convert configuration to hash
55
+ # If called with parameter +true+ than consider only explicit setted parameters
56
+ def to_hash(explicit=false)
57
+ h = {}
58
+ each(explicit){|k, v|
59
+ if Group === v
60
+ v = v.to_hash(explicit)
61
+ next if explicit && v.empty?
62
+ end
63
+ h[k] = v
64
+ }
65
+ h
66
+ end
67
+
68
+ def inspect
69
+ "<#{self.class.name} #{each.map{|k,v| "#{k}=#{v.inspect}"}.join(' ')}>"
70
+ end
71
+
72
+ # :stopdoc:
73
+
74
+ def _cache_get(k, &block)
75
+ _._cache.fetch(_sub_name(k), NOT_SET)
76
+ end
77
+
78
+ def _cache_set(k, v)
79
+ _._cache[_sub_name(k)] = v
80
+ end
81
+
82
+ def _sub_name(name)
83
+ self.class._sub_name(name)
84
+ end
85
+
86
+ def _path
87
+ self.class._path
88
+ end
89
+
90
+ def self._params
91
+ @params ||= {}
92
+ end
93
+
94
+ def self.initialize_clone(arg)
95
+ super
96
+ if @params
97
+ params, @params = @params, {}
98
+ params.each{|name, par|
99
+ if par.is_a?(Class) && Group >= par
100
+ @params[name] = par.clone
101
+ else
102
+ @params[name] = par
103
+ end
104
+ }
105
+ end
106
+ end
107
+
108
+ def self._sub_name(name)
109
+ "#{_path}.#{name}"
110
+ end
111
+
112
+ # :startdoc:
113
+
114
+ # Define named group of values
115
+ #
116
+ # :call-seq:
117
+ # config = Funfig.new do
118
+ # group :name_of_group do
119
+ # end
120
+ # end
121
+ def self.group(name, &block)
122
+ name = name.to_sym
123
+ vname = :"@#{name}"
124
+ _prev = _params[name]
125
+ klass = _prev.is_a?(Class) && Group >= _prev ? _prev : Class.new(Group)
126
+ _params[name] = klass
127
+ path = _sub_name(name)
128
+ const_set name.capitalize, klass
129
+
130
+ klass.send(:define_singleton_method, :_path) do
131
+ path
132
+ end
133
+
134
+ define_method(name) do |*args, &block|
135
+ instance_variable_get(vname) ||
136
+ instance_variable_set(vname, klass.new(self))
137
+ end
138
+
139
+ define_method("#{name}=") do |hash|
140
+ send(name).update(hash) if hash
141
+ end
142
+
143
+ define_method("#{name}_reset!") do
144
+ _._cache_clear!
145
+ remove_instance_variable(vname) if instance_variable_defined?(vname)
146
+ end
147
+ klass.class_exec &block
148
+ end
149
+
150
+ # define named parameter
151
+ #
152
+ # :call-seq:
153
+ # config = Funfig.new do
154
+ # param :name_of_param do calculate_default_value end
155
+ # end
156
+ def self.param(name, value = NOT_SET, &block)
157
+ _params[name] = true
158
+ vname = :"@#{name}"
159
+ name = name.to_sym
160
+
161
+ block ||= proc{
162
+ begin
163
+ value.dup
164
+ rescue TypeError
165
+ block = proc { value }
166
+ value
167
+ end
168
+ }
169
+
170
+ define_method(name) do |*args|
171
+ if instance_variable_defined?(vname)
172
+ instance_variable_get(vname)
173
+ else
174
+ if (v = _cache_get(name)).equal? NOT_SET
175
+ raise "Parameter #{_sub_name(name)} must be set!" unless block
176
+ _cache_set(name, (v = instance_exec &block))
177
+ end
178
+ v
179
+ end
180
+ end
181
+
182
+ define_method("#{name}=") do |v|
183
+ _._cache_clear!
184
+ instance_variable_set(vname, v)
185
+ end
186
+
187
+ define_method("#{name}_reset!") do
188
+ _._cache_clear!
189
+ remove_instance_variable(vname) if instance_variable_defined?(vname)
190
+ end
191
+ end
192
+
193
+ # Create a copy of configuration scheme
194
+ #
195
+ # :call-seq:
196
+ # other_conf = config.clone do
197
+ # param :other_value do other_default end
198
+ # end
199
+ def self.clone(&block)
200
+ new = super
201
+ new.class_exec &block if block_given?
202
+ new
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,307 @@
1
+ require 'yaml'
2
+ require 'strscan'
3
+
4
+ module Funfig
5
+ # fake ini file loader
6
+ # loads *.ini file by converting it to YAML first
7
+ class IniParser
8
+ attr :hash
9
+ attr_accessor :string
10
+
11
+ def initialize(string = nil, file = nil)
12
+ @string = string
13
+ @file = file
14
+ end
15
+
16
+ def parse(string = @string, file = @file)
17
+ raise "Should specify string for parse" unless String === string
18
+ prepare_parse(string, file)
19
+ do_parse
20
+ finish_parse
21
+ end
22
+
23
+ class << self
24
+ def parse_file(file)
25
+ if file.respond_to?(:read)
26
+ new(file.read).parse
27
+ else
28
+ new(File.read(file), file).parse
29
+ end
30
+ end
31
+ end
32
+ private
33
+ def prepare_parse(string, file)
34
+ @file = file
35
+ @scanner = StringScanner.new(string)
36
+ @hash = {}
37
+ @line_number = 0
38
+ end
39
+
40
+ def finish_parse
41
+ @scanner = @line_number = nil
42
+ @hash
43
+ end
44
+
45
+ def line_number(line = @line_number)
46
+ if @file
47
+ "#{@file}:#{line}"
48
+ else
49
+ line
50
+ end
51
+ end
52
+
53
+ def do_parse
54
+ section = "global"
55
+ yaml = false
56
+ while true
57
+ skip_empty_lines!
58
+ break if @scanner.eos?
59
+ if @scanner.scan(/\s*\[\s*([-.\w]+)\s*\]/)
60
+ section = @scanner[1]
61
+ elsif @scanner.scan(/\s*([-.\w]+)\s*[:=]\s*/)
62
+ param = @scanner[1]
63
+ line_no = @line_number
64
+ if @scanner.match?(/YAML([- #\[{"'<]|$)/)
65
+ @scanner.skip(/YAML\s*/)
66
+ yaml = true
67
+ elsif @scanner.skip(/WORDS[ \t]+/)
68
+ words = true
69
+ end
70
+
71
+ if @scanner.scan(/\s*<<(\w+)/)
72
+ multiline = @scanner[1]
73
+ skip_eol!
74
+ unless @scanner.scan(/(.*?)^([ \t]*)#{multiline}/m)
75
+ raise "Multiline '#{multiline}' started at line #{line_number(line_no)} is not finished"
76
+ end
77
+ adjust_lineno!(@scanner[1])
78
+ value = @scanner[1].chomp
79
+ if words
80
+ value = scan_multiline_words(value)
81
+ elsif !yaml
82
+ value = unindent(value, @scanner[2])
83
+ end
84
+ elsif yaml
85
+ value = scan_yaml
86
+ elsif words
87
+ value = scan_words
88
+ else
89
+ value = scan_simple
90
+ end
91
+
92
+ if yaml
93
+ value = YAML.load(value)
94
+ yaml = false
95
+ end
96
+
97
+ set_value(section, param, value, line_no)
98
+ else
99
+ raise "Parse error at line #{line_number}"
100
+ end
101
+ end
102
+ end
103
+
104
+ EMPTY = /\s*([#;].*?)?(?:\r\n|\r|\n|\z)/
105
+ def skip_eol!
106
+ if (s = @scanner.skip(EMPTY)) && s > 0
107
+ @line_number += 1
108
+ end
109
+ end
110
+
111
+ def adjust_lineno!(str)
112
+ @line_number += str.scan(/\r\n|\r|\n/).size
113
+ end
114
+
115
+ def skip_empty_lines!
116
+ while skip_eol!; end
117
+ end
118
+
119
+ REPLACE_SQUOTE = {"\\'" => "'", "\\\\" => "\\"}
120
+ REPLACE_DQUOTE = Hash.new{|h,k| h[k] = eval("\"#{k}\"")}
121
+ def _replace_squote(str)
122
+ str.gsub(/\\[\\']/, REPLACE_SQUOTE)
123
+ end
124
+
125
+ def _replace_dquote(str)
126
+ str.gsub(/\\(x[a-fA-F\d]{1,2}|u[a-fA-F\d]{4}|[^xu])/, REPLACE_DQUOTE)
127
+ end
128
+
129
+ def convert_value(value)
130
+ case value
131
+ when '', 'null'
132
+ nil
133
+ when /\A[+-]?[1-9]\d*\z/
134
+ value.to_i
135
+ when /\A[+-]?\d*(\.\d+)?([eE][+-]?\d+)?\z/
136
+ value.to_f
137
+ when 'True', 'TRUE', 'true', 'Yes', 'yes'
138
+ true
139
+ when 'False', 'FALSE', 'false', 'No', 'no'
140
+ false
141
+ else
142
+ value
143
+ end
144
+ end
145
+
146
+ def scan_simple
147
+ val = ''
148
+ quoted = false
149
+ scan_value do |kind, string|
150
+ case kind
151
+ when :space
152
+ val << ' '
153
+ when :raw
154
+ val << string
155
+ when :single_quote
156
+ quoted = true
157
+ val << _replace_squote(string)
158
+ when :double_quote
159
+ quoted = true
160
+ val << _replace_dquote(string)
161
+ end
162
+ end
163
+ unless quoted
164
+ convert_value(val)
165
+ else
166
+ val
167
+ end
168
+ end
169
+
170
+ def scan_yaml
171
+ val = ''
172
+ scan_value do |kind, string|
173
+ case kind
174
+ when :space
175
+ val << ' '
176
+ when :raw
177
+ val << string
178
+ when :single_quote
179
+ val << "'#{string}'"
180
+ when :double_quote
181
+ val << %{"#{string}"}
182
+ end
183
+ end
184
+ val
185
+ end
186
+
187
+ def scan_words
188
+ val = ['']
189
+ quoted = false
190
+ scan_value do |kind, string|
191
+ case kind
192
+ when :space
193
+ val[-1] = convert_value(val[-1]) unless quoted
194
+ quoted = false
195
+ val << ''
196
+ when :raw
197
+ val[-1] << string
198
+ when :single_quote
199
+ val[-1] << _replace_squote(string)
200
+ quoted = true
201
+ when :double_quote
202
+ val[-1] << _replace_dquote(string)
203
+ quoted = true
204
+ end
205
+ end
206
+ return [] if val == ['']
207
+ val[-1] = convert_value(val[-1]) unless quoted
208
+ val
209
+ end
210
+
211
+ def scan_value
212
+ empty = true
213
+ while ! skip_eol! && ! @scanner.eos?
214
+ if @scanner.skip(/[ \t]+/)
215
+ yield(:space) unless empty
216
+ end
217
+ if @scanner.scan(/([^"'#;\s\r\n]+)/)
218
+ yield(:raw, @scanner[1])
219
+ elsif s = @scanner.scan(/'((?:\\'|[^'])*)'/)
220
+ adjust_lineno!(s)
221
+ yield(:single_quote, @scanner[1])
222
+ elsif s = @scanner.scan(/"(\\.|[^"]*)"/)
223
+ adjust_lineno!(s)
224
+ yield(:double_quote, @scanner[1])
225
+ else
226
+ break
227
+ end
228
+ empty = false
229
+ end
230
+ end
231
+
232
+ def scan_multiline_words(string)
233
+ scanner = StringScanner.new(string)
234
+ value = []
235
+ while !scanner.eos?
236
+ scanner.skip(/\s+/)
237
+ if s = scanner.scan(/([^"'#;\s\r\n]+)/)
238
+ value << convert_value(s)
239
+ elsif scanner.scan(/'((?:\\'|[^'])*)'/)
240
+ value << _replace_squote(scanner[1])
241
+ elsif s = scanner.scan(/"(\\.|[^"]*)"/)
242
+ value << _replace_dquote(scanner[1])
243
+ else
244
+ break
245
+ end
246
+ end
247
+ value
248
+ end
249
+
250
+ def set_value(section, param, value, line_no)
251
+ key = "#{section}.#{param}"
252
+ seq = key.split('.')
253
+ seq.shift if seq.first == 'global'
254
+ hash = @hash
255
+ path = []
256
+ while seq.size > 1
257
+ path << seq.shift
258
+ if current = hash[path.last]
259
+ unless Hash === current
260
+ raise "Could not insert [#{section}] #{param} on line #{line_number(line_no)} cause #{path.join('.')} is not a hash"
261
+ end
262
+ else
263
+ hash[path.last] = current = {}
264
+ end
265
+ hash = current
266
+ end
267
+ hash[seq.first] = value
268
+ end
269
+
270
+ def count_space(line)
271
+ n, i = 0, 0
272
+ while true
273
+ if line[i] == ?\x20
274
+ n += 1
275
+ elsif line[i] == ?\t
276
+ n = (n / 8) * 8 + 8
277
+ else
278
+ break
279
+ end
280
+ i += 1
281
+ end
282
+ n
283
+ end
284
+
285
+ def skip_space(line, nspace)
286
+ n, i = 0, 0
287
+ while n < nspace
288
+ if line[i] == ?\x20
289
+ n += 1
290
+ elsif line[i] == ?\t
291
+ n = (n / 8) * 8 + 8
292
+ else
293
+ break
294
+ end
295
+ i += 1
296
+ end
297
+ raise unless n >= nspace
298
+ (" "*(n - nspace) + line[i..-1])
299
+ end
300
+
301
+ def unindent(value, space)
302
+ nspace = count_space(space)
303
+ value.each_line.map{|line| skip_space(line, nspace)}.join
304
+ end
305
+
306
+ end
307
+ end
@@ -0,0 +1,87 @@
1
+ require 'funfig/load_proxy'
2
+ require 'funfig/ini_parser'
3
+ module Funfig
4
+ class Group
5
+ # Update config by yaml file
6
+ def load_yaml(filename)
7
+ params = YAML.load_file(filename)
8
+ update(params)
9
+ end
10
+
11
+ # Update config by yaml string
12
+ def load_yaml_string(string)
13
+ params = YAML.load(string)
14
+ update(params)
15
+ end
16
+
17
+ # Update config by ini file
18
+ def load_ini(filename)
19
+ params = IniParser.parse_file(filename)
20
+ update(params)
21
+ end
22
+
23
+ # Update config by ini string
24
+ def load_ini_string(string, file = nil)
25
+ params = IniParser.new.parse(string, file)
26
+ update(params)
27
+ end
28
+
29
+ # Update config by evaluating ruby file
30
+ def load_ruby(file)
31
+ rb = File.read(file)
32
+ load_ruby_string(rb, file)
33
+ end
34
+
35
+ # Update config by evaluating string containing ruby code
36
+ def load_ruby_string(string, file = nil)
37
+ unless file
38
+ for cl in caller
39
+ next if cl =~ %r{funfig/load\.rb}
40
+ file, line = caller.first.match(/^(.*):(\d*)/).captures
41
+ break
42
+ end
43
+ end
44
+ LoadProxy.new(self).instance_eval string, file, line.to_i
45
+ self
46
+ end
47
+
48
+ # Update config by executing block inside of proxy
49
+ def exec(&block)
50
+ LoadProxy.new(self).instance_exec &block
51
+ end
52
+ end
53
+
54
+ class Root
55
+ # Load config from yaml file
56
+ def self.from_yaml_file(file)
57
+ self.new.load_yaml(file)
58
+ end
59
+
60
+ # Load config from ini file
61
+ def self.from_ini_file(file)
62
+ self.new.load_ini(file)
63
+ end
64
+
65
+ # Evaluate config file inside of configuration object
66
+ def self.from_ruby_file(file)
67
+ self.new.load_ruby(file)
68
+ end
69
+
70
+ def self.from_file(file)
71
+ case file
72
+ when /\.yml$/, /\.yaml$/
73
+ from_yaml_file(file)
74
+ when /\.ini$/
75
+ from_ini_file(file)
76
+ when /\.rb$/
77
+ from_ruby_file(file)
78
+ when Hash
79
+ if fl = file[:yaml]
80
+ from_yaml_file(fl)
81
+ elsif fl = file[:ruby]
82
+ from_ruby_file(fl)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,41 @@
1
+ module Funfig
2
+ class LoadProxy < BasicObject
3
+ def initialize(group)
4
+ @group = group
5
+ methods = {}
6
+ group.public_methods(false).each{|m| methods[m] = true}
7
+ @methods = methods
8
+ @params = @group.class._params
9
+ end
10
+
11
+ def _parent
12
+ @group._parent
13
+ end
14
+
15
+ def _root
16
+ @group._root
17
+ end
18
+ alias _ _root
19
+
20
+ def method_missing(name, *args, &block)
21
+ if @methods.include?(name)
22
+ if par = @params[name]
23
+ if par.is_a?(::Class) && par < Group && args.empty? && block
24
+ proxy = LoadProxy.new(@group.send(name))
25
+ proxy.instance_exec &block
26
+ elsif !args.empty?
27
+ raise "Could not pass both block and value for option" if block
28
+ raise "Could set only single value for option" if args.size > 1
29
+ @group.send("#{name}=", args[0])
30
+ else
31
+ @group.send(name)
32
+ end
33
+ else
34
+ @group.send(name, *args, &block)
35
+ end
36
+ else
37
+ ::Kernel.raise ::NotImplementedError, "no configuration option #{@group._sub_name(name)}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ require 'funfig/group'
2
+ module Funfig
3
+ # :stopdoc:
4
+ class Root < Group
5
+ attr_reader :_cache
6
+ def initialize
7
+ @_cache = {}
8
+ end
9
+
10
+ def _cache_clear!
11
+ @_cache.clear
12
+ end
13
+
14
+ def _parent
15
+ raise "Already at root"
16
+ end
17
+
18
+ def _root
19
+ self
20
+ end
21
+ alias _ _root
22
+
23
+ def self._path
24
+ ""
25
+ end
26
+
27
+ def self._sub_name(name)
28
+ name.to_s
29
+ end
30
+ end
31
+ # :startdoc:
32
+ end
@@ -1,3 +1,3 @@
1
1
  module Funfig
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/funfig.rb CHANGED
@@ -1,294 +1,17 @@
1
1
  require "funfig/version"
2
+ require "funfig/group"
3
+ require "funfig/root"
4
+ require "funfig/declaration_sugar"
5
+ require "funfig/load"
2
6
  require "yaml" unless defined? YAML
3
7
 
4
8
  module Funfig
5
- NOT_SET = Object.new.freeze
6
- class ProxyParam < BasicObject
7
- def initialize(group)
8
- @group = group
9
- end
10
- def method_missing(name, value = NOT_SET, &block)
11
- unless value.equal? NOT_SET
12
- @group.param name, value
13
- else
14
- @group.param name, &block
15
- end
16
- end
17
- end
18
-
19
- class ProxyGroup < BasicObject
20
- def initialize(group)
21
- @group = group
22
- end
23
- def method_missing(name, &block)
24
- @group.group name, &block
25
- end
26
- end
27
-
28
- class Group
29
- def initialize(parent=nil) # :nodoc:
30
- @parent = parent
31
- end
32
-
33
- # Get enclosing group
34
- def _parent
35
- @parent
36
- end
37
-
38
- # Get root of configuration
39
- # :call-seq:
40
- # _
41
- # _root
42
- def _
43
- @parent._
44
- end
45
- alias _root _
46
-
47
- # Update config by yaml file
48
- def load_file(filename)
49
- params = YAML.load_file(filename)
50
- update(params)
51
- end
52
-
53
- # Update config by hash
54
- def update(hash)
55
- if hash.respond_to?(:each)
56
- hash.each{|k, v|
57
- self.send("#{k}=", v)
58
- }
59
- end
60
- end
61
-
62
- # Iterate over parameter names
63
- def each_param
64
- return to_enum(:each_param) unless block_given?
65
- self.class._params.each{|k, _|
66
- yield k
67
- }
68
- end
69
-
70
- # Iterate over parameters and values
71
- # If called with parameter +true+, then iterate only over explicit setted parameters
72
- #
73
- # :call-seq:
74
- # config.each{|name, value| puts "#{name} = #{value}"}
75
- # config.each(true){|name, value| puts "#{name} = #{value}"}
76
- def each(explicit=false)
77
- return to_enum(:each, explicit) unless block_given?
78
- self.class._params.each{|k, _|
79
- yield k, send(k) unless explicit && !instance_variable_defined?("@#{k}")
80
- }
81
- end
82
-
83
- # Convert configuration to hash
84
- # If called with parameter +true+ than consider only explicit setted parameters
85
- def to_hash(explicit=false)
86
- h = {}
87
- each(explicit){|k, v|
88
- if Group === v
89
- v = v.to_hash(explicit)
90
- next if explicit && v.empty?
91
- end
92
- h[k] = v
93
- }
94
- h
95
- end
96
-
97
- def inspect
98
- "<#{self.class.name} #{each.map{|k,v| "#{k}=#{v.inspect}"}.join(' ')}>"
99
- end
100
-
101
- # :stopdoc:
102
-
103
- def _cache_get(k, &block)
104
- _._cache.fetch(_sub_name(k), NOT_SET)
105
- end
106
-
107
- def _cache_set(k, v)
108
- _._cache[_sub_name(k)] = v
109
- end
110
-
111
- def _sub_name(name)
112
- self.class._sub_name(name)
113
- end
114
-
115
- def _path
116
- self.class._path
117
- end
118
-
119
- def self._params
120
- @params ||= {}
121
- end
122
-
123
- def self.initialize_clone(arg)
124
- super
125
- if @params
126
- params, @params = @params, {}
127
- params.each{|name, par|
128
- if par.is_a?(Class) && Group >= par
129
- @params[name] = par.clone
130
- else
131
- @params[name] = par
132
- end
133
- }
134
- end
135
- end
136
-
137
- def self._sub_name(name)
138
- "#{_path}.#{name}"
139
- end
140
-
141
- # :startdoc:
142
-
143
- # Define named group of values
144
- #
145
- # :call-seq:
146
- # config = Funfig.new do
147
- # group :name_of_group do
148
- # end
149
- # end
150
- def self.group(name, &block)
151
- name = name.to_sym
152
- vname = :"@#{name}"
153
- _prev = _params[name]
154
- klass = _prev.is_a?(Class) && Group >= _prev ? _prev : Class.new(Group)
155
- _params[name] = klass
156
- path = _sub_name(name)
157
- const_set name.capitalize, klass
158
-
159
- klass.send(:define_singleton_method, :_path) do
160
- path
161
- end
162
-
163
- define_method(name) do
164
- instance_variable_get(vname) ||
165
- instance_variable_set(vname, klass.new(self))
166
- end
167
-
168
- define_method("#{name}=") do |hash|
169
- send(name).update(hash) if hash
170
- end
171
-
172
- define_method("#{name}_reset!") do
173
- _._cache_clear!
174
- remove_instance_variable(vname) if instance_variable_defined?(vname)
175
- end
176
- klass.class_exec ProxyParam.new(klass), &block
177
- end
178
-
179
- # define named parameter
180
- #
181
- # :call-seq:
182
- # config = Funfig.new do
183
- # param :name_of_param do calculate_default_value end
184
- # end
185
- def self.param(name, value = NOT_SET, &block)
186
- _params[name] = true
187
- vname = :"@#{name}"
188
- name = name.to_sym
189
-
190
- block ||= proc{
191
- begin
192
- value.dup
193
- rescue TypeError
194
- block = proc { value }
195
- value
196
- end
197
- }
198
-
199
- define_method(name) do
200
- if instance_variable_defined?(vname)
201
- instance_variable_get(vname)
202
- else
203
- if (v = _cache_get(name)).equal? NOT_SET
204
- raise "Parameter #{_sub_name(name)} must be set!" unless block
205
- _cache_set(name, (v = instance_eval &block))
206
- end
207
- v
208
- end
209
- end
210
-
211
- define_method("#{name}=") do |v|
212
- _._cache_clear!
213
- instance_variable_set(vname, v)
214
- end
215
-
216
- define_method("#{name}_reset!") do
217
- _._cache_clear!
218
- remove_instance_variable(vname) if instance_variable_defined?(vname)
219
- end
220
- end
221
-
222
- # syntax sugar proxy for declaring params
223
- #
224
- # :call-seq
225
- # conf = Funfig.new do
226
- # p.name_of_param :default_value
227
- # p.other_param { calculate_default }
228
- # end
229
- def self.p
230
- @proxy_param ||= ProxyParam.new(self)
231
- end
232
-
233
- # syntax sugar proxy for declaring group
234
- #
235
- # :call-seq
236
- # conf = Funfig.new do
237
- # g.name_of_group do
238
- # end
239
- # end
240
- def self.g
241
- @group_param ||= ProxyGroup.new(self)
242
- end
243
-
244
- # Create a copy of configuration scheme
245
- #
246
- # :call-seq:
247
- # other_conf = config.clone do
248
- # param :other_value do other_default end
249
- # end
250
- def self.clone(&block)
251
- new = super
252
- new.send(:class_eval, &block) if block_given?
253
- new
254
- end
255
- end
256
-
257
- # :stopdoc:
258
- class Root < Group
259
- attr_reader :_cache
260
- def initialize
261
- @_cache = {}
262
- end
263
-
264
- def _cache_clear!
265
- @_cache.clear
266
- end
267
-
268
- def _parent
269
- raise "Already at root"
270
- end
271
-
272
- def _
273
- self
274
- end
275
-
276
- def self._path
277
- ""
278
- end
279
-
280
- def self._sub_name(name)
281
- name.to_s
282
- end
283
- end
284
- # :startdoc:
285
-
286
9
  # Create configuration schema
287
10
  #
288
11
  # :call-seq:
289
12
  def self.new(&block)
290
13
  conf = Class.new(Root)
291
- conf.class_exec ProxyParam.new(conf), &block
14
+ conf.class_exec &block
292
15
  conf
293
16
  end
294
17
  end
@@ -0,0 +1,157 @@
1
+ require 'minitest/autorun'
2
+ require 'funfig/ini_parser'
3
+
4
+ describe Funfig::IniParser do
5
+ let(:parser){ Funfig::IniParser.new }
6
+
7
+ def parse(string)
8
+ parser.parse(string)
9
+ end
10
+
11
+ it "should parse empty string as empty hash" do
12
+ parse('').must_equal({})
13
+ end
14
+
15
+ it "should parse simple value" do
16
+ parse('a=').must_equal( {'a'=>nil} )
17
+ parse("a= \t ").must_equal( {'a'=>nil} )
18
+ parse("a= \t # comment").must_equal( {'a'=>nil} )
19
+ parse('a=1').must_equal( {'a'=>1} )
20
+ parse('a= 1 ').must_equal( {'a'=>1} )
21
+ parse('a= 1 # comment').must_equal( {'a'=>1} )
22
+ parse('a=1.0').must_equal( {'a'=>1.0} )
23
+ parse('a= 1.0 ').must_equal( {'a'=>1.0} )
24
+ parse('a= 1.0 # comment').must_equal( {'a'=>1.0} )
25
+ parse('a= "b"').must_equal( {'a'=>'b'} )
26
+ parse('a= a"b"').must_equal( {'a'=>'ab'} )
27
+ parse('a= a "b"').must_equal( {'a'=>'a b'} )
28
+ parse('a= a "\\nb"').must_equal( {'a'=>"a \nb"} )
29
+ parse("a= a '\\nb'").must_equal( {'a'=>"a \\nb"} )
30
+ parse("a= a \t '\nb'").must_equal( {'a'=>"a \nb"} )
31
+ parse("a= a \"sdf\nb\"").must_equal( {'a'=>"a sdf\nb"} )
32
+ end
33
+
34
+ it 'should parse boolean value' do
35
+ parse('a= true').must_equal( {'a'=>true} )
36
+ parse('a= True').must_equal( {'a'=>true} )
37
+ parse('a= TRUE').must_equal( {'a'=>true} )
38
+ parse('a= Yes ').must_equal( {'a'=>true} )
39
+ parse('a= yes ').must_equal( {'a'=>true} )
40
+ parse('a= false').must_equal( {'a'=>false} )
41
+ parse('a= False').must_equal( {'a'=>false} )
42
+ parse('a= FALSE').must_equal( {'a'=>false} )
43
+ parse('a= No ').must_equal( {'a'=>false} )
44
+ parse('a= no ').must_equal( {'a'=>false} )
45
+ end
46
+
47
+ it "should parse couple of values" do
48
+ parse("a = 1\n b = 3").must_equal( {'a'=>1, 'b'=>3})
49
+ parse("a=1 #comment\n ; other comment;\n b = 3").must_equal(
50
+ {'a'=>1, 'b'=>3}
51
+ )
52
+ end
53
+
54
+ it "should parse nested value" do
55
+ parse("a.x = 1").must_equal( {'a'=>{'x'=>1}} )
56
+ parse("a.x.z = 1").must_equal( {'a'=>{'x'=>{'z'=>1}}} )
57
+ end
58
+
59
+ it "should consider section" do
60
+ parse("[sec]\na.x = 1").must_equal( {'sec'=>{'a'=>{'x'=>1}}} )
61
+ parse("[sec.a]\nx = 1").must_equal( {'sec'=>{'a'=>{'x'=>1}}} )
62
+ end
63
+
64
+ it "should consider 'global' section as empty" do
65
+ parse("[global]\na.x = 1").must_equal( {'a'=>{'x'=>1}} )
66
+ parse("[global.a]\nx = 1").must_equal( {'a'=>{'x'=>1}} )
67
+ end
68
+
69
+ it "should parse multiline value" do
70
+ val = parse <<-EOF
71
+ [section]
72
+ my-value = <<EOV
73
+ hello
74
+ world!
75
+ (and others)
76
+ EOV
77
+ EOF
78
+ val.must_equal({'section'=>{
79
+ 'my-value'=>" hello\n world!\n(and others)"
80
+ }})
81
+ val = parse <<-EOF
82
+ [section]
83
+ first_value = No
84
+ my-value = <<EOV
85
+ hello
86
+ world!
87
+ EOV
88
+ other_value = false
89
+ EOF
90
+ val.must_equal({'section'=>{
91
+ 'first_value' => false,
92
+ 'my-value' => " hello\n world!",
93
+ 'other_value' => false
94
+ }})
95
+ end
96
+
97
+ it "should parse oneline yaml" do
98
+ parse('a = YAML "hi"').must_equal('a'=>'hi')
99
+ parse('a = YAML hi').must_equal('a'=>'hi')
100
+ parse('a = YAML hi: ho').must_equal('a'=>{'hi'=>'ho'})
101
+ parse('a = YAML {hi: ho}').must_equal('a'=>{'hi'=>'ho'})
102
+ parse('a = YAML [hi, ho]').must_equal('a'=>['hi', 'ho'])
103
+ parse('a = YAML 2011-01-01').must_equal('a'=>Date.new(2011, 1, 1))
104
+ parse('a = YAML ["hi:ho", "ya:yo"]').must_equal(
105
+ 'a'=>['hi:ho', 'ya:yo']
106
+ )
107
+ end
108
+
109
+ it "should parse multiline yaml" do
110
+ val = parse <<-EOF
111
+ a = YAML <<EOV
112
+ - b : 4.0
113
+ c : Yes
114
+ - x : {1 : 2012-06-09}
115
+ EOV
116
+ EOF
117
+ val.must_equal({
118
+ 'a' => [
119
+ { 'b' => 4.0, 'c' => true },
120
+ { 'x' => {1 => Date.new(2012, 6, 9)} }
121
+ ]
122
+ })
123
+ end
124
+
125
+ it "should parse oneline words" do
126
+ val = parse <<-EOF
127
+ sockets = WORDS tcp://127.0.0.1:80 udp://10.0.0.1:900
128
+ paths = WORDS "/here and/there" "/search/ and destroy" true
129
+ empty = WORDS # nothing here
130
+ EOF
131
+ val.must_equal(
132
+ 'sockets' => ['tcp://127.0.0.1:80', 'udp://10.0.0.1:900'],
133
+ 'paths' => ['/here and/there', '/search/ and destroy', true],
134
+ 'empty' => []
135
+ )
136
+ end
137
+
138
+ it "should parse multiline words" do
139
+ val = parse <<-EOF
140
+ sockets = WORDS <<EOV
141
+ tcp://127.0.0.1:80 udp://10.0.0.1:900
142
+ "/here and/there" "/search/ and destroy"
143
+ true
144
+ EOV
145
+ empty = WORDS <<EOV
146
+ EOV
147
+ EOF
148
+ val.must_equal(
149
+ 'sockets' => [
150
+ 'tcp://127.0.0.1:80', 'udp://10.0.0.1:900',
151
+ '/here and/there', '/search/ and destroy',
152
+ true
153
+ ],
154
+ 'empty' => []
155
+ )
156
+ end
157
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: funfig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-12 00:00:00.000000000 Z
12
+ date: 2012-06-08 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Defines configuration schema with calculable defaults
15
15
  email:
@@ -24,7 +24,14 @@ files:
24
24
  - Rakefile
25
25
  - funfig.gemspec
26
26
  - lib/funfig.rb
27
+ - lib/funfig/declaration_sugar.rb
28
+ - lib/funfig/group.rb
29
+ - lib/funfig/ini_parser.rb
30
+ - lib/funfig/load.rb
31
+ - lib/funfig/load_proxy.rb
32
+ - lib/funfig/root.rb
27
33
  - lib/funfig/version.rb
34
+ - test/test_ini_parser.rb
28
35
  homepage: ''
29
36
  licenses: []
30
37
  post_install_message: