httparty 0.2.10 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of httparty might be problematic. Click here for more details.

@@ -2,12 +2,8 @@ $:.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require 'net/http'
4
4
  require 'net/https'
5
- require 'rubygems'
6
- gem 'json', '>= 1.1.3'
7
- require 'json'
8
-
9
- require 'module_level_inheritable_attributes'
10
5
  require 'core_extensions'
6
+ require 'httparty/module_inheritable_attributes'
11
7
 
12
8
  module HTTParty
13
9
 
@@ -23,36 +19,64 @@ module HTTParty
23
19
 
24
20
  def self.included(base)
25
21
  base.extend ClassMethods
26
- base.send :include, ModuleLevelInheritableAttributes
22
+ base.send :include, HTTParty::ModuleInheritableAttributes
27
23
  base.send(:mattr_inheritable, :default_options)
28
24
  base.instance_variable_set("@default_options", {})
29
25
  end
30
26
 
31
27
  module ClassMethods
32
- def default_options
33
- @default_options
34
- end
35
-
28
+ # Allows setting http proxy information to be used
29
+ #
30
+ # class Foo
31
+ # include HTTParty
32
+ # http_proxy 'http://foo.com', 80
33
+ # end
36
34
  def http_proxy(addr=nil, port = nil)
37
35
  default_options[:http_proxyaddr] = addr
38
36
  default_options[:http_proxyport] = port
39
37
  end
40
-
38
+
39
+ # Allows setting a base uri to be used for each request.
40
+ # Will normalize uri to include http, etc.
41
+ #
42
+ # class Foo
43
+ # include HTTParty
44
+ # base_uri 'twitter.com'
45
+ # end
41
46
  def base_uri(uri=nil)
42
47
  return default_options[:base_uri] unless uri
43
48
  default_options[:base_uri] = HTTParty.normalize_base_uri(uri)
44
49
  end
45
-
50
+
51
+ # Allows setting basic authentication username and password.
52
+ #
53
+ # class Foo
54
+ # include HTTParty
55
+ # basic_auth 'username', 'password'
56
+ # end
46
57
  def basic_auth(u, p)
47
58
  default_options[:basic_auth] = {:username => u, :password => p}
48
59
  end
49
60
 
61
+ # Allows setting default parameters to be appended to each request.
62
+ # Great for api keys and such.
63
+ #
64
+ # class Foo
65
+ # include HTTParty
66
+ # default_params :api_key => 'secret', :another => 'foo'
67
+ # end
50
68
  def default_params(h={})
51
69
  raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
52
70
  default_options[:default_params] ||= {}
53
71
  default_options[:default_params].merge!(h)
54
72
  end
55
-
73
+
74
+ # Allows setting a base uri to be used for each request.
75
+ #
76
+ # class Foo
77
+ # include HTTParty
78
+ # headers 'Accept' => 'text/html'
79
+ # end
56
80
  def headers(h={})
57
81
  raise ArgumentError, 'Headers must be a hash' unless h.is_a?(Hash)
58
82
  default_options[:headers] ||= {}
@@ -65,15 +89,46 @@ module HTTParty
65
89
  default_options[:cookies].add_cookies(h)
66
90
  end
67
91
 
92
+ # Allows setting the format with which to parse.
93
+ # Must be one of the allowed formats ie: json, xml
94
+ #
95
+ # class Foo
96
+ # include HTTParty
97
+ # format :json
98
+ # end
68
99
  def format(f)
69
100
  raise UnsupportedFormat, "Must be one of: #{AllowedFormats.values.join(', ')}" unless AllowedFormats.value?(f)
70
101
  default_options[:format] = f
71
102
  end
72
103
 
104
+ # Allows making a get request to a url.
105
+ #
106
+ # class Foo
107
+ # include HTTParty
108
+ # end
109
+ #
110
+ # # Simple get with full url
111
+ # Foo.get('http://foo.com/resource.json')
112
+ #
113
+ # # Simple get with full url and query parameters
114
+ # # ie: http://foo.com/resource.json?limit=10
115
+ # Foo.get('http://foo.com/resource.json', :query => {:limit => 10})
73
116
  def get(path, options={})
