generators/handsoap/handsoap_generator.rb in troelskn-handsoap-0.1.2 vs generators/handsoap/handsoap_generator.rb in troelskn-handsoap-0.2.0
- old
+ new
@@ -1,313 +1,85 @@
-require 'open-uri'
-require 'uri'
-require 'cgi'
-require 'nokogiri'
+# -*- coding: utf-8 -*-
+require "#{File.dirname(__FILE__)}/../../lib/handsoap/parser.rb"
+require "#{File.dirname(__FILE__)}/../../lib/handsoap/compiler.rb"
-# TODO: inline builders, if they are only ever used in one place
-
-class Builders
- def initialize(xsd)
- @xsd = xsd
- @builders = {}
- end
- def add(type)
- @builders[type] = false unless @builders[type]
- end
- def each
- results = []
- while builder = @builders.find { |builder,is_rendered| !is_rendered }
- results << yield(@xsd.get_complex_type(builder[0]))
- @builders[builder[0]] = true
- end
- results.join("")
- end
-end
-
-class HandsoapGenerator < Rails::Generator::NamedBase
- attr_reader :wsdl
+# TODO
+# options:
+# soap_actions (true/false)
+# soap_version (1/2/auto)
+# basename
+class HandsoapGenerator < Rails::Generator::Base
def initialize(runtime_args, runtime_options = {})
super
# Wsdl argument is required.
usage if @args.empty?
@wsdl_uri = @args.shift
end
def banner
- "WARNING: This generator is rather incomplete and buggy. Use at your own risk." +
- "\n" + "Usage: #{$0} #{spec.name} name URI [options]" +
- "\n" + " name Basename of the service class" +
- "\n" + " URI URI of the WSDL to generate from"
+ "Generates the scaffold for a Handsoap binding."+
+ "\n" + "You still have to fill in most of the meat, but this gives you a head start." +
+ "\n" + "Usage: #{$0} #{spec.name} URI" +
+ "\n" + " URI URI of the WSDL to generate from"
end
def manifest
+ wsdl = Handsoap::Parser::Wsdl.read(@wsdl_uri)
+ protocol = wsdl.preferred_protocol
+ file_name = Handsoap::Compiler.service_basename(wsdl)
record do |m|
- @wsdl = Handsoap::Wsdl.new(@wsdl_uri)
- @wsdl.parse!
- @xsd = Handsoap::XsdSpider.new(@wsdl_uri)
- @xsd.process!
m.directory "app"
m.directory "app/models"
- @builders = Builders.new(@xsd)
- m.template "gateway.rbt", "app/models/#{file_name}_service.rb"
- end
- end
-
- def builders
- @builders
- end
-
- def render_build(context_name, message_type, varname = nil, indentation = ' ')
- if varname.nil?
- ruby_name = message_type.ruby_name
- else
- ruby_name = "#{varname}[:#{message_type.ruby_name}]"
- end
- # message_type.namespaces
- if message_type.attribute?
- "#{context_name}.set_attr " + '"' + message_type.name + '", ' + ruby_name
- elsif message_type.boolean?
- "#{context_name}.add " + '"' + message_type.name + '", bool_to_str(' + ruby_name + ')'
- elsif message_type.primitive?
- "#{context_name}.add " + '"' + message_type.name + '", ' + ruby_name
- elsif message_type.list?
- list_type = @xsd.get_complex_type(message_type.type)
- builders.add(list_type.type)
- # TODO: a naming conflict waiting to happen hereabout
- # TODO: indentation
- "#{varname}.each do |#{message_type.ruby_name}|" + "\n" + indentation +
- " build_#{list_type.ruby_type}!(#{context_name}, #{message_type.ruby_name})" + "\n" + indentation +
- "end"
- else
- builders.add(message_type.type)
- "build_#{message_type.ruby_type}!(#{context_name}, " + ruby_name + ")"
- end
- end
-
-end
-
-module Handsoap
-
- class Wsdl
- attr_reader :uri, :soap_actions, :soap_ports, :target_namespace
- def initialize(uri)
- @uri = uri
- end
-
- def parse!
- wsdl = Nokogiri.XML(Kernel.open(@uri).read)
- @target_namespace = wsdl.namespaces['xmlns:tns'] || wsdl.namespaces['xmlns']
- @soap_actions = []
- @soap_ports = []
- messages = {}
-
- wsdl.xpath('//wsdl:message').each do |message|
- message_name = message['name']
- messages[message_name] = message.xpath('wsdl:part').map { |part| MessageType::Part.new(part['type'] || 'xs:element', part['name']) }
+ m.file_contents "app/models/#{file_name}_service.rb" do |file|
+ file.write Handsoap::Compiler.compile_service(wsdl, protocol, :soap_actions)
end
-
- wsdl.xpath('//*[name()="soap:operation"]').each do |operation|
- operation_name = operation.parent['name']
- operation_spec = wsdl.xpath('//wsdl:operation[@name="' + operation_name + '"]').first
- raise RuntimeError, "Couldn't find wsdl:operation node for #{operation_name}" if operation_spec.nil?
- msg_type_in = operation_spec.xpath('./wsdl:input').first["message"]
- raise RuntimeError, "Couldn't find wsdl:input node for #{operation_name}" if msg_type_in.nil?
- raise RuntimeError, "Invalid message type #{msg_type_in} for #{operation_name}" if messages[msg_type_in].nil?
- msg_type_out = operation_spec.xpath('./wsdl:output').first["message"]
- raise RuntimeError, "Couldn't find wsdl:output node for #{operation_name}" if msg_type_out.nil?
- raise RuntimeError, "Invalid message type #{msg_type_out} for #{operation_name}" if messages[msg_type_out].nil?
- @soap_actions << SoapAction.new(operation, messages[msg_type_in], messages[msg_type_out])
+ m.directory "tests"
+ m.directory "tests/integration"
+ m.file_contents "tests/integration/#{file_name}_service_test.rb" do |file|
+ file.write Handsoap::Compiler.compile_test(wsdl, protocol)
end
- raise RuntimeError, "Could not parse WSDL" if soap_actions.empty?
-
- wsdl.xpath('//wsdl:port', {"xmlns:wsdl" => 'http://schemas.xmlsoap.org/wsdl/'}).each do |port|
- name = port['name'].underscore
- location = port.xpath('./*[@location]').first['location']
- @soap_ports << { :name => name, :soap_name => port['name'], :location => location }
- end
+ # TODO
+ # Ask user about which endpoints to use ?
+ # puts "Detected endpoints:"
+ # puts Handsoap::Compiler.compile_endpoints(wsdl, protocol)
end
end
- class SoapAction
- attr_reader :input_type, :output_type
- def initialize(xml_node, input_type, output_type)
- @xml_node = xml_node
- @input_type = input_type
- @output_type = output_type
- end
- def name
- @xml_node.parent['name'].underscore
- end
- def soap_name
- @xml_node.parent['name']
- end
- def href
- @xml_node['soapAction']
- end
- end
+end
- module MessageType
-
- # complex-type is a spec (class), not an element ... (object)
- # <xs:complexType name="User">
- # <xs:annotation>
- # <xs:documentation>The element specifies a user</xs:documentation>
- # </xs:annotation>
- # <xs:attribute name="dn" type="xs:string" use="required"/>
- # </xs:complexType>
- class ComplexType
- def initialize(xml_node)
- @xml_node = xml_node
- end
- def type
- @xml_node['name']
- end
- def ruby_type
- type.gsub(/^.*:/, "").underscore.gsub(/-/, '_')
- end
- def elements
- @xml_node.xpath('./xs:attribute|./xs:all/xs:element|./xs:sequence').map do |node|
- case
- when node.node_name == 'attribute'
- Attribute.new(node['type'], node['name'])
- when node.node_name == 'element'
- Element.new(node['type'], node['name'], []) # TODO: elements.elements
- when node.node_name == 'sequence'
- choice_node = node.xpath('./xs:choice').first
- if choice_node
- # TODO
- Attribute.new('xs:choice', 'todo')
- else
- entity_node = node.xpath('./xs:element').first
- Sequence.new(entity_node['type'], entity_node['name'])
- end
- else
- puts node
- raise "Unknown type #{node.node_name}"
+module Handsoap #:nodoc:
+ module Generator #:nodoc:
+ module Commands #:nodoc:
+ module Create
+ def file_contents(relative_destination, &block)
+ destination = destination_path(relative_destination)
+ temp_file = Tempfile.new("handsoap_generator")
+ temp_file_relative_path = relative_path(temp_file.path, File.expand_path(source_path("/.")))
+ begin
+ yield temp_file
+ temp_file.close
+ return self.file(temp_file_relative_path, relative_destination)
+ ensure
+ temp_file.unlink
end
end
- end
- end
- class Base
- attr_reader :type, :name
- def initialize(type, name)
- raise "'type' can't be nil" if type.nil?
- raise "'name' can't be nil" if name.nil?
- @type = type
- @name = name
- end
- def ruby_type
- type.gsub(/^.*:/, "").underscore.gsub(/-/, '_')
- end
- def ruby_name
- name.underscore.gsub(/-/, '_')
- end
- def attribute?
- false
- end
- def primitive?
- /^xs:/.match type
- end
- def boolean?
- type == "xs:boolean"
- end
- def list?
- false
- end
- end
+ private
- # Parts are shallow elements
- # <wsdl:part name="widget-instance-id" type="xs:int" />
- class Part < Base
- end
-
- # <wsdl:part name="widget-instance-id" type="xs:int" />
- # <xs:element maxOccurs="1" minOccurs="0" name="description" type="xs:string"/>
- class Element < Base
- attr_reader :elements
- def initialize(type, name, elements = [])
- super(type, name)
- @elements = elements
- end
- end
-
- # <xs:attribute name="id" type="xs:int" use="required"/>
- class Attribute < Base
- def primitive?
- true
- end
- def attribute?
- true
- end
- end
-
- # <xs:sequence>
- # <xs:element maxOccurs="unbounded" minOccurs="0" name="widget-area" type="WidgetArea"/>
- # </xs:sequence>
- class Sequence < Base
- def list?
- true
- end
- end
- end
-end
-
-module Handsoap
-
- class XsdSpider
- def initialize(uri)
- @queue = []
- @wsdl_uri = uri
- end
-
- def results
- @queue.map { |element| element[:data] }
- end
-
- def get_complex_type(name)
- # TODO namespace
- short_name = name.gsub(/^.*:/, "")
- results.each do |data|
- search = data[:document].xpath('//xs:complexType[@name="' + short_name + '"]')
- if search.any?
- return MessageType::ComplexType.new(search.first)
+ # Convert the given absolute path into a path
+ # relative to the second given absolute path.
+ # http://www.justskins.com/forums/file-relative-path-handling-97116.html
+ def relative_path(abspath, relative_to)
+ path = abspath.split(File::SEPARATOR)
+ rel = relative_to.split(File::SEPARATOR)
+ while (path.length > 0) && (path.first == rel.first)
+ path.shift
+ rel.shift
+ end
+ ('..' + File::SEPARATOR) * rel.length + path.join(File::SEPARATOR)
end
end
- raise "Didn't find '#{name}' (short name #{short_name})"
end
-
- def process!
- spider_href(@wsdl_uri, nil)
- while process_next do end
- end
-
- private
-
- def add_href(href, namespace)
- unless @queue.find { |element| element[:href] == href }
- @queue << { :href => href, :namespace => namespace, :state => :new, :data => {} }
- end
- end
-
- def process_next
- next_element = @queue.find { |element| element[:state] == :new }
- if next_element
- next_element[:data] = spider_href(next_element[:href], next_element[:namespace])
- next_element[:state] = :done
- return true
- end
- return false
- end
-
- def spider_href(href, namespace)
- raise "'href' must be a String" if href.nil?
- xsd = Nokogiri.XML(Kernel.open(href).read)
- # <xs:include schemaLocation="...xsd"/>
- # <xs:import namespace="" schemaLocation="...xsd"/>
- xsd.xpath('//*[@schemaLocation]').each do |inc|
- add_href(inc['schemaLocation'], inc['namespace'] || namespace)
- end
- { :document => xsd, :namespace => namespace }
- end
end
end
+
+Rails::Generator::Commands::Create.send :include, Handsoap::Generator::Commands::Create