# # 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 ALL_UPPERCASE_FORM def shout() bars.upcase!; end # Convert to lowerCaseCapitalizedForm def lcaps() gsub( /\W(\w)/ ) { |m| $1.upcase } end def plural() self + (/[xs]$/ === self ? 'es' : 's'); end end # Sort an array by name. module Enumerable def sort_by_name() sort { |a,b| a.name <=> b.name }; end end # Add functions similar to attr_reader for AMQP attributes/children. # Symbols that are ruby Object function names (e.g. class) get # an "_" suffix. class Module # Add trailing _ to avoid conflict with Object methods. def mangle(sym) (Object.method_defined? sym) ? (sym.to_s+"_").intern : sym end # Add attribute reader for XML attribute. def amqp_attr_reader(*attrs) attrs.each { |a| case a when Symbol define_method(mangle(a)) { @amqp_attr_reader||={ } @amqp_attr_reader[a] ||= xml.attributes[a.to_s] } when Hash a.each { |attr, default| define_method(mangle(attr)) { @amqp_attr_reader||={ } value = xml.attributes[attr.to_s] if value @amqp_attr_reader[attr] ||= value else @amqp_attr_reader[attr] ||= default end } } end } end # Add 2 child readers: # elname(name) == child('elname',name) # elnames() == children('elname') def amqp_child_reader(*element_names) element_names.each { |e| define_method(mangle(e)) { |name| child(e.to_s, name) } define_method(mangle(e.to_s.plural)) { children(e.to_s) } } end # When there can only be one child instance def amqp_single_child_reader(*element_names) element_names.each { |e| define_method(mangle(e)) { children(e.to_s)[0] } } end end # An AmqpElement contains an XML element and provides a convenient # API to access AMQP data. # # NB: AmqpElements cache values from XML, they assume that # the XML model does not change after the AmqpElement has # been created. class AmqpElement def wrap(xml) return nil if ["assert","rule"].include? xml.name eval("Amqp"+xml.name.caps).new(xml, self) or raise "nil wrapper" end public def initialize(xml, parent) @xml, @parent=xml, parent @children=xml.elements.map { |e| wrap e }.compact @cache_child={} @cache_children={} @cache_children[nil]=@children end attr_reader :parent, :xml, :children, :doc amqp_attr_reader :name, :label # List of children of type elname, or all children if elname # not specified. def children(elname=nil) @cache_children[elname] ||= @children.select { |c| elname==c.xml.name } end # Look up child of type elname with attribute name. def child(elname, name) @cache_child[[elname,name]] ||= children(elname).find { |c| c.name==name } end # The root element. def root() @root ||=parent ? parent.root : self; end def to_s() "#<#{self.class}(#{name})>"; end def inspect() to_s; end # Text of doc child if there is one. def doc() d=xml.elements["doc"]; d and d.text; end end AmqpResponse = AmqpElement class AmqpDoc < AmqpElement def initialize(xml,parent) super; end def text() @xml.text end end class AmqpDomain < AmqpElement def initialize(xml, parent) super; end amqp_attr_reader :type amqp_single_child_reader :struct def unalias() d=self while (d.type_ != d.name and root.domain(d.type_)) d=root.domain(d.type_) end return d end end class AmqpField < AmqpElement def initialize(xml, amqp) super; end; def domain() root.domain(xml.attributes["domain"]); end amqp_single_child_reader :struct end class AmqpChassis < AmqpElement def initialize(xml, parent) super; end amqp_attr_reader :implement end class AmqpConstant < AmqpElement def initialize(xml, parent) super; end amqp_attr_reader :value, :class end class AmqpResult < AmqpElement def initialize(xml, parent) super; end amqp_single_child_reader :struct end class AmqpStruct < AmqpElement def initialize(xml, parent) super; end amqp_attr_reader :size, :type, :pack => "short" amqp_child_reader :field def result?() parent.xml.name == "result"; end def domain?() parent.xml.name == "domain"; end end class AmqpMethod < AmqpElement def initialize(xml, parent) super; end amqp_attr_reader :content, :index, :synchronous amqp_child_reader :field, :chassis,:response amqp_single_child_reader :result def on_chassis?(chassis) child("chassis", chassis); end def on_client?() on_chassis? "client"; end def on_server?() on_chassis? "server"; end end class AmqpClass < AmqpElement def initialize(xml,amqp) super; end amqp_attr_reader :index amqp_child_reader :method # chassis should be "client" or "server" def methods_on(chassis) @methods_on ||= { } @methods_on[chassis] ||= methods_.select { |m| m.on_chassis? chassis } end def l4?() !["connection", "session", "execution"].include?(name) end end # AMQP root element. class AmqpRoot < AmqpElement def parse(filename) Document.new(File.new(filename)).root; end # Initialize with output directory and spec files from ARGV. def initialize(*specs) raise "No XML spec files." if specs.empty? xml=parse(specs.shift) specs.each { |s| xml_merge(xml, parse(s)) } super(xml, nil) end amqp_attr_reader :major, :minor amqp_child_reader :class, :domain, :constant def version() major + "-" + minor; end def domain_structs() domains.map{ |d| d.struct }.compact; end def result_structs() methods_.map { |m| m.result and m.result.struct }.compact end def structs() result_structs+domain_structs; end def methods_() classes.map { |c| c.methods_ }.flatten; end # Return all methods on chassis for all classes. def methods_on(chassis) @methods_on ||= { } @methods_on[chassis] ||= classes.map { |c| c.methods_on(chassis) }.flatten end private # Merge contents of elements. def xml_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 ? xml_merge(to_child, from_child) : to.add(from_child.deep_clone) } end end # Collect information about generated files. class GenFiles @@files =[] def GenFiles.add(f) @@files << f; puts f; end def GenFiles.get() @@files; end 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. @outdent=2 Pathname.new(@outdir).mkpath unless @outdir=="-" or File.directory?(@outdir) end # Create a new file, set @out. def file(file) GenFiles.add 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 # Append str + '\n' to generated code. def genl(str="") gen str gen "\n" 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 # Generate outdented code def outdent(&block) save=@prefix @prefix=@prefix[0...-2] yield @prefix=save end attr_accessor :out end