francxk-httparty 0.1.3

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.
@@ -0,0 +1,23 @@
1
+
2
+ == 0.1.3 2008-08-22
3
+
4
+ * 3 major enhancements:
5
+ * Added http_proxy key for setting proxy server and port (francxk@gmail.com)
6
+ * Now raises exception when http error occurs (francxk@gmail.com)
7
+ * Changed auto format detection from file extension to response content type (Jay Pignata)
8
+
9
+ == 0.1.2 2008-08-09
10
+
11
+ * 1 major enhancement:
12
+ * default_params were not being appended to query string if option[:query] was blank
13
+
14
+ == 0.1.1 2008-07-30
15
+
16
+ * 2 major enhancement:
17
+ * Added :basic_auth key for options when making a request
18
+ * :query and :body both now work with query string or hash
19
+
20
+ == 0.1.0 2008-07-27
21
+
22
+ * 1 major enhancement:
23
+ * Initial release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ PostInstall.txt
5
+ README.txt
6
+ Rakefile
7
+ config/hoe.rb
8
+ config/requirements.rb
9
+ examples/aaws.rb
10
+ examples/delicious.rb
11
+ examples/twitter.rb
12
+ examples/whoismyrep.rb
13
+ httparty.gemspec
14
+ lib/httparty.rb
15
+ lib/httparty/core_ext.rb
16
+ lib/httparty/core_ext/hash.rb
17
+ lib/httparty/version.rb
18
+ script/console
19
+ script/destroy
20
+ script/generate
21
+ script/txt2html
22
+ setup.rb
23
+ spec/hash_spec.rb
24
+ spec/httparty_spec.rb
25
+ spec/spec.opts
26
+ spec/spec_helper.rb
27
+ tasks/deployment.rake
28
+ tasks/environment.rake
29
+ tasks/website.rake
30
+ website/css/common.css
31
+ website/index.html
@@ -0,0 +1 @@
1
+ When you HTTParty, you must party hard!
@@ -0,0 +1,52 @@
1
+ = httparty
2
+
3
+ == DESCRIPTION:
4
+
5
+ Makes http fun again!
6
+
7
+ == FEATURES/PROBLEMS:
8
+
9
+ * Easy get, post, put, delete requests
10
+ * Basic http authentication
11
+ * Default request query string parameters (ie: for api keys that are needed on each request)
12
+ * Automatic parsing of JSON and XML into ruby hashes based on response content-type
13
+
14
+ == SYNOPSIS:
15
+
16
+ The following is a simple example of wrapping Twitter's API for posting updates.
17
+
18
+ class Twitter
19
+ include HTTParty
20
+ base_uri 'twitter.com'
21
+ basic_auth 'username', 'password'
22
+ end
23
+
24
+ Twitter.post('/statuses/update.json', :query => {:status => "It's an HTTParty and everyone is invited!"})
25
+
26
+ That is really it! The object returned is a ruby hash that is decoded from Twitter's json response. JSON parsing is used because of the .json extension in the path of the request. You can also explicitly set a format (see the examples).
27
+
28
+ That works and all but what if you don't want to embed your username and password in the class? Below is an example to fix that:
29
+
30
+ class Twitter
31
+ include HTTParty
32
+ base_uri 'twitter.com'
33
+
34
+ def initialize(u, p)
35
+ @auth = {:username => u, :password => p}
36
+ end
37
+
38
+ def post(text)
39
+ options = { :query => {:status => text}, :basic_auth => @auth }
40
+ self.class.post('/statuses/update.json', options)
41
+ end
42
+ end
43
+
44
+ Twitter.new('username', 'password').post("It's an HTTParty and everyone is invited!")
45
+
46
+ == REQUIREMENTS:
47
+
48
+ * Active Support >= 2.1
49
+
50
+ == INSTALL:
51
+
52
+ * sudo gem install httparty
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,73 @@
1
+ require 'httparty/version'
2
+
3
+ AUTHOR = 'John Nunemaker' # can also be an array of Authors
4
+ EMAIL = "nunemaker@gmail.com"
5
+ DESCRIPTION = "Makes http fun! Also, makes consuming restful web services dead easy."
6
+ GEM_NAME = 'httparty' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'httparty' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+ EXTRA_DEPENDENCIES = [
11
+ ['activesupport', '>= 2.1']
12
+ ] # An array of rubygem dependencies [name, version]
13
+
14
+ @config_file = "~/.rubyforge/user-config.yml"
15
+ @config = nil
16
+ RUBYFORGE_USERNAME = "unknown"
17
+ def rubyforge_username
18
+ unless @config
19
+ begin
20
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
21
+ rescue
22
+ puts <<-EOS
23
+ ERROR: No rubyforge config file found: #{@config_file}
24
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
25
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
26
+ EOS
27
+ exit
28
+ end
29
+ end
30
+ RUBYFORGE_USERNAME.replace @config["username"]
31
+ end
32
+
33
+
34
+ REV = nil
35
+ # UNCOMMENT IF REQUIRED:
36
+ # REV = YAML.load(`svn info`)['Revision']
37
+ VERS = HTTParty::VERSION::STRING + (REV ? ".#{REV}" : "")
38
+ RDOC_OPTS = ['--quiet', '--title', 'httparty documentation',
39
+ "--opname", "index.html",
40
+ "--line-numbers",
41
+ "--main", "README",
42
+ "--inline-source"]
43
+
44
+ class Hoe
45
+ def extra_deps
46
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
47
+ @extra_deps
48
+ end
49
+ end
50
+
51
+ # Generate all the Rake tasks
52
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
53
+ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
54
+ p.developer(AUTHOR, EMAIL)
55
+ p.description = DESCRIPTION
56
+ p.summary = DESCRIPTION
57
+ p.url = HOMEPATH
58
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
59
+ p.test_globs = ["test/**/test_*.rb"]
60
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
61
+
62
+ # == Optional
63
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
64
+ p.extra_deps = EXTRA_DEPENDENCIES
65
+
66
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
67
+ end
68
+
69
+ CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
70
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
71
+ $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
72
+ $hoe.rsync_args = '-av --delete --ignore-errors'
73
+ $hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
@@ -0,0 +1,29 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+ require 'pp'
4
+ config = YAML::load(File.read(File.join(ENV['HOME'], '.aaws')))
5
+
6
+ module AAWS
7
+ class Book
8
+ include HTTParty
9
+ base_uri 'http://ecs.amazonaws.com'
10
+ default_params :Service => 'AWSECommerceService', :Operation => 'ItemSearch', :SearchIndex => 'Books'
11
+
12
+ def initialize(key)
13
+ self.class.default_params :AWSAccessKeyId => key
14
+ end
15
+
16
+ def search(options={})
17
+ raise ArgumentError, 'You must search for something' if options[:query].blank?
18
+
19
+ # amazon uses nasty camelized query params
20
+ options[:query] = options[:query].inject({}) { |h, q| h[q[0].to_s.camelize] = q[1]; h }
21
+
22
+ # make a request and return the items (NOTE: this doesn't handle errors at this point)
23
+ self.class.get('/onca/xml', options)['ItemSearchResponse']['Items']
24
+ end
25
+ end
26
+ end
27
+
28
+ aaws = AAWS::Book.new(config[:access_key])
29
+ pp aaws.search(:query => {:title => 'Ruby On Rails'})
@@ -0,0 +1,37 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+ require 'pp'
4
+ config = YAML::load(File.read(File.join(ENV['HOME'], '.delicious')))
5
+
6
+ class Delicious
7
+ include HTTParty
8
+ base_uri 'https://api.del.icio.us/v1'
9
+
10
+ def initialize(u, p)
11
+ @auth = {:username => u, :password => p}
12
+ end
13
+
14
+ # query params that filter the posts are:
15
+ # tag (optional). Filter by this tag.
16
+ # dt (optional). Filter by this date (CCYY-MM-DDThh:mm:ssZ).
17
+ # url (optional). Filter by this url.
18
+ # ie: posts(:query => {:tag => 'ruby'})
19
+ def posts(options={})
20
+ options.merge!({:basic_auth => @auth})
21
+ # get posts and convert to structs so we can do .key instead of ['key'] with results
22
+ self.class.get('/posts/get', options)
23
+ end
24
+
25
+ # query params that filter the posts are:
26
+ # tag (optional). Filter by this tag.
27
+ # count (optional). Number of items to retrieve (Default:15, Maximum:100).
28
+ def recent(options={})
29
+ options.merge!({:basic_auth => @auth})
30
+ self.class.get('/posts/recent', options)
31
+ end
32
+ end
33
+
34
+ delicious = Delicious.new(config['username'], config['password'])
35
+ pp delicious.posts(:query => {:tag => 'ruby'})
36
+ pp delicious.recent
37
+
@@ -0,0 +1,31 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+ require 'pp'
4
+ config = YAML::load(File.read(File.join(ENV['HOME'], '.twitter')))
5
+
6
+ class Twitter
7
+ include HTTParty
8
+ base_uri 'twitter.com'
9
+
10
+ def initialize(u, p)
11
+ @auth = {:username => u, :password => p}
12
+ end
13
+
14
+ # which can be :friends, :user or :public
15
+ # options[:query] can be things like since, since_id, count, etc.
16
+ def timeline(which=:friends, options={})
17
+ options.merge!({:basic_auth => @auth})
18
+ self.class.get("/statuses/#{which}_timeline.json", options)
19
+ end
20
+
21
+ def post(text)
22
+ options = { :query => {:status => text}, :basic_auth => @auth }
23
+ self.class.post('/statuses/update.json', options)
24
+ end
25
+ end
26
+
27
+ twitter = Twitter.new(config['email'], config['password'])
28
+ pp twitter.timeline
29
+ # pp twitter.timeline(:friends, :query => {:since_id => 868482746})
30
+ # pp twitter.timeline(:friends, :query => 'since_id=868482746')
31
+ # pp twitter.post('this is a test')
@@ -0,0 +1,9 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+ require 'pp'
4
+
5
+ class Rep
6
+ include HTTParty
7
+ end
8
+
9
+ puts Rep.get('http://whoismyrepresentative.com/whoismyrep.php?zip=46544').inspect
@@ -0,0 +1,33 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{httparty}
3
+ s.version = "0.1.3"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["John Nunemaker"]
7
+ s.date = %q{2008-08-22}
8
+ s.description = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
9
+ s.email = ["nunemaker@gmail.com"]
10
+ s.extra_rdoc_files = ["History.txt", "License.txt", "Manifest.txt", "PostInstall.txt", "README.txt"]
11
+ s.files = ["History.txt", "License.txt", "Manifest.txt", "PostInstall.txt", "README.txt", "Rakefile", "config/hoe.rb", "config/requirements.rb", "examples/aaws.rb", "examples/delicious.rb", "examples/twitter.rb", "examples/whoismyrep.rb", "httparty.gemspec", "lib/httparty.rb", "lib/httparty/core_ext.rb", "lib/httparty/core_ext/hash.rb", "lib/httparty/version.rb", "script/console", "script/destroy", "script/generate", "script/txt2html", "setup.rb", "spec/hash_spec.rb", "spec/httparty_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/deployment.rake", "tasks/environment.rake", "tasks/website.rake", "website/css/common.css", "website/index.html"]
12
+ s.has_rdoc = true
13
+ s.homepage = %q{http://httparty.rubyforge.org}
14
+ s.post_install_message = %q{When you HTTParty, you must party hard!}
15
+ s.rdoc_options = ["--main", "README.txt"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{httparty}
18
+ s.rubygems_version = %q{1.2.0}
19
+ s.summary = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 2
24
+
25
+ if current_version >= 3 then
26
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.1"])
27
+ else
28
+ s.add_dependency(%q<activesupport>, [">= 2.1"])
29
+ end
30
+ else
31
+ s.add_dependency(%q<activesupport>, [">= 2.1"])
32
+ end
33
+ end
@@ -0,0 +1,165 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'ostruct'
5
+ require 'rubygems'
6
+ require 'active_support'
7
+
8
+ $:.unshift(File.dirname(__FILE__)) unless
9
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
10
+
11
+ dir = File.expand_path(File.join(File.dirname(__FILE__), 'httparty'))
12
+ require dir + '/core_ext'
13
+
14
+ module HTTParty
15
+ class UnsupportedFormat < StandardError; end
16
+
17
+ def self.included(base)
18
+ base.extend ClassMethods
19
+ end
20
+
21
+ AllowedFormats = {:xml => 'text/xml', :json => 'application/json'}
22
+
23
+ module ClassMethods
24
+ #
25
+ # Set an http proxy
26
+ #
27
+ # class Twitter
28
+ # include HTTParty
29
+ # http_proxy http://myProxy, 1080
30
+ # ....
31
+ def http_proxy(addr=nil, port = nil)
32
+ @http_proxyaddr = addr
33
+ @http_proxyport = port
34
+ end
35
+
36
+ def base_uri(base_uri=nil)
37
+ return @base_uri unless base_uri
38
+ # don't want this to ever end with /
39
+ base_uri = base_uri.ends_with?('/') ? base_uri.chop : base_uri
40
+ @base_uri = normalize_base_uri(base_uri)
41
+ end
42
+
43
+ # Warning: This is not thread safe most likely and
44
+ # only works if you use one set of credentials. I
45
+ # leave it because it is convenient on some occasions.
46
+ def basic_auth(u, p)
47
+ @auth = {:username => u, :password => p}
48
+ end
49
+
50
+ # Updates the default query string parameters
51
+ # that should be appended to each request.
52
+ def default_params(h={})
53
+ raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
54
+ @default_params ||= {}
55
+ return @default_params if h.blank?
56
+ @default_params.merge!(h)
57
+ end
58
+
59
+ def headers(h={})
60
+ raise ArgumentError, 'Headers must be a hash' unless h.is_a?(Hash)
61
+ @headers ||= {}
62
+ return @headers if h.blank?
63
+ @headers.merge!(h)
64
+ end
65
+
66
+ def format(f)
67
+ raise UnsupportedFormat, "Must be one of: #{AllowedFormats.keys.join(', ')}" unless AllowedFormats.key?(f)
68
+ @format = f
69
+ end
70
+
71
+ # TODO: spec out this
72
+ def get(path, options={})
73
+ send_request 'get', path, options
74
+ end
75
+
76
+ # TODO: spec out this
77
+ def post(path, options={})
78
+ send_request 'post', path, options
79
+ end
80
+
81
+ # TODO: spec out this
82
+ def put(path, options={})
83
+ send_request 'put', path, options
84
+ end
85
+
86
+ # TODO: spec out this
87
+ def delete(path, options={})
88
+ send_request 'delete', path, options
89
+ end
90
+
91
+ private
92
+ def http(uri) #:nodoc:
93
+ if @http.blank?
94
+ @http = Net::HTTP.new(uri.host, uri.port, @http_proxyaddr, @http_proxyport)
95
+ @http.use_ssl = (uri.port == 443)
96
+ # so we can avoid ssl warnings
97
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
98
+ end
99
+ @http
100
+ end
101
+
102
+ # FIXME: this method is doing way to much and needs to be split up
103
+ # options can be any or all of:
104
+ # query => hash of keys/values or a query string (foo=bar&baz=poo)
105
+ # body => hash of keys/values or a query string (foo=bar&baz=poo)
106
+ # headers => hash of headers to send request with
107
+ # basic_auth => :username and :password to use as basic http authentication (overrides @auth class instance variable)
108
+ # Raises exception Net::XXX (http error code) if an http error occured
109
+ def send_request(method, path, options={}) #:nodoc:
110
+ raise ArgumentError, 'only get, post, put and delete methods are supported' unless %w[get post put delete].include?(method.to_s)
111
+ raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
112
+ raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
113
+ uri = URI.parse("#{base_uri}#{path}")
114
+ existing_query = uri.query ? "#{uri.query}&" : ''
115
+ uri.query = if options[:query].blank?
116
+ existing_query + default_params.to_query
117
+ else
118
+ existing_query + (options[:query].is_a?(Hash) ? default_params.merge(options[:query]).to_query : options[:query])
119
+ end
120
+ klass = Net::HTTP.const_get method.to_s.downcase.capitalize
121
+ request = klass.new(uri.request_uri)
122
+ request.body = options[:body].is_a?(Hash) ? options[:body].to_query : options[:body] unless options[:body].blank?
123
+ basic_auth = options.delete(:basic_auth) || @auth
124
+ request.initialize_http_header headers.merge(options[:headers] || {})
125
+ # note to self: self, do not put basic auth above headers because it removes basic auth
126
+ request.basic_auth(basic_auth[:username], basic_auth[:password]) if basic_auth
127
+ response = http(uri).request(request)
128
+
129
+ @format ||= format_from_mimetype(response['content-type'])
130
+
131
+ case response
132
+ when Net::HTTPSuccess
133
+ parse_response(response.body)
134
+ else
135
+ response.instance_eval { class << self; attr_accessor :body_parsed; end }
136
+ begin; response.body_parsed = parse_response(response.body); rescue; end
137
+ response.error! # raises exception corresponding to http error Net::XXX
138
+ end
139
+
140
+ end
141
+
142
+ def parse_response(body) #:nodoc:
143
+ case @format
144
+ when :xml
145
+ Hash.from_xml(body)
146
+ when :json
147
+ ActiveSupport::JSON.decode(body)
148
+ else
149
+ # just return the response if no format
150
+ body
151
+ end
152
+ end
153
+
154
+ # Makes it so uri is sure to parse stuff like google.com with the http
155
+ def normalize_base_uri(str) #:nodoc:
156
+ str =~ /^https?:\/\// ? str : "http#{'s' if str.include?(':443')}://#{str}"
157
+ end
158
+
159
+ # Uses the HTTP Content-Type header to determine the format of the response
160
+ # It compares the MIME type returned to the types stored in the AllowedFormats hash
161
+ def format_from_mimetype(mimetype) #:nodoc:
162
+ AllowedFormats.each { |k, v| return k if mimetype.include?(v) }
163
+ end
164
+ end
165
+ end