mwunsch-weary 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +29 -4
- data/Rakefile +5 -0
- data/VERSION +1 -1
- data/lib/weary.rb +41 -0
- data/lib/weary/request.rb +3 -0
- data/lib/weary/resource.rb +31 -4
- data/lib/weary/response.rb +7 -1
- data/spec/weary/request_spec.rb +8 -0
- data/spec/weary/resource_spec.rb +27 -0
- data/spec/weary_spec.rb +33 -0
- data/weary.gemspec +2 -2
- metadata +2 -2
data/README.md
CHANGED
@@ -8,14 +8,18 @@ The things it do:
|
|
8
8
|
|
9
9
|
+ Quickly build an interface to your favorite REST API.
|
10
10
|
+ Parse XML and JSON with the [Crack](http://github.com/jnunemaker/crack) library.
|
11
|
+
+ Authenticate, basically, with Basic Authentication.
|
12
|
+
+ Consume with [OAuth](http://oauth.net/)
|
11
13
|
|
12
14
|
Browse the documentation here: [http://rdoc.info/projects/mwunsch/weary](http://rdoc.info/projects/mwunsch/weary)
|
15
|
+
Peruse the [Wiki](http://wiki.github.com/mwunsch/weary) to discover libraries built with Weary and a more thorough review of the API.
|
13
16
|
|
14
17
|
## Requirements
|
15
18
|
|
16
|
-
+ Crack >= 0.1.2
|
17
|
-
+ Nokogiri >= 1.3.1 (if you want to use the #search method)
|
18
|
-
+
|
19
|
+
+ [Crack](http://github.com/jnunemaker/crack) >= 0.1.2
|
20
|
+
+ [Nokogiri](http://github.com/tenderlove/nokogiri) >= 1.3.1 (if you want to use the #search method)
|
21
|
+
+ [OAuth](http://github.com/mojodna/oauth) >= 0.3.5 (if you want to use OAuth)
|
22
|
+
+ [RSpec](http://rspec.info/) (for running the tests)
|
19
23
|
|
20
24
|
## Installation
|
21
25
|
|
@@ -66,6 +70,8 @@ Besides the name of the resource, you can also give `declare_resource` a block l
|
|
66
70
|
r.requires = [:id, :bar] # an array of params that the resource requires to be in the query/body
|
67
71
|
r.with = [:blah] # an array of params that you can optionally send to the resource
|
68
72
|
r.authenticates = false # does the method require basic authentication? defaults to false
|
73
|
+
r.oauth = false # does this resource use OAuth to authorize you? it's boolean
|
74
|
+
r.access_token = nil # if you're using OAuth, you should provide the user's access token.
|
69
75
|
r.follows = false # if this is set to false, the formed request will not follow redirects.
|
70
76
|
r.headers = {'Accept' => 'text/html'} # send custom headers. defaults to nil.
|
71
77
|
end
|
@@ -116,4 +122,23 @@ There are many ways to form URLs in Weary. You can define URLs for the entire cl
|
|
116
122
|
The string `<domain><resource>.<format>` helps define a simple pattern for creating URLs. These will be filled in by your resource declaration. The above `get` declaration creates a url that looks like: *http://foo.bar/show_users.xml*
|
117
123
|
|
118
124
|
If you use the `<domain>` flag but don't define a domain, an exception will be raised.
|
119
|
-
|
125
|
+
|
126
|
+
### Weary DSL
|
127
|
+
|
128
|
+
You can create some defaults for all of our resources easily:
|
129
|
+
|
130
|
+
class Foo
|
131
|
+
extend Weary
|
132
|
+
|
133
|
+
domain "http://foo.bar/"
|
134
|
+
url "<domain><resource>.<format>"
|
135
|
+
format :xml
|
136
|
+
headers {'Accept' => 'text/html'} # set headers
|
137
|
+
authenticates "basic_username","basic_password" # basic authentication
|
138
|
+
with [:login, :token] # params that should be sent with every request
|
139
|
+
oauth OAuth::AccessToken.new(consumer, "token", "secret") # an access token for OAuth
|
140
|
+
|
141
|
+
post "update" # uses the defaults defined above!
|
142
|
+
end
|
143
|
+
|
144
|
+
There's more to discover in the Wiki.
|
data/Rakefile
CHANGED
@@ -54,6 +54,11 @@ rescue LoadError
|
|
54
54
|
puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
|
55
55
|
end
|
56
56
|
|
57
|
+
desc "Open an irb session preloaded with this library"
|
58
|
+
task :console do
|
59
|
+
sh "irb -rubygems -I lib -r weary.rb"
|
60
|
+
end
|
61
|
+
|
57
62
|
Spec::Rake::SpecTask.new do |t|
|
58
63
|
t.spec_files = FileList['spec/**/*_spec.rb']
|
59
64
|
t.spec_opts = ['--color','--format nested']
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/weary.rb
CHANGED
@@ -8,8 +8,10 @@ require 'rubygems'
|
|
8
8
|
require 'crack'
|
9
9
|
|
10
10
|
gem 'nokogiri'
|
11
|
+
gem 'oauth'
|
11
12
|
autoload :Yaml, 'yaml'
|
12
13
|
autoload :Nokogiri, 'nokogiri'
|
14
|
+
autoload :OAuth, 'oauth'
|
13
15
|
|
14
16
|
require 'weary/request'
|
15
17
|
require 'weary/response'
|
@@ -90,6 +92,14 @@ module Weary
|
|
90
92
|
@headers = headers
|
91
93
|
end
|
92
94
|
alias set_headers headers
|
95
|
+
|
96
|
+
# Set the Access Token for OAuth. This must be an OAuth::AccessToken object.
|
97
|
+
# See http://github.com/mojodna/oauth/ to learn how to create Tokens
|
98
|
+
# Setting this will make resources use OAuth and this token by default.
|
99
|
+
def oauth(token)
|
100
|
+
raise ArgumentError, "Token needs to be an OAuth::AccessToken object" unless token.is_a?(OAuth::AccessToken)
|
101
|
+
@oauth = token
|
102
|
+
end
|
93
103
|
|
94
104
|
# Declare a resource. Use it with a block to setup the resource
|
95
105
|
#
|
@@ -98,10 +108,13 @@ module Weary
|
|
98
108
|
# [<tt>with</tt>] An array of parameters that will be passed to the body or query of the request. If you pass a hash, it will define default <tt>values</tt> for params <tt>keys</tt>
|
99
109
|
# [<tt>requires</tt>] Array of members of <tt>:with</tt> that are required by the resource.
|
100
110
|
# [<tt>authenticates</tt>] Boolean value; does the resource require authentication?
|
111
|
+
# [<tt>oauth</tt>] Boolean value; does the resource use OAuth?
|
112
|
+
# [<tt>access_token</tt>] Provide the Token for OAuth. Must be an OAuth::AccessToken object.
|
101
113
|
# [<tt>url</tt>] The url of the resource. You can use the same flags as #construct_url
|
102
114
|
# [<tt>format</tt>] The format you would like to request. Defaults to json
|
103
115
|
# [<tt>follows</tt>] Boolean; Does this follow redirects? Defaults to true
|
104
116
|
# [<tt>domain</tt>] Sets the domain you would like this individual resource to be on (if you include the domain flag in <tt>url</tt>)
|
117
|
+
# [<tt>headers</tt>] Set headers for the HTTP Request
|
105
118
|
def declare(name)
|
106
119
|
resource = prepare_resource(name,:get)
|
107
120
|
yield resource if block_given?
|
@@ -141,6 +154,10 @@ module Weary
|
|
141
154
|
preparation.url = (@url_pattern || "<domain><resource>.<format>")
|
142
155
|
preparation.with = @always_with unless @always_with.nil?
|
143
156
|
preparation.headers = @headers unless (@headers.nil? || @headers.empty?)
|
157
|
+
if !@oauth.nil?
|
158
|
+
preparation.oauth = true
|
159
|
+
preparation.access_token = @oauth
|
160
|
+
end
|
144
161
|
return preparation
|
145
162
|
end
|
146
163
|
|
@@ -148,6 +165,12 @@ module Weary
|
|
148
165
|
if resource.authenticates?
|
149
166
|
raise StandardError, "Can not authenticate unless username and password are defined" unless (@username && @password)
|
150
167
|
end
|
168
|
+
if resource.oauth?
|
169
|
+
if resource.access_token.nil?
|
170
|
+
raise StandardError, "Access Token is not provided" if @oauth.nil?
|
171
|
+
resource.access_token = @oauth
|
172
|
+
end
|
173
|
+
end
|
151
174
|
@resources ||= []
|
152
175
|
@resources << resource.to_hash
|
153
176
|
craft_methods(resource)
|
@@ -214,6 +237,24 @@ module Weary
|
|
214
237
|
if resource.authenticates?
|
215
238
|
code << %Q{options[:basic_auth] = {:username => "#{@username}", :password => "#{@password}"} \n}
|
216
239
|
end
|
240
|
+
if resource.oauth?
|
241
|
+
consumer_options = ""
|
242
|
+
resource.access_token.consumer.options.each_pair {|k,v|
|
243
|
+
if k.is_a?(Symbol)
|
244
|
+
k_string = ":#{k}"
|
245
|
+
else
|
246
|
+
k_string = "'#{k}'"
|
247
|
+
end
|
248
|
+
if v.is_a?(Symbol)
|
249
|
+
v_string = ":#{v}"
|
250
|
+
else
|
251
|
+
v_string = "'#{v}'"
|
252
|
+
end
|
253
|
+
consumer_options << "#{k_string} => #{v_string},"
|
254
|
+
}
|
255
|
+
code << %Q{ oauth_consumer = OAuth::Consumer.new("#{resource.access_token.consumer.key}","#{resource.access_token.consumer.secret}",#{consumer_options.chop}) \n}
|
256
|
+
code << %Q{ options[:oauth] = OAuth::AccessToken.new(oauth_consumer, "#{resource.access_token.token}", "#{resource.access_token.secret}") \n}
|
257
|
+
end
|
217
258
|
unless resource.follows_redirects?
|
218
259
|
code << %Q{options[:no_follow] = true \n}
|
219
260
|
end
|
data/lib/weary/request.rb
CHANGED
data/lib/weary/resource.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Weary
|
2
2
|
class Resource
|
3
|
-
attr_accessor :name, :domain, :with, :requires, :via, :format, :url, :authenticates, :follows, :headers
|
3
|
+
attr_accessor :name, :domain, :with, :requires, :via, :format, :url, :authenticates, :follows, :headers, :oauth, :access_token
|
4
4
|
|
5
5
|
def initialize(name)
|
6
6
|
self.name = name
|
@@ -9,6 +9,7 @@ module Weary
|
|
9
9
|
self.follows = true
|
10
10
|
self.with = []
|
11
11
|
self.requires = []
|
12
|
+
self.oauth = false
|
12
13
|
end
|
13
14
|
|
14
15
|
def name=(resource_name)
|
@@ -82,14 +83,38 @@ module Weary
|
|
82
83
|
@url = pattern
|
83
84
|
end
|
84
85
|
|
85
|
-
def
|
86
|
-
if
|
86
|
+
def oauth=(bool)
|
87
|
+
@authenticates = false if bool
|
88
|
+
@oauth = if bool
|
89
|
+
true
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def authenticates=(bool)
|
96
|
+
@oauth = false if bool
|
97
|
+
@authenticates = if bool
|
87
98
|
true
|
88
99
|
else
|
89
100
|
false
|
90
101
|
end
|
91
102
|
end
|
92
103
|
|
104
|
+
def authenticates?
|
105
|
+
@authenticates
|
106
|
+
end
|
107
|
+
|
108
|
+
def oauth?
|
109
|
+
@oauth
|
110
|
+
end
|
111
|
+
|
112
|
+
def access_token=(token)
|
113
|
+
raise ArgumentError, "Token needs to be an OAuth::AccessToken object" unless token.is_a?(OAuth::AccessToken)
|
114
|
+
@oauth = true
|
115
|
+
@access_token = token
|
116
|
+
end
|
117
|
+
|
93
118
|
def follows_redirects?
|
94
119
|
if @follows
|
95
120
|
true
|
@@ -107,7 +132,9 @@ module Weary
|
|
107
132
|
:format => @format,
|
108
133
|
:url => @url,
|
109
134
|
:domain => @domain,
|
110
|
-
:headers => @headers
|
135
|
+
:headers => @headers,
|
136
|
+
:oauth => oauth?,
|
137
|
+
:access_token => @access_token}}
|
111
138
|
end
|
112
139
|
|
113
140
|
end
|
data/lib/weary/response.rb
CHANGED
@@ -17,14 +17,17 @@ module Weary
|
|
17
17
|
self.format = http_response.content_type
|
18
18
|
end
|
19
19
|
|
20
|
+
# Is this an HTTP redirect?
|
20
21
|
def redirected?
|
21
22
|
@raw.is_a?(Net::HTTPRedirection)
|
22
23
|
end
|
23
24
|
|
25
|
+
# Was this Request successful?
|
24
26
|
def success?
|
25
27
|
(200..299).include?(@code)
|
26
28
|
end
|
27
29
|
|
30
|
+
# Returns a symbol corresponding to the Response's Content Type
|
28
31
|
def format=(type)
|
29
32
|
@format = case type
|
30
33
|
when *ContentTypes[:json]
|
@@ -42,6 +45,7 @@ module Weary
|
|
42
45
|
end
|
43
46
|
end
|
44
47
|
|
48
|
+
# Follow the Redirect
|
45
49
|
def follow_redirect
|
46
50
|
if redirected?
|
47
51
|
Request.new(@raw['location'], @method).perform
|
@@ -66,11 +70,13 @@ module Weary
|
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
73
|
+
# Same as parse[key]
|
69
74
|
def [](key)
|
70
75
|
parse[key]
|
71
76
|
end
|
72
77
|
|
73
|
-
# Search the body with a CSS/XPath selector with Nokogiri
|
78
|
+
# Search the body with a CSS/XPath selector with Nokogiri.
|
79
|
+
# If the document is not XMLish, fall back to #parse and ditch the selector.
|
74
80
|
def search(selector)
|
75
81
|
if @format == (:xml || :html)
|
76
82
|
doc = Nokogiri.parse(@body)
|
data/spec/weary/request_spec.rb
CHANGED
@@ -38,4 +38,12 @@ describe Weary::Request do
|
|
38
38
|
# not exactly kosher.
|
39
39
|
end
|
40
40
|
|
41
|
+
it "should prepare an oauth scheme if a token is provided" do
|
42
|
+
consumer = OAuth::Consumer.new("consumer_token","consumer_secret",{:site => 'http://foo.bar'})
|
43
|
+
token = OAuth::AccessToken.new(consumer, "token", "secret")
|
44
|
+
test = Weary::Request.new("http://foo.bar", :post, {:oauth => token})
|
45
|
+
test.send(:request).oauth_helper.options[:token].should == token
|
46
|
+
# seems a good a way as any to test if OAuth helpers have been added to the request
|
47
|
+
end
|
48
|
+
|
41
49
|
end
|
data/spec/weary/resource_spec.rb
CHANGED
@@ -35,6 +35,33 @@ describe Weary::Resource do
|
|
35
35
|
@test.authenticates?.should == false
|
36
36
|
end
|
37
37
|
|
38
|
+
it "oauth should be boolean" do
|
39
|
+
@test.oauth = "foobar"
|
40
|
+
@test.oauth?.should == true
|
41
|
+
@test.oauth = false
|
42
|
+
@test.oauth?.should == false
|
43
|
+
end
|
44
|
+
|
45
|
+
it "oauth should override basic authentication" do
|
46
|
+
@test.authenticates = true
|
47
|
+
@test.oauth = true
|
48
|
+
@test.authenticates?.should == false
|
49
|
+
@test.oauth?.should == true
|
50
|
+
end
|
51
|
+
|
52
|
+
it "providing an access token should set oauth to true" do
|
53
|
+
consumer = OAuth::Consumer.new("consumer_token","consumer_secret",{:site => 'http://foo.bar'})
|
54
|
+
token = OAuth::AccessToken.new(consumer, "token", "secret")
|
55
|
+
@test.oauth = false
|
56
|
+
@test.access_token = token
|
57
|
+
@test.oauth?.should == true
|
58
|
+
@test.access_token.should == token
|
59
|
+
end
|
60
|
+
|
61
|
+
it "an access token must contain an OAuth::AccessToken" do
|
62
|
+
lambda { @test.access_token = "foobar" }.should raise_error
|
63
|
+
end
|
64
|
+
|
38
65
|
it 'follows_redirects? should be boolean' do
|
39
66
|
@test.follows = "false"
|
40
67
|
@test.follows_redirects?.should == true
|
data/spec/weary_spec.rb
CHANGED
@@ -57,6 +57,39 @@ describe Weary do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
describe "OAuth" do
|
61
|
+
before do
|
62
|
+
consumer = OAuth::Consumer.new("consumer_token","consumer_secret",{:site => 'http://foo.bar'})
|
63
|
+
@token = OAuth::AccessToken.new(consumer, "token", "secret")
|
64
|
+
@test.oauth @token
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should accept an OAuth Access Token" do
|
68
|
+
@test.instance_variable_get(:@oauth).should == @token
|
69
|
+
lambda { @test.oauth "foobar" }.should raise_error
|
70
|
+
end
|
71
|
+
it "should notify the Resource that this is using OAuth" do
|
72
|
+
@test.domain "http://foo.bar"
|
73
|
+
r = @test.declare("show")
|
74
|
+
r.oauth?.should == true
|
75
|
+
r.access_token.should == @token
|
76
|
+
end
|
77
|
+
it "should be able to handle tokens set within the resource intelligently" do
|
78
|
+
test = Class.new
|
79
|
+
test.instance_eval { extend Weary }
|
80
|
+
test.domain "http://foo.bar"
|
81
|
+
r = test.declare("show")
|
82
|
+
r.oauth?.should == false
|
83
|
+
r.access_token.should == nil
|
84
|
+
r.oauth = true
|
85
|
+
lambda { test.send(:form_resource, r) }.should raise_error
|
86
|
+
r.access_token = @token
|
87
|
+
r.access_token.should == @token
|
88
|
+
test.send(:form_resource, r)[:show][:oauth].should == true
|
89
|
+
test.send(:form_resource, r)[:show][:access_token].should == @token
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
60
93
|
describe "Set Headers" do
|
61
94
|
it "should be a hash of values to pass in the Request head" do
|
62
95
|
@test.on_domain "http://foo.bar"
|
data/weary.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{weary}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.5.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Mark Wunsch"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-07-15}
|
10
10
|
s.description = %q{The Weary need REST: a tiny DSL that makes the consumption of RESTful web services simple.}
|
11
11
|
s.email = %q{mark@markwunsch.com}
|
12
12
|
s.extra_rdoc_files = [
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mwunsch-weary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Wunsch
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-07-15 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|