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.
- data/lib/funfig/declaration_sugar.rb +49 -0
- data/lib/funfig/group.rb +205 -0
- data/lib/funfig/ini_parser.rb +307 -0
- data/lib/funfig/load.rb +87 -0
- data/lib/funfig/load_proxy.rb +41 -0
- data/lib/funfig/root.rb +32 -0
- data/lib/funfig/version.rb +1 -1
- data/lib/funfig.rb +5 -282
- data/test/test_ini_parser.rb +157 -0
- metadata +9 -2
@@ -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
|
data/lib/funfig/group.rb
ADDED
@@ -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
|
data/lib/funfig/load.rb
ADDED
@@ -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
|
data/lib/funfig/root.rb
ADDED
@@ -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
|
data/lib/funfig/version.rb
CHANGED
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
|
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.
|
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-
|
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:
|