spreedly 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/History.txt +6 -0
  2. data/Manifest.txt +66 -0
  3. data/README.txt +68 -0
  4. data/Rakefile +72 -0
  5. data/lib/spreedly.rb +154 -0
  6. data/lib/spreedly/common.rb +24 -0
  7. data/lib/spreedly/mock.rb +113 -0
  8. data/lib/spreedly/version.rb +3 -0
  9. data/test/spreedly_gem_test.rb +152 -0
  10. data/test/test_site.yml +2 -0
  11. data/vendor/httparty/History +108 -0
  12. data/vendor/httparty/MIT-LICENSE +20 -0
  13. data/vendor/httparty/Manifest +55 -0
  14. data/vendor/httparty/README +35 -0
  15. data/vendor/httparty/Rakefile +47 -0
  16. data/vendor/httparty/bin/httparty +98 -0
  17. data/vendor/httparty/cucumber.yml +1 -0
  18. data/vendor/httparty/examples/aaws.rb +32 -0
  19. data/vendor/httparty/examples/basic.rb +11 -0
  20. data/vendor/httparty/examples/delicious.rb +37 -0
  21. data/vendor/httparty/examples/google.rb +16 -0
  22. data/vendor/httparty/examples/rubyurl.rb +14 -0
  23. data/vendor/httparty/examples/twitter.rb +31 -0
  24. data/vendor/httparty/examples/whoismyrep.rb +10 -0
  25. data/vendor/httparty/features/basic_authentication.feature +20 -0
  26. data/vendor/httparty/features/command_line.feature +7 -0
  27. data/vendor/httparty/features/deals_with_http_error_codes.feature +26 -0
  28. data/vendor/httparty/features/handles_multiple_formats.feature +34 -0
  29. data/vendor/httparty/features/steps/env.rb +15 -0
  30. data/vendor/httparty/features/steps/httparty_response_steps.rb +26 -0
  31. data/vendor/httparty/features/steps/httparty_steps.rb +15 -0
  32. data/vendor/httparty/features/steps/mongrel_helper.rb +55 -0
  33. data/vendor/httparty/features/steps/remote_service_steps.rb +47 -0
  34. data/vendor/httparty/features/supports_redirection.feature +22 -0
  35. data/vendor/httparty/httparty.gemspec +37 -0
  36. data/vendor/httparty/lib/core_extensions.rb +189 -0
  37. data/vendor/httparty/lib/httparty.rb +201 -0
  38. data/vendor/httparty/lib/httparty/cookie_hash.rb +9 -0
  39. data/vendor/httparty/lib/httparty/exceptions.rb +7 -0
  40. data/vendor/httparty/lib/httparty/logging.rb +35 -0
  41. data/vendor/httparty/lib/httparty/module_inheritable_attributes.rb +25 -0
  42. data/vendor/httparty/lib/httparty/parsers.rb +4 -0
  43. data/vendor/httparty/lib/httparty/parsers/json.rb +74 -0
  44. data/vendor/httparty/lib/httparty/parsers/xml.rb +209 -0
  45. data/vendor/httparty/lib/httparty/request.rb +169 -0
  46. data/vendor/httparty/lib/httparty/response.rb +17 -0
  47. data/vendor/httparty/lib/httparty/version.rb +3 -0
  48. data/vendor/httparty/setup.rb +1585 -0
  49. data/vendor/httparty/spec/fixtures/delicious.xml +23 -0
  50. data/vendor/httparty/spec/fixtures/empty.xml +0 -0
  51. data/vendor/httparty/spec/fixtures/google.html +3 -0
  52. data/vendor/httparty/spec/fixtures/twitter.json +1 -0
  53. data/vendor/httparty/spec/fixtures/twitter.xml +403 -0
  54. data/vendor/httparty/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  55. data/vendor/httparty/spec/hash_spec.rb +49 -0
  56. data/vendor/httparty/spec/httparty/cookie_hash_spec.rb +38 -0
  57. data/vendor/httparty/spec/httparty/parsers/json_spec.rb +44 -0
  58. data/vendor/httparty/spec/httparty/parsers/xml_spec.rb +454 -0
  59. data/vendor/httparty/spec/httparty/request_spec.rb +203 -0
  60. data/vendor/httparty/spec/httparty/response_spec.rb +53 -0
  61. data/vendor/httparty/spec/httparty_spec.rb +259 -0
  62. data/vendor/httparty/spec/spec.opts +3 -0
  63. data/vendor/httparty/spec/spec_helper.rb +21 -0
  64. data/vendor/httparty/spec/string_spec.rb +29 -0
  65. data/vendor/httparty/website/css/common.css +47 -0
  66. data/vendor/httparty/website/index.html +74 -0
  67. metadata +141 -0
