diff options
Diffstat (limited to 'ruby/spec.rb')
-rw-r--r-- | ruby/spec.rb | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/ruby/spec.rb b/ruby/spec.rb new file mode 100644 index 0000000000..ad04a4a91f --- /dev/null +++ b/ruby/spec.rb @@ -0,0 +1,291 @@ +# +# Copyright (c) 2006 The Apache Software Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "rexml/document" +require "set" +require "fields" +require "traverse" +require "delegate" + +module Spec + + include REXML + + class Container < Array + + def initialize() + @cache = {} + end + + def [](key) + return @cache[key] if @cache.include?(key) + + case key + when String + value = find {|x| x.name == key.intern()} + when Symbol + value = find {|x| x.name == key} + when Integer + value = find {|x| x.id == key} + else + raise Exception.new("invalid key: #{key}") + end + + @cache[key] = value + return value + end + + end + + class Root + fields(:major, :minor, :classes, :constants, :domains) + + def ruby_method(name) + classes.each do |c| + c.methods.each do |m| + if name == m.ruby_name + return m + end + end + end + end + end + + class Constant + fields(:name, :id, :type, :docs) + end + + class Domain + fields(:name, :type) + end + + class Class + fields(:name, :id, :handler, :fields, :methods, :docs) + end + + class Method + fields(:name, :id, :content?, :responses, :synchronous?, :fields, + :docs) + + def init() + @response = false + end + + attr :parent, true + + def response?; @response end + def response=(b); @response = b end + + def ruby_name + Spec.rubyize(:"#{parent.name}_#{name}") + end + end + + class Field + fields(:name, :id, :type, :docs) + + def ruby_name + Spec.rubyize(name) + end + + def default + case type + when :bit then false + when :octet, :short, :long, :longlong then 0 + when :shortstr, :longstr then "" + when :table then {} + end + end + + end + + class Doc + fields(:type, :text) + end + + class Reference + + fields(:name) + + def init(&block) + @resolver = block + end + + def resolve(spec, klass) + @resolver.call(spec, klass) + end + + end + + class Loader + + def initialize() + @stack = [] + end + + def load(obj) + case obj + when String + elem = @stack[-1] + result = Container.new() + elem.elements.each(obj) {|e| + @index = result.size + result << load(e) + } + @index = nil + return result + else + elem = obj + @stack << elem + begin + result = send(:"load_#{elem.name}") + ensure + @stack.pop() + end + return result + end + end + + def element + @stack[-1] + end + + def text + element.text + end + + def attr(name, type = :string, default = nil) + value = element.attributes[name] + value = value.strip() unless value.nil? + value = nil unless value.nil? or value.any? + if value.nil? and not default.nil? then + default + else + send(:"parse_#{type}", value) + end + end + + def parse_int(value) + value.to_i + end + + TRUE = ["yes", "true", "1"].to_set + FALSE = ["no", "false", "0", nil].to_set + + def parse_bool(value) + if TRUE.include?(value) + true + elsif FALSE.include?(value) + false + else + raise Exception.new("parse error, expecting boolean: #{value}") + end + end + + def parse_string(value) + value.to_s + end + + def parse_symbol(value) + value.intern() unless value.nil? + end + + def load_amqp() + Root.new(attr("major", :int), attr("minor", :int), load("class"), + load("constant"), load("domain")) + end + + def load_class() + Class.new(attr("name", :symbol), attr("index", :int), attr("handler", :symbol), + load("field"), load("method"), load("doc")) + end + + def load_method() + Method.new(attr("name", :symbol), attr("index", :int), + attr("content", :bool), load("response"), + attr("synchronous", :bool), load("field"), load("docs")) + end + + def load_response() + name = attr("name", :symbol) + Reference.new {|spec, klass| + response = klass.methods[name] + if response.nil? + raise Exception.new("no such method: #{name}") + end + response + } + end + + def load_field() + type = attr("type", :symbol) + if type.nil? + domain = attr("domain", :symbol) + type = Reference.new {|spec, klass| + spec.domains[domain].type + } + end + Field.new(attr("name", :symbol), @index, type, load("docs")) + end + + def load_constant() + Constant.new(attr("name", :symbol), attr("value", :int), attr("class", :symbol), + load("doc")) + end + + def load_domain() + Domain.new(attr("name", :symbol), attr("type", :symbol)) + end + + def load_doc() + Doc.new(attr("type", :symbol), text) + end + + end + + def Spec.load(spec) + case spec + when String + spec = File.new(spec) + end + doc = Document.new(spec) + spec = Loader.new().load(doc.root) + spec.classes.each do |klass| + klass.traverse! do |o| + case o + when Reference + o.resolve(spec, klass) + else + o + end + end + klass.methods.each do |m| + m.parent = klass + m.responses.each do |r| + r.response = true + end + end + end + spec + end + + private + + def Spec.rubyize(name) + name.to_s.gsub(/[\s-]/, '_').intern() + end + +end |