thounds 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.yardopts +9 -0
- data/Gemfile +3 -0
- data/HISTORY.mkd +3 -0
- data/LICENSE.mkd +20 -0
- data/README.mkd +81 -0
- data/Rakefile +22 -0
- data/lib/faraday/multipart.rb +30 -0
- data/lib/faraday/oauth.rb +22 -0
- data/lib/faraday/raise_http_4xx.rb +50 -0
- data/lib/faraday/raise_http_5xx.rb +31 -0
- data/lib/thounds.rb +26 -0
- data/lib/thounds/api.rb +23 -0
- data/lib/thounds/authentication.rb +25 -0
- data/lib/thounds/client.rb +46 -0
- data/lib/thounds/client/proxy.rb +46 -0
- data/lib/thounds/configuration.rb +93 -0
- data/lib/thounds/connection.rb +33 -0
- data/lib/thounds/error.rb +28 -0
- data/lib/thounds/request.rb +24 -0
- data/lib/thounds/version.rb +4 -0
- data/spec/faraday/response_spec.rb +31 -0
- data/spec/fixtures/user_31.js +1 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/thounds/api_spec.rb +68 -0
- data/spec/thounds/client_spec.rb +10 -0
- data/spec/thounds_spec.rb +358 -0
- data/test.rb +78 -0
- data/thounds.gemspec +44 -0
- metadata +356 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/HISTORY.mkd
ADDED
data/LICENSE.mkd
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Giovanni Cappellotto
|
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.
|
data/README.mkd
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#The Thounds Ruby Gem
|
2
|
+
|
3
|
+
A Ruby wrapper for the Thounds REST API
|
4
|
+
|
5
|
+
##Documentation
|
6
|
+
|
7
|
+
The full Thounds API reference could be found at [http://developers.thounds.com/API](http://developers.thounds.com/API)
|
8
|
+
|
9
|
+
##Installation
|
10
|
+
|
11
|
+
gem install thounds
|
12
|
+
|
13
|
+
##Follow @thounds on Twitter
|
14
|
+
|
15
|
+
You should [follow @thounds on Twitter](http://twitter.com/thounds) for announcements,
|
16
|
+
updates, and news about the thounds gem.
|
17
|
+
|
18
|
+
##Usage Examples
|
19
|
+
|
20
|
+
require "rubygems"
|
21
|
+
require "thounds"
|
22
|
+
|
23
|
+
# Certain methods require authentication. To get your Thounds OAuth credentials,
|
24
|
+
# register an app at http://thounds.com/oauth_clients/new
|
25
|
+
Thounds.configure do |config|
|
26
|
+
config.consumer_key = YOUR_CONSUMER_KEY
|
27
|
+
config.consumer_secret = YOUR_CONSUMER_SECRET
|
28
|
+
config.oauth_token = YOUR_OAUTH_TOKEN
|
29
|
+
config.oauth_token_secret = YOUR_OAUTH_TOKEN_SECRET
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get informations about your profile
|
33
|
+
Thounds.profile do |me|
|
34
|
+
puts me.name
|
35
|
+
end
|
36
|
+
|
37
|
+
##Contributing
|
38
|
+
|
39
|
+
In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html), **everyone** is encouraged to help improve this project.
|
40
|
+
|
41
|
+
Here are some ways *you* can contribute:
|
42
|
+
|
43
|
+
* by using alpha, beta, and prerelease versions
|
44
|
+
* by reporting bugs
|
45
|
+
* by suggesting new features
|
46
|
+
* by writing or editing documentation
|
47
|
+
* by writing specifications
|
48
|
+
* by writing code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace)
|
49
|
+
* by refactoring code
|
50
|
+
* by closing [issues](http://github.com/potomak/thounds/issues)
|
51
|
+
* by reviewing patches
|
52
|
+
<!-- * [financially](http://pledgie.com/campaigns/TODO) -->
|
53
|
+
|
54
|
+
All contributors will be added to the [HISTORY](https://github.com/potomak/thounds/blob/master/HISTORY.mkd)
|
55
|
+
file and will receive the respect and gratitude of the community.
|
56
|
+
|
57
|
+
##Submitting an Issue
|
58
|
+
|
59
|
+
We use the [GitHub issue tracker](http://github.com/potomak/thounds/issues) to track bugs and
|
60
|
+
features. Before submitting a bug report or feature request, check to make sure it hasn't already
|
61
|
+
been submitted. You can indicate support for an existing issuse by voting it up. When submitting a
|
62
|
+
bug report, please include a [Gist](http://gist.github.com/) that includes a stack trace and any
|
63
|
+
details that may be necessary to reproduce the bug, including your gem version, Ruby version, and
|
64
|
+
operating system. Ideally, a bug report should include a pull request with failing specs.
|
65
|
+
|
66
|
+
##Submitting a Pull Request
|
67
|
+
|
68
|
+
1. Fork the project.
|
69
|
+
2. Create a topic branch.
|
70
|
+
3. Implement your feature or bug fix.
|
71
|
+
4. Add documentation for your feature or bug fix.
|
72
|
+
5. Run `bundle exec rake doc:yard`. If your changes are not 100% documented, go back to step 4.
|
73
|
+
6. Add specs for your feature or bug fix.
|
74
|
+
7. Run `bundle exec rake spec`. If your changes are not 100% covered, go back to step 6.
|
75
|
+
8. Commit and push your changes.
|
76
|
+
9. Submit a pull request. Please do not include changes to the gemspec, version, or history file. (If you want to create your own version for some reason, please do so in a separate commit.)
|
77
|
+
|
78
|
+
##Copyright
|
79
|
+
|
80
|
+
Copyright (c) 2010 Giovanni Cappellotto.
|
81
|
+
See [LICENSE](https://github.com/potomak/thounds/blob/master/LICENSE.mkd) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
namespace :doc do
|
10
|
+
require 'yard'
|
11
|
+
YARD::Rake::YardocTask.new do |task|
|
12
|
+
task.files = ['HISTORY.mkd', 'LICENSE.mkd', 'lib/**/*.rb']
|
13
|
+
task.options = [
|
14
|
+
'--protected',
|
15
|
+
'--output-dir', 'doc/yard',
|
16
|
+
'--tag', 'format:Supported formats',
|
17
|
+
'--tag', 'authenticated:Requires Authentication',
|
18
|
+
'--tag', 'rate_limited:Rate Limited',
|
19
|
+
'--markup', 'markdown',
|
20
|
+
]
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
# @private
|
4
|
+
module Faraday
|
5
|
+
# @private
|
6
|
+
class Request::Multipart < Faraday::Middleware
|
7
|
+
def call(env)
|
8
|
+
if env[:body].is_a?(Hash)
|
9
|
+
env[:body].each do |key, value|
|
10
|
+
if value.is_a?(File)
|
11
|
+
env[:body][key] = Faraday::UploadIO.new(value, mime_type(value), value.path)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def mime_type(file)
|
22
|
+
case file.path
|
23
|
+
when /\.jpe?g/i then 'image/jpeg'
|
24
|
+
when /\.gif$/i then 'image/gif'
|
25
|
+
when /\.png$/i then 'image/png'
|
26
|
+
else 'application/octet-stream'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'simple_oauth'
|
3
|
+
|
4
|
+
# @private
|
5
|
+
module Faraday
|
6
|
+
# @private
|
7
|
+
class Request::OAuth < Faraday::Middleware
|
8
|
+
def call(env)
|
9
|
+
params = env[:body].is_a?(Hash) ? env[:body] : {}
|
10
|
+
signature_params = params.reject{|k,v| v.respond_to?(:content_type) }
|
11
|
+
header = SimpleOAuth::Header.new(env[:method], env[:url], signature_params, @options)
|
12
|
+
|
13
|
+
env[:request_headers]['Authorization'] = header.to_s
|
14
|
+
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(app, options)
|
19
|
+
@app, @options = app, options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
# @private
|
4
|
+
module Faraday
|
5
|
+
# @private
|
6
|
+
class Response::RaiseHttp4xx < Response::Middleware
|
7
|
+
def self.register_on_complete(env)
|
8
|
+
env[:response].on_complete do |response|
|
9
|
+
case response[:status].to_i
|
10
|
+
when 400
|
11
|
+
raise Thounds::BadRequest, error_message(response)
|
12
|
+
when 401
|
13
|
+
raise Thounds::Unauthorized, error_message(response)
|
14
|
+
when 403
|
15
|
+
raise Thounds::Forbidden, error_message(response)
|
16
|
+
when 404
|
17
|
+
raise Thounds::NotFound, error_message(response)
|
18
|
+
when 406
|
19
|
+
raise Thounds::NotAcceptable, error_message(response)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(app)
|
25
|
+
super
|
26
|
+
@parser = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def self.error_message(response)
|
32
|
+
"#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]}#{error_body(response[:body])}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.error_body(body)
|
36
|
+
if body.nil?
|
37
|
+
nil
|
38
|
+
elsif body['error']
|
39
|
+
": #{body['error']}"
|
40
|
+
elsif body['errors']
|
41
|
+
first = body['errors'].to_a.first
|
42
|
+
if first.kind_of? Hash
|
43
|
+
": #{first['message'].chomp}"
|
44
|
+
else
|
45
|
+
": #{first.chomp}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
# @private
|
4
|
+
module Faraday
|
5
|
+
# @private
|
6
|
+
class Response::RaiseHttp5xx < Response::Middleware
|
7
|
+
def self.register_on_complete(env)
|
8
|
+
env[:response].on_complete do |response|
|
9
|
+
case response[:status].to_i
|
10
|
+
when 500
|
11
|
+
raise Thounds::InternalServerError, error_message(response, "Something is technically wrong.")
|
12
|
+
when 502
|
13
|
+
raise Thounds::BadGateway, error_message(response, "Thounds is down or being upgraded.")
|
14
|
+
when 503
|
15
|
+
raise Thounds::ServiceUnavailable, error_message(response, "(__-){ Thounds is over capacity.")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(app)
|
21
|
+
super
|
22
|
+
@parser = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.error_message(response, body=nil)
|
28
|
+
"#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', body].compact.join(' ')} Check http://status.thounds.com/ for updates on the status of the Thounds service."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/thounds.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path('../thounds/error', __FILE__)
|
2
|
+
require File.expand_path('../thounds/configuration', __FILE__)
|
3
|
+
require File.expand_path('../thounds/api', __FILE__)
|
4
|
+
require File.expand_path('../thounds/client', __FILE__)
|
5
|
+
|
6
|
+
module Thounds
|
7
|
+
extend Configuration
|
8
|
+
|
9
|
+
# Alias for Thounds::Client.new
|
10
|
+
#
|
11
|
+
# @return [Thounds::Client]
|
12
|
+
def self.client(options={})
|
13
|
+
Thounds::Client.new(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Delegate to Thounds::Client
|
17
|
+
def self.method_missing(method, *args, &block)
|
18
|
+
#return super unless client.respond_to?(method)
|
19
|
+
client.send(method, *args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Delegate to Thounds::Client
|
23
|
+
# def self.respond_to?(method)
|
24
|
+
# return client.respond_to?(method) || super
|
25
|
+
# end
|
26
|
+
end
|
data/lib/thounds/api.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path('../connection', __FILE__)
|
2
|
+
require File.expand_path('../request', __FILE__)
|
3
|
+
require File.expand_path('../authentication', __FILE__)
|
4
|
+
|
5
|
+
module Thounds
|
6
|
+
# @private
|
7
|
+
class API
|
8
|
+
# @private
|
9
|
+
attr_accessor *Configuration::VALID_OPTIONS_KEYS
|
10
|
+
|
11
|
+
# Creates a new API
|
12
|
+
def initialize(options={})
|
13
|
+
options = Thounds.options.merge(options)
|
14
|
+
Configuration::VALID_OPTIONS_KEYS.each do |key|
|
15
|
+
send("#{key}=", options[key])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
include Connection
|
20
|
+
include Request
|
21
|
+
include Authentication
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Thounds
|
2
|
+
# @private
|
3
|
+
module Authentication
|
4
|
+
private
|
5
|
+
|
6
|
+
# Authentication hash
|
7
|
+
#
|
8
|
+
# @return [Hash]
|
9
|
+
def authentication
|
10
|
+
{
|
11
|
+
:consumer_key => consumer_key,
|
12
|
+
:consumer_secret => consumer_secret,
|
13
|
+
:token => oauth_token,
|
14
|
+
:token_secret => oauth_token_secret
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check whether user is authenticated
|
19
|
+
#
|
20
|
+
# @return [Boolean]
|
21
|
+
def authenticated?
|
22
|
+
authentication.values.all?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Thounds
|
2
|
+
# Wrapper for the Thounds REST API
|
3
|
+
#
|
4
|
+
# @note See the {http://developers.thounds.com/doc Thounds API Documentation} for more informations.
|
5
|
+
# @see http://developers.thounds.com
|
6
|
+
class Client < API
|
7
|
+
# Require client method modules after initializing the Client class in
|
8
|
+
# order to avoid a superclass mismatch error, allowing those modules to be
|
9
|
+
# Client-namespaced.
|
10
|
+
Dir[File.expand_path('../client/*.rb', __FILE__)].each{|f| require f}
|
11
|
+
|
12
|
+
alias :api_endpoint :endpoint
|
13
|
+
|
14
|
+
attr_reader :proxy
|
15
|
+
|
16
|
+
def initialize(options={})
|
17
|
+
super(options)
|
18
|
+
@proxy = Proxy.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Delegate to Thounds::Client
|
22
|
+
def method_missing(method, *args, &block)
|
23
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
24
|
+
id = args.last ? args.pop : nil
|
25
|
+
|
26
|
+
# puts "method: #{method}"
|
27
|
+
# puts "options: #{options.inspect}"
|
28
|
+
# puts "id: #{id}"
|
29
|
+
|
30
|
+
@proxy.append(method, id, options)
|
31
|
+
|
32
|
+
if block_given?
|
33
|
+
@proxy.compose_request
|
34
|
+
yield send(:request, @proxy.verb, @proxy.path, @proxy.options)
|
35
|
+
end
|
36
|
+
|
37
|
+
self
|
38
|
+
|
39
|
+
# begin
|
40
|
+
# send(verb, path, options)
|
41
|
+
# rescue Yajl::ParseError => e
|
42
|
+
# puts "error: #{e}"
|
43
|
+
# end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Thounds
|
3
|
+
class Client
|
4
|
+
class Proxy
|
5
|
+
attr_reader :options, :verb, :path
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@verb = :get
|
9
|
+
@keys = []
|
10
|
+
@ids = {}
|
11
|
+
@options = {}
|
12
|
+
@path = ""
|
13
|
+
end
|
14
|
+
|
15
|
+
def append(key, id=nil, options={})
|
16
|
+
@verb = key.to_sym if ["get", "post", "put", "delete"].include? key.to_s
|
17
|
+
@keys << key unless ["get", "post", "put", "delete"].include? key.to_s
|
18
|
+
@ids[key.to_sym] = id if id
|
19
|
+
@options = @options.merge(options) if options
|
20
|
+
end
|
21
|
+
|
22
|
+
def compose_request
|
23
|
+
# puts "@keys: #{@keys.inspect}"
|
24
|
+
# puts "@ids: #{@ids.inspect}"
|
25
|
+
# puts "@options: #{@options.inspect}"
|
26
|
+
|
27
|
+
# compose request path
|
28
|
+
@path = @keys.collect do |key|
|
29
|
+
if id = @ids.delete(key.to_sym)
|
30
|
+
"#{key}/#{id}"
|
31
|
+
else
|
32
|
+
key
|
33
|
+
end
|
34
|
+
end.join("/")
|
35
|
+
|
36
|
+
# puts to_s
|
37
|
+
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
"#{@verb.to_s.upcase} /#{@path} OPTIONS: #{@options.inspect}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|