rapuncel 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +171 -0
  3. data/Rakefile +24 -0
  4. data/lib/rapuncel.rb +25 -0
  5. data/lib/rapuncel/adapters/net_http_adapter.rb +34 -0
  6. data/lib/rapuncel/base.rb +7 -0
  7. data/lib/rapuncel/client.rb +54 -0
  8. data/lib/rapuncel/connection.rb +75 -0
  9. data/lib/rapuncel/core_ext/array.rb +23 -0
  10. data/lib/rapuncel/core_ext/big_decimal.rb +7 -0
  11. data/lib/rapuncel/core_ext/boolean.rb +29 -0
  12. data/lib/rapuncel/core_ext/float.rb +11 -0
  13. data/lib/rapuncel/core_ext/hash.rb +32 -0
  14. data/lib/rapuncel/core_ext/integer.rb +11 -0
  15. data/lib/rapuncel/core_ext/nil.rb +7 -0
  16. data/lib/rapuncel/core_ext/object.rb +49 -0
  17. data/lib/rapuncel/core_ext/string.rb +12 -0
  18. data/lib/rapuncel/core_ext/symbol.rb +7 -0
  19. data/lib/rapuncel/core_ext/time.rb +14 -0
  20. data/lib/rapuncel/proxy.rb +82 -0
  21. data/lib/rapuncel/request.rb +49 -0
  22. data/lib/rapuncel/response.rb +92 -0
  23. data/rapuncel-0.0.1.preview.gem +0 -0
  24. data/rapuncel.gemspec +22 -0
  25. data/test/functional/client_test.rb +54 -0
  26. data/test/functional_test_helper.rb +13 -0
  27. data/test/test_helper.rb +38 -0
  28. data/test/test_server.rb +28 -0
  29. data/test/unit/array_test.rb +96 -0
  30. data/test/unit/boolean_test.rb +34 -0
  31. data/test/unit/connection_test.rb +17 -0
  32. data/test/unit/float_test.rb +23 -0
  33. data/test/unit/hash_test.rb +54 -0
  34. data/test/unit/int_test.rb +27 -0
  35. data/test/unit/nil_test.rb +16 -0
  36. data/test/unit/object_test.rb +83 -0
  37. data/test/unit/proxy_test.rb +52 -0
  38. data/test/unit/request_test.rb +34 -0
  39. data/test/unit/response_test.rb +100 -0
  40. data/test/unit/string_test.rb +40 -0
  41. data/test/unit/time_test.rb +23 -0
  42. metadata +154 -0
