Welcome to the "trac"-ing site of soap4r!
[soap4r] [httpclient] [openpgp4u] [pkcs1] [logger] [csv] [vtr]

root/trunk/lib/soap/property.rb

Revision 1824, 7.0 kB (checked in by nahi, 2 years ago)
  • Copyright notice updated. add '2000-2007' uniformly.
  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 # soap/property.rb: SOAP4R - Property implementation.
2 # Copyright (C) 2000-2007  NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
4 # This program is copyrighted free software by NAKAMURA, Hiroshi.  You can
5 # redistribute it and/or modify it under the same terms of Ruby's license;
6 # either the dual license version in 2003, or any later version.
7
8
9 module SOAP
10
11
12 # Property stream format:
13 #
14 #   line separator is \r?\n.  1 line per a property.
15 #   line which begins with '#' is a comment line.  empty line is ignored, too.
16 #   key/value separator is ':' or '='.
17 #   '\' as escape character.  but line separator cannot be escaped.
18 #   \s at the head/tail of key/value are trimmed.
19 #
20 #   '[' + key + ']' indicates property section.  for example,
21 #
22 #     [aaa.bbb]
23 #     ccc = ddd
24 #     eee.fff = ggg
25 #     []
26 #     aaa.hhh = iii
27 #
28 #   is the same as;
29 #
30 #     aaa.bbb.ccc = ddd
31 #     aaa.bbb.eee.fff = ggg
32 #     aaa.hhh = iii
33 #
34 class Property
35   FrozenError = (RUBY_VERSION >= "1.9.0") ? RuntimeError : TypeError
36
37   include Enumerable
38
39   module Util
40     def const_from_name(fqname)
41       fqname.split("::").inject(Kernel) { |klass, name| klass.const_get(name) }
42     end
43     module_function :const_from_name
44
45     def require_from_name(fqname)
46       require File.join(fqname.split("::").collect { |ele| ele.downcase })
47     end
48     module_function :require_from_name
49   end
50
51   def self.load(stream)
52     new.load(stream)
53   end
54
55   def self.loadproperty(propname)
56     new.loadproperty(propname)
57   end
58
59   def initialize
60     @store = Hash.new
61     @hook = Hash.new
62     @self_hook = Array.new
63     @locked = false
64   end
65
66   KEY_REGSRC = '([^=:\\\\]*(?:\\\\.[^=:\\\\]*)*)'
67   DEF_REGSRC = '\\s*' + KEY_REGSRC + '\\s*[=:]\\s*(.*)'
68   COMMENT_REGEXP = Regexp.new('^(?:#.*|)$', nil, 'u')
69   CATDEF_REGEXP = Regexp.new("^\\[\\s*#{KEY_REGSRC}\\s*\\]$", nil, 'u')
70   LINE_REGEXP = Regexp.new("^#{DEF_REGSRC}$", nil, 'u')
71   def load(stream)
72     key_prefix = ""
73     stream.each_with_index do |line, lineno|
74       line.sub!(/\r?\n\z/u, '')
75       case line
76       when COMMENT_REGEXP
77         next
78       when CATDEF_REGEXP
79         key_prefix = $1.strip
80       when LINE_REGEXP
81         key, value = $1.strip, $2.strip
82         key = "#{key_prefix}.#{key}" unless key_prefix.empty?
83         key, value = loadstr(key), loadstr(value)
84         self[key] = value
85       else
86         raise TypeError.new(
87           "property format error at line #{lineno + 1}: `#{line}'")
88       end
89     end
90     self
91   end
92
93   # find property from $:.
94   def loadproperty(propname)
95     return loadpropertyfile(propname) if File.file?(propname)
96     $:.each do |path|
97       if File.file?(file = File.join(path, propname))
98         return loadpropertyfile(file)
99       end
100     end
101     nil
102   end
103
104   # name: a Symbol, String or an Array
105   def [](name)
106     referent(name_to_a(name))
107   end
108
109   # name: a Symbol, String or an Array
110   # value: an Object
111   def []=(name, value)
112     name_pair = name_to_a(name).freeze
113     hooks = assign(name_pair, value)
114     hooks.each do |hook|
115       hook.call(name_pair, value)
116     end
117     value
118   end
119
120   # value: an Object
121   # key is generated by property
122   def <<(value)
123     self[generate_new_key] = value
124   end
125
126   # name: a Symbol, String or an Array; nil means hook to the root
127   # cascade: true/false; for cascading hook of sub key
128   # hook: block which will be called with 2 args, name and value
129   def add_hook(name = nil, cascade = false, &hook)
130     if name == nil or name == true or name == false
131       cascade = name
132       assign_self_hook(cascade, &hook)
133     else
134       assign_hook(name_to_a(name), cascade, &hook)
135     end
136   end
137
138   def each
139     @store.each do |key, value|
140       yield(key, value)
141     end
142   end
143
144   def empty?
145     @store.empty?
146   end
147
148   def keys
149     @store.keys
150   end
151
152   def values
153     @store.values
154   end
155
156   def lock(cascade = false)
157     if cascade
158       each_key do |key|
159         key.lock(cascade)
160       end
161     end
162     @locked = true
163     self
164   end
165
166   def unlock(cascade = false)
167     @locked = false
168     if cascade
169       each_key do |key|
170         key.unlock(cascade)
171       end
172     end
173     self
174   end
175
176   def locked?
177     @locked
178   end
179
180 protected
181
182   def deref_key(key)
183     check_lock(key)
184     ref = @store[key] ||= self.class.new
185     unless propkey?(ref)
186       raise ArgumentError.new("key `#{key}' already defined as a value")
187     end
188     ref
189   end
190
191   def local_referent(key)
192     check_lock(key)
193     if propkey?(@store[key]) and @store[key].locked?
194       raise FrozenError.new("cannot split any key from locked property")
195     end
196     @store[key]
197   end
198
199   def local_assign(key, value)
200     check_lock(key)
201     if @locked
202       if propkey?(value)
203         raise FrozenError.new("cannot add any key to locked property")
204       elsif propkey?(@store[key])
205         raise FrozenError.new("cannot override any key in locked property")
206       end
207     end
208     @store[key] = value
209   end
210
211   def local_hook(key, direct)
212     hooks = []
213     (@self_hook + (@hook[key] || NO_HOOK)).each do |hook, cascade|
214       hooks << hook if direct or cascade
215     end
216     hooks
217   end
218
219   def local_assign_hook(key, cascade, &hook)
220     check_lock(key)
221     @store[key] ||= nil
222     (@hook[key] ||= []) << [hook, cascade]
223   end
224
225 private
226
227   NO_HOOK = [].freeze
228
229   def referent(ary)
230     ary[0..-2].inject(self) { |ref, name|
231       ref.deref_key(to_key(name))
232     }.local_referent(to_key(ary.last))
233   end
234
235   def assign(ary, value)
236     ref = self
237     hook = NO_HOOK
238     ary[0..-2].each do |name|
239       key = to_key(name)
240       hook += ref.local_hook(key, false)
241       ref = ref.deref_key(key)
242     end
243     last_key = to_key(ary.last)
244     ref.local_assign(last_key, value)
245     hook + ref.local_hook(last_key, true)
246   end
247
248   def assign_hook(ary, cascade, &hook)
249     ary[0..-2].inject(self) { |ref, name|
250       ref.deref_key(to_key(name))
251     }.local_assign_hook(to_key(ary.last), cascade, &hook)
252   end
253
254   def assign_self_hook(cascade, &hook)
255     check_lock(nil)
256     @self_hook << [hook, cascade]
257   end
258
259   def each_key
260     self.each do |key, value|
261       if propkey?(value)
262         yield(value)
263       end
264     end
265   end
266
267   def check_lock(key)
268     if @locked and (key.nil? or !@store.key?(key))
269       raise FrozenError.new("cannot add any key to locked property")
270     end
271   end
272
273   def propkey?(value)
274     value.is_a?(::SOAP::Property)
275   end
276
277   def name_to_a(name)
278     case name
279     when Symbol
280       [name]
281     when String
282       name.scan(/[^.\\]+(?:\\.[^.\\])*/u)       # split with unescaped '.'
283     when Array
284       name
285     else
286       raise ArgumentError.new("Unknown name #{name}(#{name.class})")
287     end
288   end
289
290   def to_key(name)
291     name.to_s.downcase
292   end
293
294   def generate_new_key
295     if @store.empty?
296       "0"
297     else
298       (key_max + 1).to_s
299     end
300   end
301
302   def key_max
303     (@store.keys.max { |l, r| l.to_s.to_i <=> r.to_s.to_i }).to_s.to_i
304   end
305
306   def loadpropertyfile(file)
307     puts "find property at #{file}" if $DEBUG
308     File.open(file) do |f|
309       load(f)
310     end
311   end
312
313   def loadstr(str)
314     str.gsub(/\\./u) { |c| eval("\"#{c}\"") }
315   end
316 end
317
318
319 end
320
321
322 # for ruby/1.6.
323 unless Enumerable.instance_methods.include?('inject')
324   module Enumerable
325     def inject(init)
326       result = init
327       each do |item|
328         result = yield(result, item)
329       end
330       result
331     end
332   end
333 end
Note: See TracBrowser for help on using the browser.