relax 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,133 +0,0 @@
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
@@ -1,147 +0,0 @@
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
data/lib/relax/parsers.rb DELETED
@@ -1,13 +0,0 @@
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
data/lib/relax/query.rb DELETED
@@ -1,46 +0,0 @@
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
data/lib/relax/request.rb DELETED
@@ -1,107 +0,0 @@
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
@@ -1,82 +0,0 @@
1
- module Relax
2
- # Response is intended to be a parent class for responses passed to
3
- # Service#call.
4
- #
5
- # A response is in essence an object used to facilitate XML parsing. It
6
- # stores an XML document, and provides access to it through methods like
7
- # #element and #attribute.
8
- class Response
9
- attr_accessor :raw
10
-
11
- # New takes in and parses the raw response.
12
- #
13
- # This will raise a MissingParameter error if a parameterd marked as
14
- # required is not present in the parsed response.
15
- def initialize(xml)
16
- @raw = xml
17
- @parser = Relax::Parsers::Factory.get(parser_name).new(xml.to_s, self)
18
- end
19
-
20
- def parser_name #:nodoc:
21
- self.class.instance_variable_get('@parser') || :default
22
- end
23
-
24
- def node_name(name, namespace=nil) #:nodoc:
25
- "#{namespace.to_s + ':' if namespace}#{name}"
26
- end
27
-
28
- def method_missing(method, *args) #:nodoc:
29
- if @parser.respond_to?(method)
30
- @parser.__send__(method, *args)
31
- else
32
- super
33
- end
34
- end
35
-
36
- class << self
37
- # When a Response is extended, the superclass's parameters are copied
38
- # into the new class. This behavior has the following side-effect: if
39
- # parameters are added to the superclass after it has been extended,
40
- # those new paramters won't be passed on to its children. This shouldn't
41
- # be a problem in most cases.
42
- def inherited(subclass)
43
- @parameters.each do |name, options|
44
- subclass.parameter(name, options)
45
- end if @parameters
46
-
47
- subclass.parser(@parser) if @parser
48
- end
49
-
50
- # Specifes a parameter that will be automatically parsed when the
51
- # Response is instantiated.
52
- #
53
- # Options:
54
- # - <tt>:attribute</tt>: An attribute name to use, or <tt>true</tt> to
55
- # use the <tt>:element</tt> value as the attribute name on the root.
56
- # - <tt>:collection</tt>: A class used to instantiate each item when
57
- # selecting a collection of elements.
58
- # - <tt>:element</tt>: The XML element name.
59
- # - <tt>:object</tt>: A class used to instantiate an element.
60
- # - <tt>:type</tt>: The type of the parameter. Should be one of
61
- # <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>, or <tt>:date</tt>.
62
- def parameter(name, options={})
63
- attr_accessor name
64
- @parameters ||= {}
65
- @parameters[name] = options
66
- end
67
-
68
- # Specifies the parser to use when decoding the server response. If no
69
- # parser is specified for the response, then the default parser will be
70
- # used.
71
- #
72
- # See Relax::Parsers for a list of available parsers.
73
- def parser(name)
74
- @parser ||= name
75
- end
76
-
77
- def ===(response)
78
- response.is_a?(Class) ? response.ancestors.include?(self) : super
79
- end
80
- end
81
- end
82
- end
@@ -1,79 +0,0 @@
1
- module Relax
2
- # SymbolicHash provides an extension of Hash, but one that only supports keys
3
- # that are symbols. This has been done in an effort to prevent the case where
4
- # both a string key and a symbol key are set on the same hash, and espcially
5
- # for dealing with this particular case when convert the hash to a string.
6
- #
7
- # === Example
8
- #
9
- # hash = Relax::SymbolicHash.new
10
- # hash[:one] = 1
11
- # hash['one'] = 2
12
- # puts hash[:one] # => 2
13
- #
14
- # === Credits
15
- #
16
- # Some of the inspiration (and code) for this class comes from the
17
- # HashWithIndifferentAccess that ships with Rails.
18
- class SymbolicHash < Hash
19
- def initialize(constructor = {})
20
- if constructor.is_a?(Hash)
21
- super()
22
- update(constructor)
23
- else
24
- super(constructor)
25
- end
26
- end
27
-
28
- def [](key)
29
- super(convert_key(key))
30
- end
31
-
32
- def []=(key, value)
33
- super(convert_key(key), convert_value(value))
34
- end
35
-
36
- def update(other_hash)
37
- other_hash.each_pair { |key, value| store(convert_key(key), convert_value(value)) }
38
- self
39
- end
40
- alias :merge! :update
41
-
42
- def fetch(key, *extras)
43
- super(convert_key(key), *extras)
44
- end
45
-
46
- def values_at(*indices)
47
- indices.collect { |key| self[convert_key(key)] }
48
- end
49
-
50
- def dup
51
- SymbolicHash.new(self)
52
- end
53
-
54
- def merge(hash)
55
- self.dup.update(hash)
56
- end
57
-
58
- def delete(key)
59
- super(convert_key(key))
60
- end
61
-
62
- def key?(key)
63
- super(convert_key(key))
64
- end
65
- alias :include? :key?
66
- alias :has_key? :key?
67
- alias :member? :key?
68
-
69
- def convert_key(key)
70
- !key.kind_of?(Symbol) ? key.to_sym : key
71
- end
72
- protected :convert_key
73
-
74
- def convert_value(value)
75
- value
76
- end
77
- protected :convert_value
78
- end
79
- end
@@ -1,29 +0,0 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
2
-
3
- require 'relax'
4
- require 'relax/parsers/factory'
5
-
6
- class TestParser ; end
7
-
8
- describe 'a parser factory' do
9
-
10
- before(:each) do
11
- @factory = Relax::Parsers::Factory
12
- Relax::Parsers::Factory.register(:test, TestParser)
13
- end
14
-
15
- it 'should raise UnrecognizedParser for un-registered names' do
16
- lambda {
17
- @factory.get(:bad_name)
18
- }.should raise_error(Relax::UnrecognizedParser)
19
- end
20
-
21
- it 'should return a registered parser class' do
22
- @factory.get(:test).should ==TestParser
23
- end
24
-
25
- it 'should register the first registered parser as the default' do
26
- @factory.get(:default).should ==Relax::Parsers::Hpricot
27
- end
28
-
29
- end
@@ -1,31 +0,0 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
2
- require File.dirname(__FILE__) + '/../parser_helper'
3
-
4
- class HpricotTestResponse < Relax::Response
5
- class Token < Relax::Response
6
- parser :hpricot
7
- parameter :token_id, :element => :tokenid
8
- parameter :status
9
- end
10
-
11
- class Error < Relax::Response
12
- parser :hpricot
13
- parameter :code, :type => :integer
14
- parameter :message
15
- end
16
-
17
- parser :hpricot
18
- parameter :status, :required => true
19
- parameter :request_id, :element => :requestid, :type => :integer
20
- parameter :valid_request, :element => :requestid, :attribute => :valid
21
- parameter :tokens, :collection => Token
22
- parameter :error, :type => Error
23
- end
24
-
25
- describe 'an Hpricot parser' do
26
- before(:each) do
27
- @response = HpricotTestResponse.new(XML)
28
- end
29
-
30
- it_should_behave_like 'a successfully parsed response'
31
- end
@@ -1,36 +0,0 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
2
- require File.dirname(__FILE__) + '/../parser_helper'
3
-
4
- class RexmlTestResponse < Relax::Response
5
- class Token < Relax::Response
6
- parser :rexml
7
- parameter :token_id, :element => 'TokenId'
8
- parameter :status, :element => 'Status'
9
- end
10
-
11
- class Error < Relax::Response
12
- parser :rexml
13
- parameter :code, :element => 'Code', :type => :integer
14
- parameter :message, :element => 'Message'
15
- end
16
-
17
- parser :rexml
18
- parameter :status, :element => 'Status', :required => true
19
- parameter :request_id, :element => 'RequestId', :type => :integer
20
- parameter :valid_request, :element => 'RequestId', :attribute => :valid
21
- parameter :namespace, :element => 'Namespace', :namespace => 'ns1'
22
- parameter :tokens, :element => 'Tokens', :collection => Token
23
- parameter :error, :element => 'Error', :type => Error
24
- end
25
-
26
- describe 'a REXML parser' do
27
- before(:each) do
28
- @response = RexmlTestResponse.new(XML)
29
- end
30
-
31
- it_should_behave_like 'a successfully parsed response'
32
-
33
- it 'should parse namespaced parameters' do
34
- @response.namespace.should eql('Passed')
35
- end
36
- end