funfig 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|