@@ -0,0 +1,7 @@
1
+ class BigDecimal
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ b.double self.to_s
4
+
5
+ b.to_xml
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ class TrueClass
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ b.boolean "1"
4
+
5
+ b.to_xml
6
+ end
7
+ end
8
+
9
+ class FalseClass
10
+ def to_xml_rpc b = Rapuncel.get_builder
11
+ b.boolean "0"
12
+
13
+ b.to_xml
14
+ end
15
+ end
16
+
17
+ # this is to catch the from_xml_rpc call from Object
18
+ class Rapuncel::Boolean
19
+ def self.from_xml_rpc xml_node
20
+
21
+ # DISCUSS: need convention here:
22
+ case xml_node.text.downcase
23
+ when '1'
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ class Float
2
+ def self.from_xml_rpc xml_node
3
+ xml_node.text.strip.to_f #calling to_float on the text between the (hopefully correct) tags
4
+ end
5
+
6
+ def to_xml_rpc b = Rapuncel.get_builder
7
+ b.double to_s
8
+
9
+ b.to_xml
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ class Hash
2
+ # Hash will be translated into an XML-RPC "struct" object
3
+
4
+ def to_xml_rpc b = Rapuncel.get_builder
5
+ b.struct do |b|
6
+ self.each_pair do |key, value|
7
+ b.member do |b|
8
+ b.name key.to_s
9
+
10
+ b.value do |b|
11
+ value.to_xml_rpc b
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ b.to_xml
18
+ end
19
+
20
+ def self.from_xml_rpc xml_node
21
+ keys_and_values = xml_node.element_children
22
+
23
+ new.tap do |hash|
24
+ keys_and_values.each do |kv|
25
+ key = kv.first_element_child.text.strip.to_sym
26
+ value = Object.from_xml_rpc kv.last_element_child.first_element_child
27
+
28
+ hash[key] = value
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ class Integer
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ b.int self.to_s
4
+
5
+ b.to_xml
6
+ end
7
+
8
+ def self.from_xml_rpc xml_node
9
+ xml_node.text.strip.to_i #calling to_i on the text between the i4 or int tags
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ class NilClass
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ false.to_xml_rpc b
4
+
5
+ b.to_xml
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ class Object
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ if respond_to?(:acts_like?) && acts_like?(:time)
4
+ to_time.to_xml_rpc b
5
+ else
6
+ _collect_ivars_in_hash.to_xml_rpc b
7
+ end
8
+
9
+ b.to_xml
10
+ end
11
+
12
+ def self.from_xml_rpc xml_node
13
+ if xml_node.is_a? String
14
+ xml_node = Nokogiri::XML.parse(xml_node).root
15
+ end
16
+
17
+ return nil if xml_node.nil?
18
+
19
+ case xml_node.name
20
+ when 'i4', 'int'
21
+ Integer.from_xml_rpc xml_node
22
+ when 'array'
23
+ Array.from_xml_rpc xml_node
24
+ when 'struct'
25
+ Hash.from_xml_rpc xml_node
26
+ when 'double'
27
+ Float.from_xml_rpc xml_node
28
+ when 'boolean'
29
+ Rapuncel::Boolean.from_xml_rpc xml_node
30
+ when 'string'
31
+ String.from_xml_rpc xml_node
32
+ when 'dateTime.iso8601'
33
+ Time.from_xml_rpc xml_node
34
+ when 'base64'
35
+ raise 'Now its time to implement Base64'
36
+ else
37
+ raise "What is this? I didn't know #{xml_node.name} was part of the XMLRPC specification? Anyway, the value was: #{xml_node.text.strip}"
38
+ end
39
+ end
40
+
41
+ private
42
+ def _collect_ivars_in_hash
43
+ {}.tap do |hsh|
44
+ instance_variables.each do |ivar|
45
+ hsh[ivar[1..-1]] = instance_variable_get ivar
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,12 @@
1
+ class String
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ b.string self
4
+
5
+ b.to_xml
6
+ end
7
+
8
+ def self.from_xml_rpc xml_node
9
+ xml_node.text.gsub(/(\r\n|\r)/, "\n") #just give back the string between the 'string' tags
10
+ # DISCUSS: to strip or not to strip
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ class Symbol
2
+ def to_xml_rpc b = Rapuncel.get_builder
3
+ b.string to_s
4
+
5
+ b.to_xml
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ require 'time'
2
+
3
+ class Time
4
+
5
+ def to_xml_rpc b=Rapuncel.get_builder
6
+ b.send "dateTime.iso8601", self.iso8601
7
+
8
+ b.to_xml
9
+ end
10
+
11
+ def self.from_xml_rpc xml_node
12
+ Time.parse xml_node.text.strip #make a Time object of this string which is hopefully in the right format
13
+ end
14
+ end
@@ -0,0 +1,82 @@
1
+ require 'rapuncel/request'
2
+
3
+ module Rapuncel
4
+ class Proxy
5
+ PROXY_METHODS = %w(tap inspect clone freeze dup class initialize).freeze
6
+ LOCKED_METHODS = %w(method_missing).freeze
7
+ LOCKED_PATTERN = /(\A__|\?\Z|!\Z)/.freeze
8
+
9
+ class << self
10
+ # Initialize a new Proxy object for a specific Client. Alternatively
11
+ # you can pass a Hash containing configuration for a new Client, which
12
+ # will be created on-the-fly, but not accessible. The second parameter
13
+ # specifies a specific interface/namespace for the remote calls,
14
+ # i.e. if your RPC method is
15
+ #
16
+ # int numbers.add(int a, int b)
17
+ #
18
+ # You can create a specific proxy for +numbers+, and use +add+ directly
19
+ #
20
+ # proxy = Proxy.new client, 'numbers'
21
+ # proxy.add(40, 2) -> 42
22
+ #
23
+ def new client_or_configuration, interface = nil
24
+ client_or_configuration = Client.new client_or_configuration if client_or_configuration.is_a?(Hash)
25
+
26
+ allocate.__tap__ do |new_proxy|
27
+ new_proxy.__initialize__ client_or_configuration, interface
28
+ end
29
+ end
30
+
31
+ def define_proxy_method name
32
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
33
+ def #{name} *args, &block
34
+ call! '#{name}', *args, &block
35
+ end
36
+ RUBY
37
+ end
38
+ end
39
+
40
+ PROXY_METHODS.each do |name|
41
+ alias_method "__#{name}__", name
42
+ end
43
+
44
+ instance_methods.each do |name|
45
+ unless LOCKED_METHODS.include?(name) || LOCKED_PATTERN.match(name)
46
+ define_proxy_method name
47
+ end
48
+ end
49
+
50
+ def call! name, *args
51
+ name = "#{@interface}.#{name}" if @interface
52
+
53
+ @client.call_to_ruby(name, *args).tap do |response|
54
+
55
+ if block_given?
56
+ yield response
57
+ end
58
+ end
59
+ end
60
+
61
+ def __initialize__ client, interface #:nodoc:
62
+ @interface = interface
63
+ @client = client
64
+ end
65
+
66
+ def respond_to? name #:nodoc:
67
+ LOCKED_PATTERN.match(name.to_s) ? super : true
68
+ end
69
+
70
+ protected
71
+ def method_missing name, *args, &block #:nodoc:
72
+ name = name.to_s
73
+
74
+ if LOCKED_PATTERN.match name
75
+ super name.to_sym, *args, &block
76
+ else
77
+ self.__class__.define_proxy_method name
78
+ call! name, *args, &block
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+
2
+
3
+
4
+ module Rapuncel
5
+ class Request
6
+ attr_accessor :method_name, :arguments
7
+
8
+
9
+ # Create a new XML-RPC request
10
+ def initialize method_name, *args
11
+ @method_name, @arguments = method_name, args
12
+ end
13
+
14
+ def to_xml_rpc builder = Rapuncel.get_builder
15
+ method_call! builder
16
+
17
+ builder.to_xml :encoding => 'UTF-8'
18
+ end
19
+
20
+ protected
21
+ def method_call! builder
22
+
23
+ builder.methodCall do |builder|
24
+ method_name! builder
25
+ params! builder
26
+ end
27
+ end
28
+
29
+ def method_name! builder
30
+ builder.methodName method_name
31
+ end
32
+
33
+ def params! builder
34
+ builder.params do |builder|
35
+ arguments.each do |value|
36
+ param! builder, value
37
+ end
38
+ end
39
+ end
40
+
41
+ def param! builder, value
42
+ builder.param do |builder|
43
+ builder.value do |builder|
44
+ value.to_xml_rpc builder
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,92 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Rapuncel
4
+ class Response
5
+ class Exception < ::Exception
6
+ attr_accessor :code, :string
7
+
8
+ def initialize code, string
9
+ @code, @string = code, string
10
+ end
11
+
12
+ def exception
13
+ self
14
+ end
15
+ end
16
+ class Fault < Exception ; end
17
+ class Error < Exception ; end
18
+
19
+ attr_accessor :http_response, :status, :status_code
20
+
21
+ delegate :body,
22
+ :to => :http_response
23
+
24
+ def initialize http_response
25
+ @http_response = http_response
26
+
27
+ evaluate_status
28
+ end
29
+
30
+ def evaluate_status
31
+ @status_code = http_response.code.to_i
32
+
33
+ @status = case
34
+ when !http_response.success? then 'error'
35
+ when parsed_body && method_response_success? then 'success'
36
+ else 'fault'
37
+ end
38
+ end
39
+
40
+ def success?
41
+ status == 'success'
42
+ end
43
+
44
+ def fault?
45
+ status == 'fault'
46
+ end
47
+
48
+ def error?
49
+ status == 'error'
50
+ end
51
+
52
+ def fault
53
+ if fault?
54
+ @fault ||= begin
55
+ fault_node = parsed_body.xpath('/methodResponse/fault/value/struct').first
56
+ fault_hash = Hash.from_xml_rpc(fault_node)
57
+
58
+ Fault.new fault_hash[:faultCode], fault_hash[:faultString]
59
+ end
60
+ end
61
+ end
62
+
63
+ def error
64
+ if error?
65
+ @error ||= Error.new @status_code, body
66
+ end
67
+ end
68
+
69
+ def result
70
+ if success?
71
+ @result ||= begin
72
+ return_values = parsed_body.xpath('/methodResponse/params/param/value/*')
73
+
74
+ Object.from_xml_rpc return_values.first
75
+ end
76
+ end
77
+ end
78
+
79
+ def to_ruby
80
+ result || fault || error
81
+ end
82
+
83
+ protected
84
+ def parsed_body
85
+ @xml_doc ||= Nokogiri::XML.parse body
86
+ end
87
+
88
+ def method_response_success?
89
+ parsed_body.xpath('/methodResponse/fault').empty?
90
+ end
91
+ end
92
+ end
Binary file
data/rapuncel.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "rapuncel"
3
+ s.version = "0.0.1.alpha"
4
+ s.date = "2010-12-02"
5
+ s.authors = ["Michael Eickenberg", "Marian Theisen"]
6
+ s.email = 'marian@cice-online.net'
7
+ s.summary = "Simple XML-RPC Client"
8
+ s.homepage = "http://github.com/cice/rapuncel"
9
+ s.description = "Rapuncel is a simple XML-RPC Client based on Nokogiri, thus provides a fast and easy way to interact with XML-RPC services."
10
+
11
+ s.files = Dir["**/*"] -
12
+ Dir["coverage/**/*"] -
13
+ Dir["rdoc/**/*"] -
14
+ Dir["doc/**/*"] -
15
+ Dir["sdoc/**/*"] -
16
+ Dir["rcov/**/*"]
17
+
18
+ s.add_dependency 'nokogiri'
19
+ s.add_dependency 'activesupport', '>= 3.0.0'
20
+
21
+ s.add_development_dependency 'mocha'
22
+ end