@@ -0,0 +1,35 @@
1
+ module HTTParty
2
+ module Logging
3
+ def self.logging_env
4
+ ENV["HTTPARTY_LOGGING"]
5
+ end
6
+
7
+ def self.enabled?
8
+ logging_env
9
+ end
10
+
11
+ def self.destination
12
+ unless @destination
13
+ if(logging_env == "stdout")
14
+ @destination = STDOUT
15
+ else
16
+ @destination = File.open(logging_env, 'a')
17
+ at_exit{@destination.close}
18
+ end
19
+ end
20
+ @destination
21
+ end
22
+
23
+ def self.log(string)
24
+ destination.puts(string)
25
+ end
26
+
27
+ def record_log
28
+ if Logging.enabled?
29
+ record = []
30
+ yield(record)
31
+ Logging.log(record.join("\n"))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module HTTParty
2
+ module ModuleInheritableAttributes #:nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods #:nodoc:
8
+ def mattr_inheritable(*args)
9
+ @mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
10
+ @mattr_inheritable_attrs += args
11
+ args.each do |arg|
12
+ module_eval %(class << self; attr_accessor :#{arg} end)
13
+ end
14
+ @mattr_inheritable_attrs
15
+ end
16
+
17
+ def inherited(subclass)
18
+ @mattr_inheritable_attrs.each do |inheritable_attribute|
19
+ instance_var = "@#{inheritable_attribute}"
20
+ subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ Dir[File.dirname(__FILE__) + "/parsers/*.rb"].sort.each do |path|
2
+ filename = File.basename(path)
3
+ require "httparty/parsers/#{filename}"
4
+ end
@@ -0,0 +1,74 @@
1
+ # Copyright (c) 2004-2008 David Heinemeier Hansson
2
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5
+
6
+ require 'yaml'
7
+ require 'strscan'
8
+
9
+ module HTTParty
10
+ module Parsers #:nodoc:
11
+ module JSON #:nodoc:
12
+ class ParseError < StandardError #:nodoc:
13
+ end
14
+
15
+ def self.decode(json)
16
+ YAML.load(unescape(convert_json_to_yaml(json)))
17
+ rescue ArgumentError => e
18
+ raise ParseError, "Invalid JSON string"
19
+ end
20
+
21
+ protected
22
+
23
+ def self.unescape(str)
24
+ str.gsub(/\\u([0-9a-f]{4})/) {
25
+ [$1.hex].pack("U")
26
+ }
27
+ end
28
+
29
+ # matches YAML-formatted dates
30
+ DATE_REGEX = /^\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[ \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?$/
31
+
32
+ # Ensure that ":" and "," are always followed by a space
33
+ def self.convert_json_to_yaml(json) #:nodoc:
34
+ scanner, quoting, marks, pos, times = StringScanner.new(json), false, [], nil, []
35
+ while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
36
+ case char = scanner[1]
37
+ when '"', "'"
38
+ if !quoting
39
+ quoting = char
40
+ pos = scanner.pos
41
+ elsif quoting == char
42
+ if json[pos..scanner.pos-2] =~ DATE_REGEX
43
+ # found a date, track the exact positions of the quotes so we can remove them later.
44
+ # oh, and increment them for each current mark, each one is an extra padded space that bumps
45
+ # the position in the final YAML output
46
+ total_marks = marks.size
47
+ times << pos+total_marks << scanner.pos+total_marks
48
+ end
49
+ quoting = false
50
+ end
51
+ when ":",","
52
+ marks << scanner.pos - 1 unless quoting
53
+ end
54
+ end
55
+
56
+ if marks.empty?
57
+ json.gsub(/\\\//, '/')
58
+ else
59
+ left_pos = [-1].push(*marks)
60
+ right_pos = marks << json.length
61
+ output = []
62
+ left_pos.each_with_index do |left, i|
63
+ output << json[left.succ..right_pos[i]]
64
+ end
65
+ output = output * " "
66
+
67
+ times.each { |i| output[i-1] = ' ' }
68
+ output.gsub!(/\\\//, '/')
69
+ output
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,209 @@
1
+ require 'rexml/parsers/streamparser'
2
+ require 'rexml/parsers/baseparser'
3
+ require 'rexml/light/node'
4
+
5
+ # This is a slighly modified version of the XMLUtilityNode from
6
+ # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
7
+ # It's mainly just adding vowels, as I ht cd wth n vwls :)
8
+ # This represents the hard part of the work, all I did was change the
9
+ # underlying parser.
10
+ class REXMLUtilityNode #:nodoc:
11
+ attr_accessor :name, :attributes, :children, :type
12
+
13
+ def self.typecasts
14
+ @@typecasts
15
+ end
16
+
17
+ def self.typecasts=(obj)
18
+ @@typecasts = obj
19
+ end
20
+
21
+ def self.available_typecasts
22
+ @@available_typecasts
23
+ end
24
+
25
+ def self.available_typecasts=(obj)
26
+ @@available_typecasts = obj
27
+ end
28
+
29
+ self.typecasts = {}
30
+ self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
31
+ self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
32
+ self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
33
+ self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
34
+ self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
35
+ self.typecasts["decimal"] = lambda{|v| v.nil? ? nil : BigDecimal(v.to_s)}
36
+ self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
37
+ self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
38
+ self.typecasts["symbol"] = lambda{|v| v.nil? ? nil : v.to_sym}
39
+ self.typecasts["string"] = lambda{|v| v.to_s}
40
+ self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)}
41
+ self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
42
+
43
+ self.available_typecasts = self.typecasts.keys
44
+
45
+ def initialize(name, attributes = {})
46
+ @name = name.tr("-", "_")
47
+ # leave the type alone if we don't know what it is
48
+ @type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
49
+
50
+ @nil_element = attributes.delete("nil") == "true"
51
+ @attributes = undasherize_keys(attributes)
52
+ @children = []
53
+ @text = false
54
+ end
55
+
56
+ def add_node(node)
57
+ @text = true if node.is_a? String
58
+ @children << node
59
+ end
60
+
61
+ def to_hash
62
+ if @type == "file"
63
+ f = StringIO.new((@children.first || '').unpack('m').first)
64
+ class << f
65
+ attr_accessor :original_filename, :content_type
66
+ end
67
+ f.original_filename = attributes['name'] || 'untitled'
68
+ f.content_type = attributes['content_type'] || 'application/octet-stream'
69
+ return {name => f}
70
+ end
71
+
72
+ if @text
73
+ return { name => typecast_value( translate_xml_entities( inner_html ) ) }
74
+ else
75
+ #change repeating groups into an array
76
+ groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
77
+
78
+ out = nil
79
+ if @type == "array"
80
+ out = []
81
+ groups.each do |k, v|
82
+ if v.size == 1
83
+ out << v.first.to_hash.entries.first.last
84
+ else
85
+ out << v.map{|e| e.to_hash[k]}
86
+ end
87
+ end
88
+ out = out.flatten
89
+
90
+ else # If Hash
91
+ out = {}
92
+ groups.each do |k,v|
93
+ if v.size == 1
94
+ out.merge!(v.first)
95
+ else
96
+ out.merge!( k => v.map{|e| e.to_hash[k]})
97
+ end
98
+ end
99
+ out.merge! attributes unless attributes.empty?
100
+ out = out.empty? ? nil : out
101
+ end
102
+
103
+ if @type && out.nil?
104
+ { name => typecast_value(out) }
105
+ else
106
+ { name => out }
107
+ end
108
+ end
109
+ end
110
+
111
+ # Typecasts a value based upon its type. For instance, if
112
+ # +node+ has #type == "integer",
113
+ # {{[node.typecast_value("12") #=> 12]}}
114
+ #
115
+ # @param value<String> The value that is being typecast.
116
+ #
117
+ # @details [:type options]
118
+ # "integer"::
119
+ # converts +value+ to an integer with #to_i
120
+ # "boolean"::
121
+ # checks whether +value+, after removing spaces, is the literal
122
+ # "true"
123
+ # "datetime"::
124
+ # Parses +value+ using Time.parse, and returns a UTC Time
125
+ # "date"::
126
+ # Parses +value+ using Date.parse
127
+ #
128
+ # @return <Integer, TrueClass, FalseClass, Time, Date, Object>
129
+ # The result of typecasting +value+.
130
+ #
131
+ # @note
132
+ # If +self+ does not have a "type" key, or if it's not one of the
133
+ # options specified above, the raw +value+ will be returned.
134
+ def typecast_value(value)
135
+ return value unless @type
136
+ proc = self.class.typecasts[@type]
137
+ proc.nil? ? value : proc.call(value)
138
+ end
139
+
140
+ # Convert basic XML entities into their literal values.
141
+ #
142
+ # @param value<#gsub> An XML fragment.
143
+ #
144
+ # @return <#gsub> The XML fragment after converting entities.
145
+ def translate_xml_entities(value)
146
+ value.gsub(/&lt;/, "<").
147
+ gsub(/&gt;/, ">").
148
+ gsub(/&quot;/, '"').
149
+ gsub(/&apos;/, "'").
150
+ gsub(/&amp;/, "&")
151
+ end
152
+
153
+ # Take keys of the form foo-bar and convert them to foo_bar
154
+ def undasherize_keys(params)
155
+ params.keys.each do |key, value|
156
+ params[key.tr("-", "_")] = params.delete(key)
157
+ end
158
+ params
159
+ end
160
+
161
+ # Get the inner_html of the REXML node.
162
+ def inner_html
163
+ @children.join
164
+ end
165
+
166
+ # Converts the node into a readable HTML node.
167
+ #
168
+ # @return <String> The HTML node in text form.
169
+ def to_html
170
+ attributes.merge!(:type => @type ) if @type
171
+ "<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}</#{name}>"
172
+ end
173
+
174
+ # @alias #to_html #to_s
175
+ def to_s
176
+ to_html
177
+ end
178
+ end
179
+
180
+ module HTTParty
181
+ module Parsers #:nodoc:
182
+ module XML #:nodoc:
183
+ def self.parse(xml)
184
+ stack = []
185
+ parser = REXML::Parsers::BaseParser.new(xml)
186
+
187
+ while true
188
+ event = parser.pull
189
+ case event[0]
190
+ when :end_document
191
+ break
192
+ when :end_doctype, :start_doctype
193
+ # do nothing
194
+ when :start_element
195
+ stack.push REXMLUtilityNode.new(event[1], event[2])
196
+ when :end_element
197
+ if stack.size > 1
198
+ temp = stack.pop
199
+ stack.last.add_node(temp)
200
+ end
201
+ when :text, :cdata
202
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
203
+ end
204
+ end
205
+ stack.length > 0 ? stack.pop.to_hash : {}
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,169 @@
1
+ require 'uri'
2
+ require 'httparty/logging'
3
+
4
+ module HTTParty
5
+ class Request #:nodoc:
6
+ SupportedHTTPMethods = [Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Put, Net::HTTP::Delete]
7
+
8
+ include Logging
9
+
10
+ attr_accessor :http_method, :options
11
+ attr_reader :path
12
+
13
+ def initialize(http_method, path, o={})
14
+ self.http_method = http_method
15
+ self.path = path
16
+ self.options = {
17
+ :limit => o.delete(:no_follow) ? 0 : 5,
18
+ :default_params => {},
19
+ }.merge(o)
20
+ @redirect = false
21
+ end
22
+
23
+ def path=(uri)
24
+ @path = URI.parse(uri)
25
+ end
26
+
27
+ def uri
28
+ new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
29
+
30
+ # avoid double query string on redirects [#12]
31
+ unless @redirect || post?
32
+ new_uri.query = query_string(new_uri)
33
+ end
34
+
35
+ new_uri
36
+ end
37
+
38
+ def format
39
+ options[:format]
40
+ end
41
+
42
+ def perform
43
+ validate
44
+ setup_raw_request
45
+ handle_response(get_response)
46
+ end
47
+
48
+ private
49
+ def http
50
+ http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
51
+ http.use_ssl = (uri.port == 443)
52
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
53
+ http
54
+ end
55
+
56
+ def configure_basic_auth
57
+ @raw_request.basic_auth(options[:basic_auth][:username], options[:basic_auth][:password])
58
+ end
59
+
60
+ def setup_raw_request
61
+ @raw_request = http_method.new(uri.request_uri)
62
+
63
+ if post? && options[:query]
64
+ @raw_request.set_form_data(options[:query])
65
+ end
66
+
67
+ @raw_request.body = options[:body].is_a?(Hash) ? options[:body].to_params : options[:body] unless options[:body].blank?
68
+ @raw_request.initialize_http_header options[:headers]
69
+
70
+ configure_basic_auth if options[:basic_auth]
71
+ end
72
+
73
+ def perform_actual_request
74
+ record_log do |record|
75
+ record << "REQUEST"
76
+ record << " #{@raw_request.method} #{http.use_ssl ? "https" : "http"}://#{http.address}#{@raw_request.path}"
77
+ unless @raw_request.to_hash.empty?
78
+ record << " HEADERS"
79
+ @raw_request.each_header do |header, value|
80
+ record << " #{header}: #{value}"
81
+ end
82
+ end
83
+ if @raw_request.body && !@raw_request.body.empty?
84
+ record << " BODY"
85
+ record << " #{@raw_request.body.split("\n").join("\n ")}"
86
+ end
87
+ end
88
+ http.request(@raw_request)
89
+ end
90
+
91
+ def get_response
92
+ response = perform_actual_request
93
+ options[:format] ||= format_from_mimetype(response['content-type'])
94
+ response
95
+ end
96
+
97
+ def query_string(uri)
98
+ query_string_parts = []
99
+ query_string_parts << uri.query unless uri.query.blank?
100
+
101
+ if options[:query].is_a?(Hash)
102
+ query_string_parts << options[:default_params].merge(options[:query]).to_params
103
+ else
104
+ query_string_parts << options[:default_params].to_params unless options[:default_params].blank?
105
+ query_string_parts << options[:query] unless options[:query].blank?
106
+ end
107
+
108
+ query_string_parts.size > 0 ? query_string_parts.join('&') : nil
109
+ end
110
+
111
+ # Raises exception Net::XXX (http error code) if an http error occured
112
+ def handle_response(response)
113
+ case response
114
+ when Net::HTTPRedirection
115
+ record_log{|record| record << "-> REDIRECT to #{response['location']}"}
116
+ options[:limit] -= 1
117
+ self.path = response['location']
118
+ @redirect = true
119
+ perform
120
+ else
121
+ record_log do |record|
122
+ record << "RESPONSE"
123
+ record << " CODE: #{response.code}"
124
+ record << " HEADERS"
125
+ response.each do |header, value|
126
+ record << " #{header}: #{value}"
127
+ end
128
+ record << " BODY"
129
+ record << " #{response.body.split("\n").join("\n ")}"
130
+ end
131
+ parsed_response = parse_response(response.body)
132
+ Response.new(parsed_response, response.body, response.code, response.to_hash)
133
+ end
134
+ end
135
+
136
+ def parse_response(body)
137
+ return nil if body.nil? or body.strip.empty?
138
+ case format
139
+ when :xml
140
+ HTTParty::Parsers::XML.parse(body)
141
+ when :json
142
+ HTTParty::Parsers::JSON.decode(body)
143
+ when :yaml
144
+ YAML::load(body)
145
+ else
146
+ body
147
+ end
148
+ end
149
+
150
+ # Uses the HTTP Content-Type header to determine the format of the response
151
+ # It compares the MIME type returned to the types stored in the AllowedFormats hash
152
+ def format_from_mimetype(mimetype)
153
+ return nil if mimetype.nil?
154
+ AllowedFormats.each { |k, v| return v if mimetype.include?(k) }
155
+ end
156
+
157
+ def validate
158
+ raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0
159
+ raise ArgumentError, 'only get, post, put and delete methods are supported' unless SupportedHTTPMethods.include?(http_method)
160
+ raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
161
+ raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
162
+ raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
163
+ end
164
+
165
+ def post?
166
+ Net::HTTP::Post == http_method
167
+ end
168
+ end
169
+ end