rapuncel 0.0.1.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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