fuguta 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/fuguta.rb +354 -0
- metadata +54 -0
data/lib/fuguta.rb
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Fuguta
|
4
|
+
class Configuration
|
5
|
+
|
6
|
+
class ValidationError < StandardError
|
7
|
+
attr_reader :errors
|
8
|
+
def initialize(errors)
|
9
|
+
super("validation error")
|
10
|
+
@errors = errors
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.walk_tree(conf, &blk)
|
15
|
+
raise ArgumentError, "conf must be a 'Configuration'. Got '#{conf.class}'." unless conf.is_a?(Configuration)
|
16
|
+
|
17
|
+
blk.call(conf)
|
18
|
+
conf.config.values.each { |c|
|
19
|
+
case c
|
20
|
+
when Configuration
|
21
|
+
walk_tree(c, &blk)
|
22
|
+
when Hash
|
23
|
+
c.values.each { |c1| walk_tree(c1, &blk) if c1.is_a?(Configuration) }
|
24
|
+
when Array
|
25
|
+
c.each { |c1| walk_tree(c1, &blk) if c1.is_a?(Configuration) }
|
26
|
+
end
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
class Loader
|
31
|
+
def initialize(conf)
|
32
|
+
@conf = conf
|
33
|
+
end
|
34
|
+
|
35
|
+
def load(path)
|
36
|
+
buf = String.new
|
37
|
+
case path
|
38
|
+
when String
|
39
|
+
raise "does not exist: #{path}" unless File.exists?(path)
|
40
|
+
buf = File.read(path)
|
41
|
+
when IO
|
42
|
+
path.lines.each { |l| buf += l }
|
43
|
+
else
|
44
|
+
raise "Unknown type: #{path.class}"
|
45
|
+
end
|
46
|
+
|
47
|
+
@conf.parse_dsl do |me|
|
48
|
+
me.instance_eval(buf, path.to_s)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate
|
53
|
+
errors = []
|
54
|
+
Configuration.walk_tree(@conf) do |c|
|
55
|
+
c.validate(errors)
|
56
|
+
end
|
57
|
+
raise ValidationError, errors if errors.size > 0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class DSLProxy
|
62
|
+
def initialize(subject)
|
63
|
+
@subject = subject
|
64
|
+
@config = subject.config
|
65
|
+
end
|
66
|
+
|
67
|
+
def config
|
68
|
+
self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module ConfigurationMethods
|
73
|
+
module ClassMethods
|
74
|
+
# Helper method to define class specific configuration class.
|
75
|
+
#
|
76
|
+
# def_configuration(&blk) is available when you include this module
|
77
|
+
#
|
78
|
+
# # Example:
|
79
|
+
# class Base
|
80
|
+
# include Fuguta::Configuration::ConfigurationMethods
|
81
|
+
# def_configuration do
|
82
|
+
# param :xxxx
|
83
|
+
# param :yyyy
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Above example does exactly same thing as below:
|
88
|
+
#
|
89
|
+
# class Base
|
90
|
+
# class Configuration < Fuguta::Configuration
|
91
|
+
# param :xxxx
|
92
|
+
# param :yyyy
|
93
|
+
# end
|
94
|
+
# @configuration_class = Configuration
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# # Examples for new classes of Base inheritance.
|
98
|
+
# class A < Base
|
99
|
+
# def_configuration do
|
100
|
+
# param :zzzz
|
101
|
+
# end
|
102
|
+
# def_configuration do
|
103
|
+
# param :xyxy
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# p Configuration # => A::Configuration
|
107
|
+
# p Configuration.superclass # => Base::Configuration
|
108
|
+
# p @configuration_class # => A::Configuration
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# class B < A
|
112
|
+
# p self.configuration_class # => A::Configuration
|
113
|
+
# end
|
114
|
+
def def_configuration(&blk)
|
115
|
+
# create new configuration class if not exist.
|
116
|
+
if self.const_defined?(:Configuration, false)
|
117
|
+
unless self.const_get(:Configuration, false) < Fuguta::Configuration
|
118
|
+
raise TypeError, "#{self}::Configuration constant is defined already for another purpose."
|
119
|
+
end
|
120
|
+
else
|
121
|
+
self.const_set(:Configuration, Class.new(self.configuration_class || Fuguta::Configuration))
|
122
|
+
@configuration_class = self.const_get(:Configuration, false)
|
123
|
+
end
|
124
|
+
if blk
|
125
|
+
@configuration_class.module_eval(&blk)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def configuration_class
|
130
|
+
ConfigurationMethods.find_configuration_class(self)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.find_configuration_class(c)
|
135
|
+
begin
|
136
|
+
v = c.instance_variable_get(:@configuration_class)
|
137
|
+
return v if v
|
138
|
+
if c.const_defined?(:Configuration, false)
|
139
|
+
return c.const_get(:Configuration, false)
|
140
|
+
end
|
141
|
+
end while c = c.superclass
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
def self.included(klass)
|
147
|
+
klass.extend ClassMethods
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class << self
|
152
|
+
def on_initialize_hook(&blk)
|
153
|
+
@on_initialize_hooks << blk
|
154
|
+
end
|
155
|
+
|
156
|
+
def on_initialize_hooks
|
157
|
+
@on_initialize_hooks
|
158
|
+
end
|
159
|
+
|
160
|
+
# Show warning message if the old parameter is set.
|
161
|
+
def deprecated_warn_param(old_name, message=nil, &blk)
|
162
|
+
on_param_create_hook do |param_name, opts|
|
163
|
+
warn_msg = message || "WARN: Deprecated parameter: #{old_name}. Please use '#{param_name}'."
|
164
|
+
|
165
|
+
alias_param old_name, param_name
|
166
|
+
self.const_get(:DSL, false).class_eval %Q{
|
167
|
+
def #{old_name}(v)
|
168
|
+
STDERR.puts "#{warn_msg}"
|
169
|
+
#{param_name.to_s}(v)
|
170
|
+
end
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Raise an error if the old parameter is set.
|
176
|
+
def deprecated_error_param(old_name, message=nil)
|
177
|
+
on_param_create_hook do |param_name, opts|
|
178
|
+
err_msg = message || "ERROR: Parameter is no longer supported: #{old_name}. Please use '#{param_name}'."
|
179
|
+
|
180
|
+
alias_param old_name, param_name
|
181
|
+
self.const_get(:DSL, false).class_eval %Q{
|
182
|
+
def #{old_name}(v)
|
183
|
+
raise "#{err_msg}"
|
184
|
+
end
|
185
|
+
}
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def alias_param (alias_name, ref_name)
|
190
|
+
# getter
|
191
|
+
self.class_eval %Q{
|
192
|
+
# Ruby alias show error if the method to be defined later is
|
193
|
+
# set. So create method to call the reference method.
|
194
|
+
def #{alias_name.to_s}()
|
195
|
+
#{ref_name}()
|
196
|
+
end
|
197
|
+
}
|
198
|
+
|
199
|
+
# DSL setter
|
200
|
+
self.const_get(:DSL, false).class_eval %Q{
|
201
|
+
def #{alias_name}(v)
|
202
|
+
#{ref_name.to_s}(v)
|
203
|
+
end
|
204
|
+
alias_method :#{alias_name.to_s}=, :#{alias_name.to_s}
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
def param(name, opts={})
|
209
|
+
opts = opts.merge(@opts)
|
210
|
+
|
211
|
+
case opts[:default]
|
212
|
+
when Proc
|
213
|
+
# create getter method if proc is set as default value
|
214
|
+
self.class_exec {
|
215
|
+
define_method(name.to_s.to_sym) do
|
216
|
+
@config[name.to_s.to_sym] || self.instance_exec(&opts[:default])
|
217
|
+
end
|
218
|
+
}
|
219
|
+
else
|
220
|
+
on_initialize_hook do |c|
|
221
|
+
@config[name.to_s.to_sym] = opts[:default]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
@on_param_create_hooks.each { |blk|
|
226
|
+
blk.call(name.to_s.to_sym, opts)
|
227
|
+
}
|
228
|
+
self.const_get(:DSL, false).class_eval %Q{
|
229
|
+
def #{name}(v)
|
230
|
+
@config["#{name.to_s}".to_sym] = v
|
231
|
+
end
|
232
|
+
alias_method :#{name.to_s}=, :#{name.to_s}
|
233
|
+
}
|
234
|
+
|
235
|
+
@opts.clear
|
236
|
+
@on_param_create_hooks.clear
|
237
|
+
end
|
238
|
+
|
239
|
+
def load(*paths)
|
240
|
+
c = self.new
|
241
|
+
|
242
|
+
l = Loader.new(c)
|
243
|
+
|
244
|
+
paths.each { |path|
|
245
|
+
l.load(path)
|
246
|
+
}
|
247
|
+
l.validate
|
248
|
+
|
249
|
+
c
|
250
|
+
end
|
251
|
+
|
252
|
+
# Helper method defines "module DSL" under the current conf class.
|
253
|
+
#
|
254
|
+
# This does mostly same things as "module DSL" but using
|
255
|
+
# "module" statement get the "DSL" constant defined in unexpected
|
256
|
+
# location if you combind to use with other Ruby DSL syntax.
|
257
|
+
#
|
258
|
+
# Usage:
|
259
|
+
# class Conf1 < Configuration
|
260
|
+
# DSL do
|
261
|
+
# end
|
262
|
+
# end
|
263
|
+
def DSL(&blk)
|
264
|
+
self.const_get(:DSL, false).class_eval(&blk)
|
265
|
+
self
|
266
|
+
end
|
267
|
+
|
268
|
+
private
|
269
|
+
def inherited(klass)
|
270
|
+
super
|
271
|
+
klass.const_set(:DSL, Module.new)
|
272
|
+
klass.instance_eval {
|
273
|
+
@on_initialize_hooks = []
|
274
|
+
@opts = {}
|
275
|
+
@on_param_create_hooks = []
|
276
|
+
}
|
277
|
+
|
278
|
+
dsl_mods = []
|
279
|
+
c = klass
|
280
|
+
while c < Configuration && c.superclass.const_defined?(:DSL, false)
|
281
|
+
parent_dsl = c.superclass.const_get(:DSL, false)
|
282
|
+
if parent_dsl && parent_dsl.class === Module
|
283
|
+
dsl_mods << parent_dsl
|
284
|
+
end
|
285
|
+
c = c.superclass
|
286
|
+
end
|
287
|
+
# including order is ancestor -> descendants
|
288
|
+
dsl_mods.reverse.each { |i|
|
289
|
+
klass.const_get(:DSL, false).__send__(:include, i)
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
def on_param_create_hook(&blk)
|
294
|
+
@on_param_create_hooks << blk
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
attr_reader :config, :parent
|
299
|
+
|
300
|
+
def initialize(parent=nil)
|
301
|
+
unless parent.nil?
|
302
|
+
raise ArgumentError, "parent must be a 'Fuguta::Configuration'. Got '#{parent.class}'." unless parent.is_a?(Fuguta::Configuration)
|
303
|
+
end
|
304
|
+
@config = {}
|
305
|
+
@parent = parent
|
306
|
+
|
307
|
+
hook_lst = []
|
308
|
+
c = self.class
|
309
|
+
while c < Configuration
|
310
|
+
hook_lst << c.instance_variable_get(:@on_initialize_hooks)
|
311
|
+
c = c.superclass
|
312
|
+
end
|
313
|
+
|
314
|
+
hook_lst.reverse.each { |l|
|
315
|
+
l.each { |c|
|
316
|
+
self.instance_eval(&c)
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
after_initialize
|
321
|
+
end
|
322
|
+
|
323
|
+
def after_initialize
|
324
|
+
end
|
325
|
+
|
326
|
+
def validate(errors)
|
327
|
+
end
|
328
|
+
|
329
|
+
def parse_dsl(&blk)
|
330
|
+
dsl = self.class.const_get(:DSL, false)
|
331
|
+
raise "DSL module was not found" unless dsl && dsl.is_a?(Module)
|
332
|
+
|
333
|
+
cp_class = DSLProxy.dup
|
334
|
+
cp_class.__send__(:include, dsl)
|
335
|
+
cp = cp_class.new(self)
|
336
|
+
|
337
|
+
cp.instance_eval(&blk)
|
338
|
+
|
339
|
+
self
|
340
|
+
end
|
341
|
+
|
342
|
+
private
|
343
|
+
def method_missing(m, *args)
|
344
|
+
if @config.has_key?(m.to_sym)
|
345
|
+
@config[m.to_sym]
|
346
|
+
elsif @config.has_key?(m.to_s)
|
347
|
+
@config[m.to_s]
|
348
|
+
else
|
349
|
+
super
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
end
|
354
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fuguta
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: "1.0"
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Axsh co. LTD
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2013-04-24 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A configuration framework for Ruby programs
|
17
|
+
email:
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/fuguta.rb
|
26
|
+
homepage: https://github.com/axsh/fuguta
|
27
|
+
licenses: []
|
28
|
+
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: "0"
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.25
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: A configuration framework for Ruby programs
|
53
|
+
test_files: []
|
54
|
+
|