diff options
author | Alan Conway <aconway@apache.org> | 2007-07-31 20:00:10 +0000 |
---|---|---|
committer | Alan Conway <aconway@apache.org> | 2007-07-31 20:00:10 +0000 |
commit | c11f9a79eec63da7aa6e6dac248a689a9d461beb (patch) | |
tree | 7c89f9804904dd3b99f3b3e564d14278ee3a7044 /cpp/rubygen/amqpgen.rb | |
parent | 82d128b33fac55b8618d593c89283356ee4e192b (diff) | |
download | qpid-python-c11f9a79eec63da7aa6e6dac248a689a9d461beb.tar.gz |
Ruby code generator for C++.
Not yet in active use yet but two sample templates are provided.
Note: same dependency story as java codegen: distribution has pre-generated
code so ruby not required to build a distro. Only required for svn working
copy builds.
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@561468 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/rubygen/amqpgen.rb')
-rwxr-xr-x | cpp/rubygen/amqpgen.rb | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/cpp/rubygen/amqpgen.rb b/cpp/rubygen/amqpgen.rb new file mode 100755 index 0000000000..3fc66525d8 --- /dev/null +++ b/cpp/rubygen/amqpgen.rb @@ -0,0 +1,208 @@ +# +# Generic AMQP code generation library. +# + +require 'delegate' +require 'rexml/document' +require 'pathname' +include REXML + +# Handy String functions for converting names. +class String + # Convert to CapitalizedForm. + def caps() gsub( /(^|\W)(\w)/ ) { |m| $2.upcase } end + + # Convert to underbar_separated_form. + def bars() tr('- .','_'); end + + # Convert to lowerCaseCapitalizedForm + def lcaps() gsub( /\W(\w)/ ) { |m| $1.upcase } end +end + +# Sort an array by name. +class Array + def sort_by_name() + sort() { |a,b| a.name <=> b.name } + end +end + +# Add collect to Elements +class Elements + def collect(xpath, &block) + result=[] + each(xpath) { |el| result << yield(el) } + result + end +end + +# An AmqpElement extends (delegates to) a REXML::Element +# +# NB: AmqpElements cache various values, they assume that +# the XML model does not change after the AmqpElement has +# been created. +# +class AmqpElement < DelegateClass(Element) + def initialize(xml, amqp) super(xml); @amqp_parent=amqp; end + + attr_reader :amqp_parent + + # Return the name attribute, not the element name. + def name() attributes["name"]; end + + def amqp_root() + amqp_parent ? amqp_parent.amqp_root : self; + end +end + +# AMQP field element +class AmqpField < AmqpElement + def initialize(xml, amqp) super; end; + + # Get AMQP type for a domain name. + def domain_type(name) + domain=elements["/amqp/domain[@name='#{name}']"] + (domain and domain.attributes["type"] or name) + end + + # Get the AMQP type of this field. + def field_type() + d=attributes["domain"] + dt=domain_type d if d + (dt or attributes["type"]) + end +end + +# AMQP method element +class AmqpMethod < AmqpElement + def initialize(xml, amqp) super; end + + def fields() + @cache_fields ||= elements.collect("field") { |f| AmqpField.new(f,self); } + end + + # Responses to this method (0-9) + def responses() + @cache_responses ||= elements.collect("response") { |el| new AmqpMethod(el,self) } + end + + # Methods this method responds to (0-9) + def responds_to() + @cache_responds_to ||= elements.collect("../method/response[@name='#{attributes['name']}']") { |el| + AmqpMethod.new(el.parent, amqp_parent) + } + end + + def request?() responds_to().empty?; end + def response?() not request?; end +end + +# AMQP class element. +class AmqpClass < AmqpElement + def initialize(xml,amqp) super; end + + def methods() + @cache_methods ||= elements.collect("method") { |el| + AmqpMethod.new(el,self) + }.sort_by_name + end + + # chassis should be "client" or "server" + def methods_on(chassis) + elements.collect("method/chassis[@name='#{chassis}']/..") { |m| + AmqpMethod.new(m,self) + }.sort_by_name + end +end + +# AMQP root element. +class AmqpRoot < AmqpElement + + # Initialize with output directory and spec files from ARGV. + def initialize(*specs) + specs.size or raise "No XML spec files." + specs.each { |f| File.exists?(f) or raise "Invalid XML file: #{f}"} + super Document.new(File.new(specs.shift)).root, nil + specs.each { |s| # Merge in additional specs + root=Document.new(File.new(s)).root + merge(self,root) + } + end + + def version() + attributes["major"]+"-"+attributes["minor"] + end + + def classes() + @cache_classes ||= elements.collect("class") { |c| AmqpClass.new(c,self) }.sort_by_name + end + + # Return all methods on chassis for all classes. + def methods_on(chassis) + classes.collect { |c| + c.methods_on(chassis) + }.flatten + end + + # Merge contents of elements. + def merge(to,from) + from.elements.each { |from_child| + tag,name = from_child.name, from_child.attributes["name"] + to_child=to.elements["./#{tag}[@name='#{name}']"] + to_child ? merge(to_child, from_child) : to.add(from_child.clone) } + end + + private :merge + +end + +# Base class for code generators. +# Supports setting a per-line prefix, useful for e.g. indenting code. +# +class Generator + # Takes directory for output or "-", meaning print file names that + # would be generated. + def initialize (outdir, amqp) + @amqp=amqp + @outdir=outdir + @prefix='' # For indentation or comments. + @indentstr=' ' # One indent level. + raise "Invalid output directory: #{outdir}" unless @outdir=="-" or File.directory?(@outdir) + end + + # Create a new file, set @out. + def file(file) + puts file + if (@outdir != "-") + path=Pathname.new "#{@outdir}/#{file}" + path.parent.mkpath + path.open('w') { |@out| yield } + end + end + + # Append multi-line string to generated code, prefixing each line. + def gen (str) + str.each_line { |line| + @out << @prefix unless @midline + @out << line + @midline = nil + } + # Note if we stopped mid-line + @midline = /[^\n]\z/ === str + end + + # Generate code with added prefix. + def prefix(add) + save=@prefix + @prefix+=add + yield + @prefix=save + end + + # Generate indented code + def indent(n=1,&block) prefix(@indentstr * n,&block); end + + attr_accessor :out +end + + + |