rest-core 0.8.2 → 1.0.0
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.
- data/.travis.yml +4 -2
- data/CHANGES.md +130 -19
- data/Gemfile +4 -0
- data/README.md +147 -13
- data/TODO.md +0 -10
- data/doc/ToC.md +7 -0
- data/doc/dependency.md +4 -0
- data/doc/design.md +4 -0
- data/example/auto.rb +51 -0
- data/example/coolio.rb +21 -0
- data/example/eventmachine.rb +28 -0
- data/example/multi.rb +33 -0
- data/example/rest-client.rb +16 -0
- data/lib/rest-core/app/abstract/async_fiber.rb +13 -0
- data/lib/rest-core/app/auto.rb +22 -0
- data/lib/rest-core/app/coolio-async.rb +32 -0
- data/lib/rest-core/app/coolio-fiber.rb +30 -0
- data/lib/rest-core/app/coolio.rb +9 -0
- data/lib/rest-core/app/dry.rb +1 -0
- data/lib/rest-core/app/em-http-request-async.rb +34 -0
- data/lib/rest-core/app/em-http-request-fiber.rb +39 -0
- data/lib/rest-core/app/em-http-request.rb +9 -0
- data/lib/rest-core/app/rest-client.rb +24 -7
- data/lib/rest-core/client/universal.rb +3 -4
- data/lib/rest-core/client.rb +31 -4
- data/lib/rest-core/middleware/cache.rb +23 -5
- data/lib/rest-core/middleware/common_logger.rb +13 -3
- data/lib/rest-core/middleware/error_detector.rb +10 -1
- data/lib/rest-core/middleware/error_handler.rb +7 -1
- data/lib/rest-core/middleware/follow_redirect.rb +42 -0
- data/lib/rest-core/middleware/json_decode.rb +11 -2
- data/lib/rest-core/middleware/timeout/coolio_timer.rb +4 -0
- data/lib/rest-core/middleware/timeout/eventmachine_timer.rb +14 -0
- data/lib/rest-core/middleware/timeout.rb +73 -1
- data/lib/rest-core/middleware.rb +7 -0
- data/lib/rest-core/patch/rest-client.rb +35 -0
- data/lib/rest-core/version.rb +1 -1
- data/lib/rest-core.rb +19 -0
- data/rest-core.gemspec +28 -8
- data/test/test_client.rb +20 -3
- data/test/test_follow_redirect.rb +47 -0
- data/test/test_json_decode.rb +24 -0
- metadata +36 -11
- data/example/facebook.rb +0 -9
- data/example/github.rb +0 -4
- data/example/linkedin.rb +0 -8
- data/example/twitter.rb +0 -11
data/lib/rest-core.rb
CHANGED
@@ -14,6 +14,9 @@ module RestCore
|
|
14
14
|
FAIL = 'core.fail'
|
15
15
|
LOG = 'core.log'
|
16
16
|
|
17
|
+
ASYNC = 'async.callback'
|
18
|
+
TIMER = 'async.timer'
|
19
|
+
|
17
20
|
# core utilities
|
18
21
|
autoload :Builder , 'rest-core/builder'
|
19
22
|
autoload :Client , 'rest-core/client'
|
@@ -42,6 +45,7 @@ module RestCore
|
|
42
45
|
autoload :ErrorDetector , 'rest-core/middleware/error_detector'
|
43
46
|
autoload :ErrorDetectorHttp, 'rest-core/middleware/error_detector_http'
|
44
47
|
autoload :ErrorHandler , 'rest-core/middleware/error_handler'
|
48
|
+
autoload :FollowRedirect, 'rest-core/middleware/follow_redirect'
|
45
49
|
autoload :JsonDecode , 'rest-core/middleware/json_decode'
|
46
50
|
autoload :Oauth1Header , 'rest-core/middleware/oauth1_header'
|
47
51
|
autoload :Oauth2Header , 'rest-core/middleware/oauth2_header'
|
@@ -49,8 +53,15 @@ module RestCore
|
|
49
53
|
autoload :Timeout , 'rest-core/middleware/timeout'
|
50
54
|
|
51
55
|
# apps
|
56
|
+
autoload :Auto , 'rest-core/app/auto'
|
52
57
|
autoload :Dry , 'rest-core/app/dry'
|
53
58
|
autoload :RestClient , 'rest-core/app/rest-client'
|
59
|
+
autoload :Coolio , 'rest-core/app/coolio'
|
60
|
+
autoload :CoolioAsync , 'rest-core/app/coolio-async'
|
61
|
+
autoload :CoolioFiber , 'rest-core/app/coolio-fiber'
|
62
|
+
autoload :EmHttpRequest , 'rest-core/app/em-http-request'
|
63
|
+
autoload :EmHttpRequestAsync, 'rest-core/app/em-http-request-async'
|
64
|
+
autoload :EmHttpRequestFiber, 'rest-core/app/em-http-request-fiber'
|
54
65
|
|
55
66
|
# clients
|
56
67
|
autoload :Simple , 'rest-core/client/simple'
|
@@ -58,3 +69,11 @@ module RestCore
|
|
58
69
|
end
|
59
70
|
|
60
71
|
RC = RestCore unless Object.const_defined?(:RC)
|
72
|
+
|
73
|
+
begin
|
74
|
+
require 'fiber'
|
75
|
+
rescue LoadError
|
76
|
+
end
|
77
|
+
# assume we would always require 'rest-core' in root fiber
|
78
|
+
RestCore::RootFiber = Fiber.current if Object.const_defined?(:Fiber) &&
|
79
|
+
Fiber.respond_to?(:current)
|
data/rest-core.gemspec
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "rest-core"
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "1.0.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = [
|
9
9
|
"Cardinal Blue",
|
10
10
|
"Lin Jen-Shin (godfat)"]
|
11
|
-
s.date = "2012-
|
12
|
-
s.description = "Modular Ruby clients interface for REST APIs\n\nThere has been an explosion in the number of REST APIs available today.\nTo address the need for a way to access these APIs easily and elegantly,\nwe have developed [rest-core][], which consists of composable middleware\nthat allows you to build a REST client for any REST API. Or in the case of\ncommon APIs such as Facebook, Github, and Twitter, you can simply use the\ndedicated clients provided by [rest-more][].\n\n[rest-core]:
|
11
|
+
s.date = "2012-03-17"
|
12
|
+
s.description = "Modular Ruby clients interface for REST APIs\n\nThere has been an explosion in the number of REST APIs available today.\nTo address the need for a way to access these APIs easily and elegantly,\nwe have developed [rest-core][], which consists of composable middleware\nthat allows you to build a REST client for any REST API. Or in the case of\ncommon APIs such as Facebook, Github, and Twitter, you can simply use the\ndedicated clients provided by [rest-more][].\n\n[rest-core]: https://github.com/cardinalblue/rest-core\n[rest-more]: https://github.com/cardinalblue/rest-more"
|
13
13
|
s.email = ["dev (XD) cardinalblue.com"]
|
14
14
|
s.files = [
|
15
15
|
".gitignore",
|
@@ -22,12 +22,24 @@ Gem::Specification.new do |s|
|
|
22
22
|
"README.md",
|
23
23
|
"Rakefile",
|
24
24
|
"TODO.md",
|
25
|
-
"
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"example/
|
25
|
+
"doc/ToC.md",
|
26
|
+
"doc/dependency.md",
|
27
|
+
"doc/design.md",
|
28
|
+
"example/auto.rb",
|
29
|
+
"example/coolio.rb",
|
30
|
+
"example/eventmachine.rb",
|
31
|
+
"example/multi.rb",
|
32
|
+
"example/rest-client.rb",
|
29
33
|
"lib/rest-core.rb",
|
34
|
+
"lib/rest-core/app/abstract/async_fiber.rb",
|
35
|
+
"lib/rest-core/app/auto.rb",
|
36
|
+
"lib/rest-core/app/coolio-async.rb",
|
37
|
+
"lib/rest-core/app/coolio-fiber.rb",
|
38
|
+
"lib/rest-core/app/coolio.rb",
|
30
39
|
"lib/rest-core/app/dry.rb",
|
40
|
+
"lib/rest-core/app/em-http-request-async.rb",
|
41
|
+
"lib/rest-core/app/em-http-request-fiber.rb",
|
42
|
+
"lib/rest-core/app/em-http-request.rb",
|
31
43
|
"lib/rest-core/app/rest-client.rb",
|
32
44
|
"lib/rest-core/builder.rb",
|
33
45
|
"lib/rest-core/client.rb",
|
@@ -49,11 +61,15 @@ Gem::Specification.new do |s|
|
|
49
61
|
"lib/rest-core/middleware/error_detector.rb",
|
50
62
|
"lib/rest-core/middleware/error_detector_http.rb",
|
51
63
|
"lib/rest-core/middleware/error_handler.rb",
|
64
|
+
"lib/rest-core/middleware/follow_redirect.rb",
|
52
65
|
"lib/rest-core/middleware/json_decode.rb",
|
53
66
|
"lib/rest-core/middleware/oauth1_header.rb",
|
54
67
|
"lib/rest-core/middleware/oauth2_header.rb",
|
55
68
|
"lib/rest-core/middleware/oauth2_query.rb",
|
56
69
|
"lib/rest-core/middleware/timeout.rb",
|
70
|
+
"lib/rest-core/middleware/timeout/coolio_timer.rb",
|
71
|
+
"lib/rest-core/middleware/timeout/eventmachine_timer.rb",
|
72
|
+
"lib/rest-core/patch/rest-client.rb",
|
57
73
|
"lib/rest-core/test.rb",
|
58
74
|
"lib/rest-core/util/hmac.rb",
|
59
75
|
"lib/rest-core/util/parse_query.rb",
|
@@ -70,13 +86,15 @@ Gem::Specification.new do |s|
|
|
70
86
|
"test/test_client_oauth1.rb",
|
71
87
|
"test/test_error_detector.rb",
|
72
88
|
"test/test_error_detector_http.rb",
|
89
|
+
"test/test_follow_redirect.rb",
|
90
|
+
"test/test_json_decode.rb",
|
73
91
|
"test/test_oauth1_header.rb",
|
74
92
|
"test/test_payload.rb",
|
75
93
|
"test/test_universal.rb",
|
76
94
|
"test/test_wrapper.rb"]
|
77
95
|
s.homepage = "https://github.com/cardinalblue/rest-core"
|
78
96
|
s.require_paths = ["lib"]
|
79
|
-
s.rubygems_version = "1.8.
|
97
|
+
s.rubygems_version = "1.8.19"
|
80
98
|
s.summary = "Modular Ruby clients interface for REST APIs"
|
81
99
|
s.test_files = [
|
82
100
|
"test/test_auth_basic.rb",
|
@@ -85,6 +103,8 @@ Gem::Specification.new do |s|
|
|
85
103
|
"test/test_client_oauth1.rb",
|
86
104
|
"test/test_error_detector.rb",
|
87
105
|
"test/test_error_detector_http.rb",
|
106
|
+
"test/test_follow_redirect.rb",
|
107
|
+
"test/test_json_decode.rb",
|
88
108
|
"test/test_oauth1_header.rb",
|
89
109
|
"test/test_payload.rb",
|
90
110
|
"test/test_universal.rb",
|
data/test/test_client.rb
CHANGED
@@ -1,15 +1,32 @@
|
|
1
1
|
|
2
2
|
require 'rest-core/test'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe RC::Simple do
|
5
5
|
after do
|
6
6
|
WebMock.reset!
|
7
7
|
RR.verify
|
8
8
|
end
|
9
9
|
|
10
10
|
should 'do simple request' do
|
11
|
-
|
12
|
-
|
11
|
+
[:get, :post, :delete, :put,
|
12
|
+
:head, :patch, :options].each do |method|
|
13
|
+
stub_request(method, 'http://localhost/').to_return(:body => '[]')
|
14
|
+
RC::Simple.new.send(method, 'http://localhost/').should.eq '[]'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'call the callback' do
|
19
|
+
[:get, :post, :delete, :put,
|
20
|
+
:head, :patch, :options].each do |method|
|
21
|
+
stub_request(method, 'http://localhost/').to_return(:body => '123')
|
22
|
+
(client = RC::Simple.new).send(method, 'http://localhost/'){ |res|
|
23
|
+
res.should.eq '123' }.should.eq client
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'have correct to_i' do
|
28
|
+
stub_request(:get, 'http://localhost/').to_return(:body => '123')
|
29
|
+
RC::Simple.new.get('http://localhost/').to_i.should.eq 123
|
13
30
|
end
|
14
31
|
|
15
32
|
should 'use defaults' do
|
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/test'
|
3
|
+
|
4
|
+
describe RC::FollowRedirect do
|
5
|
+
before do
|
6
|
+
@dry = RC::Dry.new
|
7
|
+
@app = RC::FollowRedirect.new(dry, 1)
|
8
|
+
end
|
9
|
+
after do
|
10
|
+
RR.verify
|
11
|
+
end
|
12
|
+
def dry; @dry; end
|
13
|
+
def app; @app; end
|
14
|
+
|
15
|
+
[301, 302, 303, 307].each do |status|
|
16
|
+
should "not follow redirect if reached max_redirects: #{status}" do
|
17
|
+
mock(dry).call(anything){ |env|
|
18
|
+
env.merge(RC::RESPONSE_STATUS => status,
|
19
|
+
RC::RESPONSE_HEADERS => {'LOCATION' => 'location'})
|
20
|
+
}
|
21
|
+
app.call(RC::REQUEST_METHOD => :get,
|
22
|
+
'max_redirects' => 0)[RC::RESPONSE_HEADERS]['LOCATION'].
|
23
|
+
should.eq 'location'
|
24
|
+
end
|
25
|
+
|
26
|
+
should "follow once: #{status}" do
|
27
|
+
mock(dry).call(anything){ |env|
|
28
|
+
env.merge(RC::RESPONSE_STATUS => status,
|
29
|
+
RC::RESPONSE_HEADERS => {'LOCATION' => 'location'})
|
30
|
+
}.times(2)
|
31
|
+
app.call(RC::REQUEST_METHOD => :get)[RC::RESPONSE_HEADERS]['LOCATION'].
|
32
|
+
should.eq 'location'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
[200, 201, 404, 500].each do |status|
|
37
|
+
should "not follow redirect if it's not a redirect status: #{status}" do
|
38
|
+
mock(dry).call(anything){ |env|
|
39
|
+
env.merge(RC::RESPONSE_STATUS => status,
|
40
|
+
RC::RESPONSE_HEADERS => {'LOCATION' => 'location'})
|
41
|
+
}
|
42
|
+
app.call(RC::REQUEST_METHOD => :get,
|
43
|
+
'max_redirects' => 9)[RC::RESPONSE_HEADERS]['LOCATION'].
|
44
|
+
should.eq 'location'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require 'rest-core/test'
|
3
|
+
|
4
|
+
describe RC::JsonDecode do
|
5
|
+
before do
|
6
|
+
@app = RC::JsonDecode.new(RC::Dry.new, true)
|
7
|
+
end
|
8
|
+
|
9
|
+
should 'do nothing' do
|
10
|
+
@app.call({}).should.eq(RC::RESPONSE_BODY => nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'decode sync' do
|
14
|
+
@app.call(RC::RESPONSE_BODY => '{}').should.eq(
|
15
|
+
RC::RESPONSE_BODY => {} )
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'decode async' do
|
19
|
+
@app.call(RC::RESPONSE_BODY => '{}',
|
20
|
+
RC::ASYNC => lambda{ |response|
|
21
|
+
response[RC::RESPONSE_BODY].should.eq({})}
|
22
|
+
)[RC::RESPONSE_BODY].should.eq '{}'
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-03-17 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rest-client
|
17
|
-
requirement:
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,7 +22,12 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements:
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
26
31
|
description: ! 'Modular Ruby clients interface for REST APIs
|
27
32
|
|
28
33
|
|
@@ -39,9 +44,9 @@ description: ! 'Modular Ruby clients interface for REST APIs
|
|
39
44
|
dedicated clients provided by [rest-more][].
|
40
45
|
|
41
46
|
|
42
|
-
[rest-core]:
|
47
|
+
[rest-core]: https://github.com/cardinalblue/rest-core
|
43
48
|
|
44
|
-
[rest-more]:
|
49
|
+
[rest-more]: https://github.com/cardinalblue/rest-more'
|
45
50
|
email:
|
46
51
|
- dev (XD) cardinalblue.com
|
47
52
|
executables: []
|
@@ -58,12 +63,24 @@ files:
|
|
58
63
|
- README.md
|
59
64
|
- Rakefile
|
60
65
|
- TODO.md
|
61
|
-
-
|
62
|
-
-
|
63
|
-
-
|
64
|
-
- example/
|
66
|
+
- doc/ToC.md
|
67
|
+
- doc/dependency.md
|
68
|
+
- doc/design.md
|
69
|
+
- example/auto.rb
|
70
|
+
- example/coolio.rb
|
71
|
+
- example/eventmachine.rb
|
72
|
+
- example/multi.rb
|
73
|
+
- example/rest-client.rb
|
65
74
|
- lib/rest-core.rb
|
75
|
+
- lib/rest-core/app/abstract/async_fiber.rb
|
76
|
+
- lib/rest-core/app/auto.rb
|
77
|
+
- lib/rest-core/app/coolio-async.rb
|
78
|
+
- lib/rest-core/app/coolio-fiber.rb
|
79
|
+
- lib/rest-core/app/coolio.rb
|
66
80
|
- lib/rest-core/app/dry.rb
|
81
|
+
- lib/rest-core/app/em-http-request-async.rb
|
82
|
+
- lib/rest-core/app/em-http-request-fiber.rb
|
83
|
+
- lib/rest-core/app/em-http-request.rb
|
67
84
|
- lib/rest-core/app/rest-client.rb
|
68
85
|
- lib/rest-core/builder.rb
|
69
86
|
- lib/rest-core/client.rb
|
@@ -85,11 +102,15 @@ files:
|
|
85
102
|
- lib/rest-core/middleware/error_detector.rb
|
86
103
|
- lib/rest-core/middleware/error_detector_http.rb
|
87
104
|
- lib/rest-core/middleware/error_handler.rb
|
105
|
+
- lib/rest-core/middleware/follow_redirect.rb
|
88
106
|
- lib/rest-core/middleware/json_decode.rb
|
89
107
|
- lib/rest-core/middleware/oauth1_header.rb
|
90
108
|
- lib/rest-core/middleware/oauth2_header.rb
|
91
109
|
- lib/rest-core/middleware/oauth2_query.rb
|
92
110
|
- lib/rest-core/middleware/timeout.rb
|
111
|
+
- lib/rest-core/middleware/timeout/coolio_timer.rb
|
112
|
+
- lib/rest-core/middleware/timeout/eventmachine_timer.rb
|
113
|
+
- lib/rest-core/patch/rest-client.rb
|
93
114
|
- lib/rest-core/test.rb
|
94
115
|
- lib/rest-core/util/hmac.rb
|
95
116
|
- lib/rest-core/util/parse_query.rb
|
@@ -106,6 +127,8 @@ files:
|
|
106
127
|
- test/test_client_oauth1.rb
|
107
128
|
- test/test_error_detector.rb
|
108
129
|
- test/test_error_detector_http.rb
|
130
|
+
- test/test_follow_redirect.rb
|
131
|
+
- test/test_json_decode.rb
|
109
132
|
- test/test_oauth1_header.rb
|
110
133
|
- test/test_payload.rb
|
111
134
|
- test/test_universal.rb
|
@@ -130,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
153
|
version: '0'
|
131
154
|
requirements: []
|
132
155
|
rubyforge_project:
|
133
|
-
rubygems_version: 1.8.
|
156
|
+
rubygems_version: 1.8.19
|
134
157
|
signing_key:
|
135
158
|
specification_version: 3
|
136
159
|
summary: Modular Ruby clients interface for REST APIs
|
@@ -141,6 +164,8 @@ test_files:
|
|
141
164
|
- test/test_client_oauth1.rb
|
142
165
|
- test/test_error_detector.rb
|
143
166
|
- test/test_error_detector_http.rb
|
167
|
+
- test/test_follow_redirect.rb
|
168
|
+
- test/test_json_decode.rb
|
144
169
|
- test/test_oauth1_header.rb
|
145
170
|
- test/test_payload.rb
|
146
171
|
- test/test_universal.rb
|
data/example/facebook.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/client/rest-graph'
|
3
|
-
|
4
|
-
p RestGraph.new.get('4') # get user info
|
5
|
-
|
6
|
-
facebook = RestGraph.new(:app_id => '...', :secret => '...')
|
7
|
-
facebook.authorize_url # copy and paste the URL in browser to authorize
|
8
|
-
facebook.authorize!(:redirect_uri => '...', :code => '...')
|
9
|
-
p facebook.get('me')
|
data/example/github.rb
DELETED
data/example/linkedin.rb
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core'
|
3
|
-
|
4
|
-
linkedin = RestCore::Linkedin.new(:consumer_key => '...',
|
5
|
-
:consumer_secret => '...')
|
6
|
-
linkedin.authorize_url! # copy and paste the URL in browser to authorize
|
7
|
-
linkedin.authorize!('..') # paste your code from browser
|
8
|
-
p linkedin.me # get current user info
|
data/example/twitter.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core'
|
3
|
-
RestCore::Twitter.new.statuses('_cardinalblue') # get user tweets
|
4
|
-
|
5
|
-
twitter = RestCore::Twitter.new(:consumer_key => '...',
|
6
|
-
:consumer_secret => '...')
|
7
|
-
twitter.authorize_url! # copy and paste the URL in browser to authorize
|
8
|
-
twitter.authorize!('..') # paste your code from browser
|
9
|
-
p twitter.tweet('hi!') # tweet for the current user
|
10
|
-
|
11
|
-
p twitter.tweet('hi with pic!', File.open('...'))
|