| 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 |
lineno = 0 |
|---|
| 74 |
stream.each_line do |line| |
|---|
| 75 |
line.sub!(/\r?\n\z/u, '') |
|---|
| 76 |
lineno += 1 |
|---|
| 77 |
case line |
|---|
| 78 |
when COMMENT_REGEXP |
|---|
| 79 |
next |
|---|
| 80 |
when CATDEF_REGEXP |
|---|
| 81 |
key_prefix = $1.strip |
|---|
| 82 |
when LINE_REGEXP |
|---|
| 83 |
key, value = $1.strip, $2.strip |
|---|
| 84 |
key = "#{key_prefix}.#{key}" unless key_prefix.empty? |
|---|
| 85 |
key, value = loadstr(key), loadstr(value) |
|---|
| 86 |
self[key] = value |
|---|
| 87 |
else |
|---|
| 88 |
raise TypeError.new( |
|---|
| 89 |
"property format error at line #{lineno}: `#{line}'") |
|---|
| 90 |
end |
|---|
| 91 |
end |
|---|
| 92 |
self |
|---|
| 93 |
end |
|---|
| 94 |
|
|---|
| 95 |
# find property from $:. |
|---|
| 96 |
def loadproperty(propname) |
|---|
| 97 |
return loadpropertyfile(propname) if File.file?(propname) |
|---|
| 98 |
$:.each do |path| |
|---|
| 99 |
if File.file?(file = File.join(path, propname)) |
|---|
| 100 |
return loadpropertyfile(file) |
|---|
| 101 |
end |
|---|
| 102 |
end |
|---|
| 103 |
nil |
|---|
| 104 |
end |
|---|
| 105 |
|
|---|
| 106 |
# name: a Symbol, String or an Array |
|---|
| 107 |
def [](name) |
|---|
| 108 |
referent(name_to_a(name)) |
|---|
| 109 |
end |
|---|
| 110 |
|
|---|
| 111 |
# name: a Symbol, String or an Array |
|---|
| 112 |
# value: an Object |
|---|
| 113 |
def []=(name, value) |
|---|
| 114 |
name_pair = name_to_a(name).freeze |
|---|
| 115 |
hooks = assign(name_pair, value) |
|---|
| 116 |
hooks.each do |hook| |
|---|
| 117 |
hook.call(name_pair, value) |
|---|
| 118 |
end |
|---|
| 119 |
value |
|---|
| 120 |
end |
|---|
| 121 |
|
|---|
| 122 |
# value: an Object |
|---|
| 123 |
# key is generated by property |
|---|
| 124 |
def <<(value) |
|---|
| 125 |
self[generate_new_key] = value |
|---|
| 126 |
end |
|---|
| 127 |
|
|---|
| 128 |
# name: a Symbol, String or an Array; nil means hook to the root |
|---|
| 129 |
# cascade: true/false; for cascading hook of sub key |
|---|
| 130 |
# hook: block which will be called with 2 args, name and value |
|---|
| 131 |
def add_hook(name = nil, cascade = false, &hook) |
|---|
| 132 |
if name == nil or name == true or name == false |
|---|
| 133 |
cascade = name |
|---|
| 134 |
assign_self_hook(cascade, &hook) |
|---|
| 135 |
else |
|---|
| 136 |
assign_hook(name_to_a(name), cascade, &hook) |
|---|
| 137 |
end |
|---|
| 138 |
end |
|---|
| 139 |
|
|---|
| 140 |
def each |
|---|
| 141 |
@store.each do |key, value| |
|---|
| 142 |
yield(key, value) |
|---|
| 143 |
end |
|---|
| 144 |
end |
|---|
| 145 |
|
|---|
| 146 |
def empty? |
|---|
| 147 |
@store.empty? |
|---|
| 148 |
end |
|---|
| 149 |
|
|---|
| 150 |
def keys |
|---|
| 151 |
@store.keys |
|---|
| 152 |
end |
|---|
| 153 |
|
|---|
| 154 |
def values |
|---|
| 155 |
@store.values |
|---|
| 156 |
end |
|---|
| 157 |
|
|---|
| 158 |
def lock(cascade = false) |
|---|
| 159 |
if cascade |
|---|
| 160 |
each_key do |key| |
|---|
| 161 |
key.lock(cascade) |
|---|
| 162 |
end |
|---|
| 163 |
end |
|---|
| 164 |
@locked = true |
|---|
| 165 |
self |
|---|
| 166 |
end |
|---|
| 167 |
|
|---|
| 168 |
def unlock(cascade = false) |
|---|
| 169 |
@locked = false |
|---|
| 170 |
if cascade |
|---|
| 171 |
each_key do |key| |
|---|
| 172 |
key.unlock(cascade) |
|---|
| 173 |
end |
|---|
| 174 |
end |
|---|
| 175 |
self |
|---|
| 176 |
end |
|---|
| 177 |
|
|---|
| 178 |
def locked? |
|---|
| 179 |
@locked |
|---|
| 180 |
end |
|---|
| 181 |
|
|---|
| 182 |
protected |
|---|
| 183 |
|
|---|
| 184 |
def deref_key(key) |
|---|
| 185 |
check_lock(key) |
|---|
| 186 |
ref = @store[key] ||= self.class.new |
|---|
| 187 |
unless propkey?(ref) |
|---|
| 188 |
raise ArgumentError.new("key `#{key}' already defined as a value") |
|---|
| 189 |
end |
|---|
| 190 |
ref |
|---|
| 191 |
end |
|---|
| 192 |
|
|---|
| 193 |
def local_referent(key) |
|---|
| 194 |
check_lock(key) |
|---|
| 195 |
if propkey?(@store[key]) and @store[key].locked? |
|---|
| 196 |
raise FrozenError.new("cannot split any key from locked property") |
|---|
| 197 |
end |
|---|
| 198 |
@store[key] |
|---|
| 199 |
end |
|---|
| 200 |
|
|---|
| 201 |
def local_assign(key, value) |
|---|
| 202 |
check_lock(key) |
|---|
| 203 |
if @locked |
|---|
| 204 |
if propkey?(value) |
|---|
| 205 |
raise FrozenError.new("cannot add any key to locked property") |
|---|
| 206 |
elsif propkey?(@store[key]) |
|---|
| 207 |
raise FrozenError.new("cannot override any key in locked property") |
|---|
| 208 |
end |
|---|
| 209 |
end |
|---|
| 210 |
@store[key] = value |
|---|
| 211 |
end |
|---|
| 212 |
|
|---|
| 213 |
def local_hook(key, direct) |
|---|
| 214 |
hooks = [] |
|---|
| 215 |
(@self_hook + (@hook[key] || NO_HOOK)).each do |hook, cascade| |
|---|
| 216 |
hooks << hook if direct or cascade |
|---|
| 217 |
end |
|---|
| 218 |
hooks |
|---|
| 219 |
end |
|---|
| 220 |
|
|---|
| 221 |
def local_assign_hook(key, cascade, &hook) |
|---|
| 222 |
check_lock(key) |
|---|
| 223 |
@store[key] ||= nil |
|---|
| 224 |
(@hook[key] ||= []) << [hook, cascade] |
|---|
| 225 |
end |
|---|
| 226 |
|
|---|
| 227 |
private |
|---|
| 228 |
|
|---|
| 229 |
NO_HOOK = [].freeze |
|---|
| 230 |
|
|---|
| 231 |
def referent(ary) |
|---|
| 232 |
ary[0..-2].inject(self) { |ref, name| |
|---|
| 233 |
ref.deref_key(to_key(name)) |
|---|
| 234 |
}.local_referent(to_key(ary.last)) |
|---|
| 235 |
end |
|---|
| 236 |
|
|---|
| 237 |
def assign(ary, value) |
|---|
| 238 |
ref = self |
|---|
| 239 |
hook = NO_HOOK |
|---|
| 240 |
ary[0..-2].each do |name| |
|---|
| 241 |
key = to_key(name) |
|---|
| 242 |
hook += ref.local_hook(key, false) |
|---|
| 243 |
ref = ref.deref_key(key) |
|---|
| 244 |
end |
|---|
| 245 |
last_key = to_key(ary.last) |
|---|
| 246 |
ref.local_assign(last_key, value) |
|---|
| 247 |
hook + ref.local_hook(last_key, true) |
|---|
| 248 |
end |
|---|
| 249 |
|
|---|
| 250 |
def assign_hook(ary, cascade, &hook) |
|---|
| 251 |
ary[0..-2].inject(self) { |ref, name| |
|---|
| 252 |
ref.deref_key(to_key(name)) |
|---|
| 253 |
}.local_assign_hook(to_key(ary.last), cascade, &hook) |
|---|
| 254 |
end |
|---|
| 255 |
|
|---|
| 256 |
def assign_self_hook(cascade, &hook) |
|---|
| 257 |
check_lock(nil) |
|---|
| 258 |
@self_hook << [hook, cascade] |
|---|
| 259 |
end |
|---|
| 260 |
|
|---|
| 261 |
def each_key |
|---|
| 262 |
self.each do |key, value| |
|---|
| 263 |
if propkey?(value) |
|---|
| 264 |
yield(value) |
|---|
| 265 |
end |
|---|
| 266 |
end |
|---|
| 267 |
end |
|---|
| 268 |
|
|---|
| 269 |
def check_lock(key) |
|---|
| 270 |
if @locked and (key.nil? or !@store.key?(key)) |
|---|
| 271 |
raise FrozenError.new("cannot add any key to locked property") |
|---|
| 272 |
end |
|---|
| 273 |
end |
|---|
| 274 |
|
|---|
| 275 |
def propkey?(value) |
|---|
| 276 |
value.is_a?(::SOAP::Property) |
|---|
| 277 |
end |
|---|
| 278 |
|
|---|
| 279 |
def name_to_a(name) |
|---|
| 280 |
case name |
|---|
| 281 |
when Symbol |
|---|
| 282 |
[name] |
|---|
| 283 |
when String |
|---|
| 284 |
name.scan(/[^.\\]+(?:\\.[^.\\])*/u) # split with unescaped '.' |
|---|
| 285 |
when Array |
|---|
| 286 |
name |
|---|
| 287 |
else |
|---|
| 288 |
raise ArgumentError.new("Unknown name #{name}(#{name.class})") |
|---|
| 289 |
end |
|---|
| 290 |
end |
|---|
| 291 |
|
|---|
| 292 |
def to_key(name) |
|---|
| 293 |
name.to_s.downcase |
|---|
| 294 |
end |
|---|
| 295 |
|
|---|
| 296 |
def generate_new_key |
|---|
| 297 |
if @store.empty? |
|---|
| 298 |
"0" |
|---|
| 299 |
else |
|---|
| 300 |
(key_max + 1).to_s |
|---|
| 301 |
end |
|---|
| 302 |
end |
|---|
| 303 |
|
|---|
| 304 |
def key_max |
|---|
| 305 |
(@store.keys.max { |l, r| l.to_s.to_i <=> r.to_s.to_i }).to_s.to_i |
|---|
| 306 |
end |
|---|
| 307 |
|
|---|
| 308 |
def loadpropertyfile(file) |
|---|
| 309 |
puts "find property at #{file}" if $DEBUG |
|---|
| 310 |
File.open(file) do |f| |
|---|
| 311 |
load(f) |
|---|
| 312 |
end |
|---|
| 313 |
end |
|---|
| 314 |
|
|---|
| 315 |
def loadstr(str) |
|---|
| 316 |
str.gsub(/\\./u) { |c| eval("\"#{c}\"") } |
|---|
| 317 |
end |
|---|
| 318 |
end |
|---|
| 319 |
|
|---|
| 320 |
|
|---|
| 321 |
end |
|---|
| 322 |
|
|---|
| 323 |
|
|---|
| 324 |
# for ruby/1.6. |
|---|
| 325 |
unless Enumerable.instance_methods.include?('inject') |
|---|
| 326 |
module Enumerable |
|---|
| 327 |
def inject(init) |
|---|
| 328 |
result = init |
|---|
| 329 |
each do |item| |
|---|
| 330 |
result = yield(result, item) |
|---|
| 331 |
end |
|---|
| 332 |
result |
|---|
| 333 |
end |
|---|
| 334 |
end |
|---|
| 335 |
end |
|---|