From c1e9ce9ca6d888cc18d945c916db4f4acd685d6b Mon Sep 17 00:00:00 2001 From: nahi Date: Fri, 19 Dec 2003 13:59:27 +0000 Subject: [PATCH] * lib/soap/property.rb (SOAP::Property#load): new method for loading property value into existing property tree. * test/soap/test_property.rb: add test. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@5220 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 7 ++ lib/soap/property.rb | 179 ++++++++++++++++++++++--------------- test/soap/test_property.rb | 38 ++++++++ 3 files changed, 154 insertions(+), 70 deletions(-) diff --git a/ChangeLog b/ChangeLog index ca0241bad9..be46652dda 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Fri Dec 19 22:56:46 2003 NAKAMURA, Hiroshi + + * lib/soap/property.rb (SOAP::Property#load): new method for loading + property value into existing property tree. + + * test/soap/test_property.rb: add test. + Fri Dec 19 19:21:49 2003 akira yamada * lib/runit/cui/testrunner.rb (RUNIT::CUI::TestRunner::run): diff --git a/lib/soap/property.rb b/lib/soap/property.rb index d2bae88825..e0516aa419 100644 --- a/lib/soap/property.rb +++ b/lib/soap/property.rb @@ -9,29 +9,34 @@ module SOAP +# Property stream format: +# +# line separator is \r?\n. 1 line per a property. +# line which begins with '#' is a comment line. empty line is ignored, too. +# key/value separator is ':' or '='. +# '\' as escape character. but line separator cannot be escaped. +# \s at the head/tail of key/value are trimmed. +# +# '[' + key + ']' indicates property section. for example, +# +# [aaa.bbb] +# ccc = ddd +# eee.fff = ggg +# [] +# aaa.hhh = iii +# +# is the same as; +# +# aaa.bbb.ccc = ddd +# aaa.bbb.eee.fff = ggg +# aaa.hhh = iii +# class Property include Enumerable - # Property file format: - # line separator is \r?\n. 1 line per a property. - # line which begins with '#' is comment line. empty line is ignored. - # key/value separator is ':', '=', or \s. - # '\' as escape character. but line separator cannot be escaped. - # \s at the head/tail of key/value are trimmed. def self.load(stream) prop = new - stream.each_with_index do |line, lineno| - line.sub!(/\r?\n\z/, '') - next if /^(#.*|)$/ =~ line - if /^\s*([^=:\s\\]+(?:\\.[^=:\s\\]*)*)\s*[=:\s]\s*(.*)$/ =~ line - key, value = $1, $2 - key = eval("\"#{key}\"") - value = eval("\"#{value.strip}\"") - prop[key] = value - else - raise TypeError.new("property format error at line #{lineno + 1}: `#{line}'") - end - end + prop.load(stream) prop end @@ -41,6 +46,7 @@ class Property end end + # find property from $:. def self.loadproperty(propname) $:.each do |path| if File.file?(file = File.join(path, propname)) @@ -57,6 +63,34 @@ class Property @locked = false end + KEY_REGSRC = '([^=:\\\\]*(?:\\\\.[^=:\\\\]*)*)' + DEF_REGSRC = '\\s*' + KEY_REGSRC + '\\s*[=:]\\s*(.*)' + COMMENT_REGEXP = Regexp.new('^(?:#.*|)$') + CATDEF_REGEXP = Regexp.new("^\\[\\s*#{KEY_REGSRC}\\s*\\]$") + LINE_REGEXP = Regexp.new("^#{DEF_REGSRC}$") + def load(stream) + key_prefix = "" + stream.each_with_index do |line, lineno| + line.sub!(/\r?\n\z/, '') + case line + when COMMENT_REGEXP + next + when CATDEF_REGEXP + key_prefix = $1.strip + when LINE_REGEXP + key, value = $1.strip, $2.strip + key = "#{key_prefix}.#{key}" unless key_prefix.empty? + key = eval("\"#{key}\"") + value = eval("\"#{value}\"") + self[key] = value + else + raise TypeError.new( + "property format error at line #{lineno + 1}: `#{line}'") + end + end + self + end + # name: a Symbol, String or an Array def [](name) referent(name_to_a(name)) @@ -133,50 +167,6 @@ class Property protected - def referent(ary) - key, rest = location_pair(ary) - if rest.empty? - local_referent(key) - else - deref_key(key).referent(rest) - end - end - - # returns: Array of hook - def assign(ary, value) - key, rest = location_pair(ary) - if rest.empty? - local_assign(key, value) - local_hook(key) - else - local_hook(key) + deref_key(key).assign(rest, value) - end - end - - def assign_hook(ary, hook) - key, rest = location_pair(ary) - if rest.empty? - local_assign_hook(key, hook) - else - deref_key(key).assign_hook(rest, hook) - end - end - - def assign_self_hook(hook) - check_lock(nil) - @self_hook << hook - end - -private - - def each_key - self.each do |key, value| - if propkey?(value) - yield(value) - end - end - end - def deref_key(key) check_lock(key) ref = @store[key] ||= self.class.new @@ -206,15 +196,56 @@ private @store[key] = value end + def local_hook(key) + @self_hook + (@hook[key] || NO_HOOK) + end + def local_assign_hook(key, hook) check_lock(key) @store[key] ||= nil (@hook[key] ||= []) << hook end +private + NO_HOOK = [].freeze - def local_hook(key) - @self_hook + (@hook[key] || NO_HOOK) + + def referent(ary) + ary[0..-2].inject(self) { |ref, name| + ref.deref_key(to_key(name)) + }.local_referent(to_key(ary.last)) + end + + def assign(ary, value) + ref = self + hook = NO_HOOK + ary[0..-2].each do |name| + key = to_key(name) + hook += ref.local_hook(key) + ref = ref.deref_key(key) + end + last_key = to_key(ary.last) + ref.local_assign(last_key, value) + hook + ref.local_hook(last_key) + end + + def assign_hook(ary, hook) + ary[0..-2].inject(self) { |ref, name| + ref.deref_key(to_key(name)) + }.local_assign_hook(to_key(ary.last), hook) + end + + def assign_self_hook(hook) + check_lock(nil) + @self_hook << hook + end + + def each_key + self.each do |key, value| + if propkey?(value) + yield(value) + end + end end def check_lock(key) @@ -240,12 +271,6 @@ private end end - def location_pair(ary) - name, *rest = *ary - key = to_key(name) - return key, rest - end - def normalize_name(name) name_to_a(name).collect { |key| to_key(key) }.join('.') end @@ -269,3 +294,17 @@ end end + + +# for ruby/1.6. +unless Enumerable.instance_methods.include?('inject') + module Enumerable + def inject(init) + result = init + each do |item| + result = yield(result, item) + end + result + end + end +end diff --git a/test/soap/test_property.rb b/test/soap/test_property.rb index d7d6c2edf7..f883d0c2d5 100644 --- a/test/soap/test_property.rb +++ b/test/soap/test_property.rb @@ -32,6 +32,14 @@ client.protocol.http.protocol_version = 1.0 foo\\:bar\\=baz = qux foo\\\\.bar.baz=\tq\\\\ux\ttab a\\ b = 1 +[ppp.qqq.rrr] +sss = 3 +ttt.uuu = 4 + +[ sss.ttt.uuu ] +vvv.www = 5 +[ ] +xxx.yyy.zzz = 6 __EOP__ prop = Property.load(propstr) assert_equal(["1", "2", "3"], prop["a.b"].values.sort) @@ -41,8 +49,38 @@ __EOP__ assert_equal("1.0", prop["client.protocol.http.protocol_version"]) assert_equal("q\\ux\ttab", prop['foo\.bar.baz']) assert_equal("1", prop['a b']) + assert_equal("3", prop['ppp.qqq.rrr.sss']) + assert_equal("4", prop['ppp.qqq.rrr.ttt.uuu']) + assert_equal("5", prop['sss.ttt.uuu.vvv.www']) + assert_equal("6", prop['xxx.yyy.zzz']) end + def test_load + prop = Property.new + hooked = false + prop.add_hook("foo.bar.baz") do |name, value| + assert_equal("foo.bar.baz", name) + assert_equal("123", value) + hooked = true + end + prop.lock + prop["foo.bar"].lock + prop.load("foo.bar.baz = 123") + assert(hooked) + assert_raises(TypeError) do + prop.load("foo.bar.qux = 123") + end + prop.load("foo.baz = 456") + assert_equal("456", prop["foo.baz"]) + end + + def test_initialize + prop = ::SOAP::Property.new + # store is empty + assert_nil(prop["a"]) + # does hook work? + assert_equal(1, prop["a"] = 1) + end def test_initialize prop = ::SOAP::Property.new # store is empty