dbalatero-relax 0.0.7.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/relax.rb ADDED
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'relax/query'
4
+ require 'relax/parsers'
5
+ require 'relax/request'
6
+ require 'relax/response'
7
+ require 'relax/service'
8
+ require 'relax/symbolic_hash'
9
+
10
+ module Relax
11
+ class MissingParameter < ArgumentError ; end
12
+ class UnrecognizedParser < ArgumentError ; end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'date'
2
+ require 'time'
3
+
4
+ require 'relax/parsers/factory'
5
+ require 'relax/parsers/base'
6
+
7
+ require 'relax/parsers/hpricot'
8
+ require 'relax/parsers/rexml'
9
+
10
+ module Relax
11
+ module Parsers
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ module Relax
2
+ module Parsers
3
+ class Base
4
+ attr_reader :parent
5
+ attr_reader :parameters
6
+
7
+ def initialize(raw, parent)
8
+ @parent = parent
9
+ @parameters = parent.class.instance_variable_get('@parameters')
10
+ parse!
11
+ end
12
+
13
+ def parse!; end
14
+
15
+ def root; end
16
+ def is?(name); end
17
+ def has?(name); end
18
+ def element(name); end
19
+ def elements(name); end
20
+
21
+ def attribute(element, name); end
22
+ def value(value); end
23
+ def text_value(value); end
24
+ def integer_value(value); end
25
+ def float_value(value); end
26
+ def date_value(value); end
27
+ def time_value(value); end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ module Relax
2
+ module Parsers
3
+ # Manages the Relax::Parsers in the library.
4
+ module Factory
5
+ class << self
6
+ # Returns the parser class which has been registered for the given
7
+ # +name+.
8
+ def get(name)
9
+ @@parsers ||= {}
10
+ @@parsers[name] || raise(UnrecognizedParser, "Given parser name not recognized: #{name.inspect}. Expected one of: #{@@parsers.keys.inspect}")
11
+ end
12
+
13
+ # Registers a new parser with the factory. The +name+ should be unique,
14
+ # but if not, it will override the previously defined parser for the
15
+ # given +name+.
16
+ def register(name, klass)
17
+ @@parsers ||= {}
18
+ @@parsers[:default] = klass if @@parsers.empty?
19
+ @@parsers[name] = klass
20
+ end
21
+
22
+ # Removes all registered parsers from the factory.
23
+ def clear!
24
+ @@parsers = {}
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,133 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+
4
+ module Relax
5
+ module Parsers
6
+ # Parses the server's raw response using the Hpricot library.
7
+ class Hpricot < Base
8
+ FACTORY_NAME = :hpricot
9
+
10
+ def initialize(raw, parent)
11
+ @xml = ::Hpricot.XML(raw)
12
+ super(raw, parent)
13
+ end
14
+
15
+ def parse!
16
+ if parameters
17
+ parameters.each do |parameter, options|
18
+ begin
19
+ element = options[:element] || parameter
20
+
21
+ node = case
22
+ when options[:attribute] && options[:attribute] == true
23
+ attribute(root, element)
24
+ when options[:attribute]
25
+ attribute(element(element), options[:attribute])
26
+ when options[:collection]
27
+ elements(element)
28
+ else
29
+ element(element)
30
+ end
31
+
32
+ if options[:collection]
33
+ value = node.collect do |element|
34
+ options[:collection].new(element)
35
+ end
36
+ else
37
+ value = case type = options[:type]
38
+ when Response
39
+ type.new(node)
40
+ when :date
41
+ date_value(node)
42
+ when :time
43
+ time_value(node)
44
+ when :float
45
+ float_value(node)
46
+ when :integer
47
+ integer_value(node)
48
+ when :text
49
+ else
50
+ text_value(node)
51
+ end
52
+ end
53
+
54
+ parent.instance_variable_set("@#{parameter}", value)
55
+ rescue ::Hpricot::Error
56
+ raise Relax::MissingParameter if options[:required]
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ # Returns the root of the XML document.
63
+ def root
64
+ @xml.root
65
+ end
66
+
67
+ # Checks the name of the root node.
68
+ def is?(name)
69
+ root.name.gsub(/.*:(.*)/, '\1') == node_name(name)
70
+ end
71
+
72
+ # Returns a set of elements matching name.
73
+ def elements(name)
74
+ root.search(root_path(name))
75
+ end
76
+
77
+ # Returns an element of the specified name.
78
+ def element(name)
79
+ root.at(root_path(name))
80
+ end
81
+ alias :has? :element
82
+
83
+ # Returns an attribute on an element.
84
+ def attribute(element, name)
85
+ element[name]
86
+ end
87
+
88
+ # Gets the value of an element or attribute.
89
+ def value(value)
90
+ value.is_a?(::Hpricot::Elem) ? value.inner_text : value.to_s
91
+ end
92
+
93
+ # Gets a text value.
94
+ def text_value(value)
95
+ value(value)
96
+ end
97
+
98
+ # Gets an integer value.
99
+ def integer_value(value)
100
+ value(value).to_i
101
+ end
102
+
103
+ # Gets a float value.
104
+ def float_value(value)
105
+ value(value).to_f
106
+ end
107
+
108
+ # Gets a date value.
109
+ def date_value(value)
110
+ Date.parse(value(value))
111
+ end
112
+
113
+ # Gets a time value.
114
+ def time_value(value)
115
+ Time.parse(value(value))
116
+ end
117
+
118
+ # Converts a name to a node name.
119
+ def node_name(name)
120
+ @parent.node_name(name)
121
+ end
122
+ private :node_name
123
+
124
+ # Gets the XPath expression representing the root node.
125
+ def root_path(name)
126
+ "/#{node_name(name)}"
127
+ end
128
+ private :root_path
129
+ end
130
+
131
+ Factory.register(Hpricot::FACTORY_NAME, Hpricot)
132
+ end
133
+ end
@@ -0,0 +1,147 @@
1
+ require 'rubygems'
2
+ require 'rexml/document'
3
+
4
+ module Relax
5
+ module Parsers
6
+ # Parsers the server's response using the REXML library.
7
+ #
8
+ # Benefits:
9
+ #
10
+ # * XML Namespace support (parameter :foo, :namespace => 'bar')
11
+ #
12
+ # Drawbacks:
13
+ #
14
+ # * Case sensitive field names (<Status>..</> != parameter :status)
15
+ class REXML < Base
16
+ FACTORY_NAME = :rexml
17
+
18
+ def initialize(raw, parent)
19
+ @xml = ::REXML::Document.new(raw)
20
+ super(raw, parent)
21
+ end
22
+
23
+ def parse!
24
+ if parameters
25
+ parameters.each do |parameter, options|
26
+ begin
27
+ element = options[:element] || parameter
28
+ namespace = options[:namespace]
29
+
30
+ node = case
31
+ when options[:attribute] && options[:attribute] == true
32
+ attribute(root, element, namespace)
33
+ when options[:attribute]
34
+ attribute(element(element), options[:attribute], namespace)
35
+ when options[:collection]
36
+ elements(element, namespace)
37
+ else
38
+ element(element, namespace)
39
+ end
40
+
41
+ if options[:collection]
42
+ value = node.collect do |element|
43
+ options[:collection].new(element.to_s)
44
+ end
45
+ else
46
+ value = case type = options[:type]
47
+ when Response
48
+ type.new(node)
49
+ when :date
50
+ date_value(node)
51
+ when :time
52
+ time_value(node)
53
+ when :float
54
+ float_value(node)
55
+ when :integer
56
+ integer_value(node)
57
+ when :text
58
+ else
59
+ text_value(node)
60
+ end
61
+ end
62
+
63
+ parent.instance_variable_set("@#{parameter}", value)
64
+ rescue
65
+ raise(Relax::MissingParameter) if node.nil? && options[:required]
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # Returns the root of the XML document.
72
+ def root
73
+ @xml.root
74
+ end
75
+
76
+ # Checks the name of the root node.
77
+ def is?(name, namespace=nil)
78
+ root.name == node_name(name, nil)
79
+ end
80
+
81
+ # Returns a set of elements matching name.
82
+ def elements(name, namespace=nil)
83
+ root.get_elements(node_path(name, namespace))
84
+ end
85
+
86
+ # Returns an element of the specified name.
87
+ def element(name, namespace=nil)
88
+ root.elements[node_path(name, namespace)]
89
+ end
90
+ alias :has? :element
91
+
92
+ # Returns an attribute on an element.
93
+ def attribute(element, name, namespace=nil)
94
+ element.attribute(name)
95
+ end
96
+
97
+ # Gets the value of an element or attribute.
98
+ def value(value)
99
+ value.is_a?(::REXML::Element) ? value.text : value.to_s
100
+ end
101
+
102
+ # Gets a text value.
103
+ def text_value(value)
104
+ value(value)
105
+ end
106
+
107
+ # Gets an integer value.
108
+ def integer_value(value)
109
+ value(value).to_i
110
+ end
111
+
112
+ # Gets a float value.
113
+ def float_value(value)
114
+ value(value).to_f
115
+ end
116
+
117
+ # Gets a date value.
118
+ def date_value(value)
119
+ Date.parse(value(value))
120
+ end
121
+
122
+ # Gets a time value.
123
+ def time_value(value)
124
+ Time.parse(value(value))
125
+ end
126
+
127
+ # Converts a name to a node name.
128
+ def node_name(name, namespace=nil)
129
+ @parent.node_name(name, namespace)
130
+ end
131
+ private :node_name
132
+
133
+ # Gets the XPath expression representing the root node.
134
+ def root_path(name)
135
+ "/#{node_name(name)}"
136
+ end
137
+ private :root_path
138
+
139
+ def node_path(name, namespace=nil)
140
+ "#{node_name(name, namespace)}"
141
+ end
142
+ private :node_path
143
+ end
144
+
145
+ Factory.register(REXML::FACTORY_NAME, REXML)
146
+ end
147
+ end
@@ -0,0 +1,46 @@
1
+ require 'cgi'
2
+ require 'uri'
3
+
4
+ require 'relax/symbolic_hash'
5
+
6
+ module Relax
7
+ # Query is used to represent the query portion of a URL. It's basically just
8
+ # a hash, where each key/value pair is a query parameter.
9
+ class Query < SymbolicHash
10
+ # Converts the Query to a query string for use in a URL.
11
+ def to_s
12
+ keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |key|
13
+ "#{key.to_s}=#{self.class.escape_value(fetch(key))}"
14
+ end.join('&')
15
+ end
16
+
17
+ class << self
18
+ # Parses a URL and returns a Query with its query portion.
19
+ def parse(uri)
20
+ query = uri.query.split('&').inject({}) do |query, parameter|
21
+ key, value = parameter.split('=', 2)
22
+ query[unescape_value(key)] = unescape_value(value)
23
+ query
24
+ end
25
+ self.new(query)
26
+ end
27
+
28
+ # Escapes a query parameter value.
29
+ def escape_value(value)
30
+ CGI.escape(value.to_s).gsub('%20', '+')
31
+ end
32
+
33
+ # Unescapes a query parameter value.
34
+ def unescape_value(value)
35
+ CGI.unescape(value)
36
+ end
37
+ end
38
+
39
+ protected
40
+
41
+ # Converts each value of the Query to a string as it's added.
42
+ def convert_value(value)
43
+ value.to_s
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,107 @@
1
+ require 'relax/query'
2
+
3
+ module Relax
4
+ # Request is intended to be a parent class for requests passed to
5
+ # Service#call.
6
+ class Request
7
+ @parameters = {}
8
+
9
+ # New takes an optional hash of default parameter values. When passed,
10
+ # the values will be set on the request if the key exists as a valid
11
+ # parameter name.
12
+ def initialize(defaults={})
13
+ # initialize default parameter values
14
+ self.class.parameters.each do |parameter, options|
15
+ if defaults.has_key?(parameter)
16
+ value = defaults[parameter]
17
+ elsif options[:value]
18
+ value = options[:value]
19
+ end
20
+
21
+ instance_variable_set("@#{parameter}", value) if value
22
+ end
23
+ end
24
+
25
+ # Converts this request into a Query object.
26
+ def to_query
27
+ self.class.parameters.keys.inject(Query.new) do |query, key|
28
+ value = send(key)
29
+ options = self.class.parameters[key]
30
+
31
+ if value && !options[:type]
32
+ query[convert_key(key)] = value if value
33
+ elsif options[:type]
34
+ options[:type].parameters.each do |parameter, options|
35
+ query[convert_complex_key(key, parameter)] = value.send(parameter) if value
36
+ end
37
+ end
38
+
39
+ query
40
+ end
41
+ end
42
+
43
+ # Converts this request into a query string for use in a URL.
44
+ def to_s
45
+ to_query.to_s
46
+ end
47
+
48
+ # Checks the validity of the property values in this request.
49
+ def valid?
50
+ self.class.parameters.each do |key, options|
51
+ if options[:required]
52
+ value = send(key)
53
+ raise Relax::MissingParameter if value.nil?
54
+ end
55
+ end
56
+ end
57
+
58
+ # Converts a key when the Request is converted to a query. By default, no
59
+ # conversion actually takes place, but this method can be overridden by
60
+ # child classes to perform standard manipulations, such as replacing
61
+ # underscores.
62
+ def convert_key(key)
63
+ key
64
+ end
65
+ protected :convert_key
66
+
67
+ # Converts a complex key (i.e. a parameter with a custom type) when the
68
+ # Request is converted to a query. By default, this means the key name and
69
+ # the parameter name separated by two underscores. This method can be
70
+ # overridden by child classes.
71
+ def convert_complex_key(key, parameter)
72
+ "#{convert_key(key)}.#{convert_key(parameter)}"
73
+ end
74
+ protected :convert_complex_key
75
+
76
+ class << self
77
+ # Create the parameters hash for the subclass.
78
+ def inherited(subclass) #:nodoc:
79
+ subclass.instance_variable_set('@parameters', {})
80
+ end
81
+
82
+ # Specifies a parameter to create on the request class.
83
+ #
84
+ # Options:
85
+ # - <tt>:type</tt>: An optional custom data type for the parameter.
86
+ # This must be a class that is a descendent of Request.
87
+ # - <tt>:value</tt>: The default value for this parameter.
88
+ def parameter(name, options = {})
89
+ attr_accessor name
90
+ options = @parameters[name].merge(options) if @parameters.has_key?(name)
91
+ @parameters[name] = options
92
+ end
93
+
94
+ # Adds a template value to a request class. Equivalent to creating a
95
+ # parameter with a default value.
96
+ def []=(key, value)
97
+ parameter(key, :value => value)
98
+ end
99
+
100
+ # Returns a hash of all of the parameters for this request, including
101
+ # those that are inherited.
102
+ def parameters #:nodoc:
103
+ (superclass.respond_to?(:parameters) ? superclass.parameters : {}).merge(@parameters)
104
+ end
105
+ end
106
+ end
107
+ end