74
117
  perform_request Net::HTTP::Get, path, options
75
118
  end
76
-
119
+
120
+ # Allows making a post request to a url.
121
+ #
122
+ # class Foo
123
+ # include HTTParty
124
+ # end
125
+ #
126
+ # # Simple post with full url and setting the body
127
+ # Foo.post('http://foo.com/resources', :body => {:bar => 'baz'})
128
+ #
129
+ # # Simple post with full url using :query option,
130
+ # # which gets set as form data on the request.
131
+ # Foo.post('http://foo.com/resources', :query => {:bar => 'baz'})
77
132
  def post(path, options={})
78
133
  perform_request Net::HTTP::Post, path, options
79
134
  end
@@ -85,6 +140,10 @@ module HTTParty
85
140
  def delete(path, options={})
86
141
  perform_request Net::HTTP::Delete, path, options
87
142
  end
143
+
144
+ def default_options #:nodoc:
145
+ @default_options
146
+ end
88
147
 
89
148
  private
90
149
  def perform_request(http_method, path, options) #:nodoc:
@@ -112,7 +171,7 @@ module HTTParty
112
171
  "http#{'s' if use_ssl}://#{url}"
113
172
  end
114
173
 
115
- class Basement
174
+ class Basement #:nodoc:
116
175
  include HTTParty
117
176
  end
118
177
 
@@ -133,7 +192,8 @@ module HTTParty
133
192
  end
134
193
  end
135
194
 
195
+ require 'httparty/cookie_hash'
136
196
  require 'httparty/exceptions'
137
197
  require 'httparty/request'
138
198
  require 'httparty/response'
139
- require 'httparty/cookie_hash'
199
+ require 'httparty/parsers'
@@ -1,4 +1,4 @@
1
- class HTTParty::CookieHash < Hash
1
+ class HTTParty::CookieHash < Hash #:nodoc:
2
2
  def add_cookies(hash)
3
3
  merge!(hash)
4
4
  end
@@ -1,4 +1,7 @@
1
1
  module HTTParty
2
+ # Exception raised when you attempt to set a non-existant format
2
3
  class UnsupportedFormat < StandardError; end
4
+
5
+ # Exception that is raised when request has redirected too many times
3
6
  class RedirectionTooDeep < StandardError; end
4
7
  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,67 @@
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(convert_json_to_yaml(json))
17
+ rescue ArgumentError => e
18
+ raise ParseError, "Invalid JSON string"
19
+ end
20
+
21
+ protected
22
+ # matches YAML-formatted dates
23
+ 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})?)?$/
24
+
25
+ # Ensure that ":" and "," are always followed by a space
26
+ def self.convert_json_to_yaml(json) #:nodoc:
27
+ scanner, quoting, marks, pos, times = StringScanner.new(json), false, [], nil, []
28
+ while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
29
+ case char = scanner[1]
30
+ when '"', "'"
31
+ if !quoting
32
+ quoting = char
33
+ pos = scanner.pos
34
+ elsif quoting == char
35
+ if json[pos..scanner.pos-2] =~ DATE_REGEX
36
+ # found a date, track the exact positions of the quotes so we can remove them later.
37
+ # oh, and increment them for each current mark, each one is an extra padded space that bumps
38
+ # the position in the final YAML output
39
+ total_marks = marks.size
40
+ times << pos+total_marks << scanner.pos+total_marks
41
+ end
42
+ quoting = false
43
+ end
44
+ when ":",","
45
+ marks << scanner.pos - 1 unless quoting
46
+ end
47
+ end
48
+
49
+ if marks.empty?
50
+ json.gsub(/\\\//, '/')
51
+ else
52
+ left_pos = [-1].push(*marks)
53
+ right_pos = marks << json.length
54
+ output = []
55
+ left_pos.each_with_index do |left, i|
56
+ output << json[left.succ..right_pos[i]]
57
+ end
58
+ output = output * " "
59
+
60
+ times.each { |i| output[i-1] = ' ' }
61
+ output.gsub!(/\\\//, '/')
62
+ output
63
+ end
64
+ end
65
+ end
66
+ end
67
+ 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.pop.to_hash
206
+ end
207
+ end
208
+ end
209
+ end