relax 0.0.7 → 0.1.0

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.
@@ -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