funfig 0.0.3 → 0.0.4

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.
@@ -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: