rest-core 0.0.1
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/.gitignore +6 -0
- data/.gitmodules +3 -0
- data/.travis.yml +9 -0
- data/CONTRIBUTORS +11 -0
- data/Gemfile +8 -0
- data/LICENSE +201 -0
- data/NOTE.md +48 -0
- data/README +83 -0
- data/README.md +83 -0
- data/Rakefile +26 -0
- data/TODO.md +17 -0
- data/example/facebook.rb +145 -0
- data/example/github.rb +21 -0
- data/lib/rest-core.rb +48 -0
- data/lib/rest-core/app/ask.rb +11 -0
- data/lib/rest-core/app/rest-client.rb +24 -0
- data/lib/rest-core/builder.rb +24 -0
- data/lib/rest-core/client.rb +278 -0
- data/lib/rest-core/client/github.rb +19 -0
- data/lib/rest-core/client/linkedin.rb +57 -0
- data/lib/rest-core/client/rest-graph.rb +262 -0
- data/lib/rest-core/client/twitter.rb +59 -0
- data/lib/rest-core/client_oauth1.rb +25 -0
- data/lib/rest-core/event.rb +17 -0
- data/lib/rest-core/middleware.rb +53 -0
- data/lib/rest-core/middleware/cache.rb +80 -0
- data/lib/rest-core/middleware/common_logger.rb +27 -0
- data/lib/rest-core/middleware/default_headers.rb +11 -0
- data/lib/rest-core/middleware/default_query.rb +11 -0
- data/lib/rest-core/middleware/default_site.rb +15 -0
- data/lib/rest-core/middleware/defaults.rb +44 -0
- data/lib/rest-core/middleware/error_detector.rb +16 -0
- data/lib/rest-core/middleware/error_detector_http.rb +11 -0
- data/lib/rest-core/middleware/error_handler.rb +19 -0
- data/lib/rest-core/middleware/json_decode.rb +83 -0
- data/lib/rest-core/middleware/oauth1_header.rb +81 -0
- data/lib/rest-core/middleware/oauth2_query.rb +19 -0
- data/lib/rest-core/middleware/timeout.rb +13 -0
- data/lib/rest-core/util/hmac.rb +22 -0
- data/lib/rest-core/version.rb +4 -0
- data/lib/rest-core/wrapper.rb +55 -0
- data/lib/rest-graph/config_util.rb +43 -0
- data/rest-core.gemspec +162 -0
- data/task/.gitignore +1 -0
- data/task/gemgem.rb +184 -0
- data/test/common.rb +29 -0
- data/test/config/rest-graph.yaml +7 -0
- data/test/pending/test_load_config.rb +42 -0
- data/test/pending/test_multi.rb +123 -0
- data/test/pending/test_test_util.rb +86 -0
- data/test/test_api.rb +98 -0
- data/test/test_cache.rb +62 -0
- data/test/test_default.rb +27 -0
- data/test/test_error.rb +66 -0
- data/test/test_handler.rb +87 -0
- data/test/test_misc.rb +75 -0
- data/test/test_oauth.rb +42 -0
- data/test/test_oauth1_header.rb +46 -0
- data/test/test_old.rb +116 -0
- data/test/test_page.rb +110 -0
- data/test/test_parse.rb +131 -0
- data/test/test_rest-graph.rb +10 -0
- data/test/test_serialize.rb +44 -0
- data/test/test_timeout.rb +25 -0
- metadata +267 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
RestCore::Builder.client('Github') do
|
3
|
+
s = self.class # this is only for ruby 1.8!
|
4
|
+
use s::Timeout , 10
|
5
|
+
|
6
|
+
use s::DefaultSite , 'https://api.github.com/'
|
7
|
+
use s::DefaultHeaders, {'Accept' => 'application/json'}
|
8
|
+
use s::Oauth2Query , 'access_token', nil
|
9
|
+
|
10
|
+
use s::CommonLogger , lambda{|obj|obj}
|
11
|
+
use s::Cache , {}, nil do
|
12
|
+
use s::ErrorHandler , lambda{|env| raise env[s::RESPONSE_BODY]['message']}
|
13
|
+
use s::ErrorDetectorHttp
|
14
|
+
use s::JsonDecode , true
|
15
|
+
run s::Ask
|
16
|
+
end
|
17
|
+
|
18
|
+
run s::RestClient
|
19
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
RestCore::Builder.client('Linkedin', :data) do
|
3
|
+
s = self.class # this is only for ruby 1.8!
|
4
|
+
use s::Timeout , 10
|
5
|
+
|
6
|
+
use s::DefaultSite , 'https://api.linkedin.com/'
|
7
|
+
use s::DefaultHeaders, {'Accept' => 'application/json'}
|
8
|
+
use s::DefaultQuery , {'format' => 'json'}
|
9
|
+
|
10
|
+
use s::Oauth1Header ,
|
11
|
+
'uas/oauth/requestToken', 'uas/oauth/accessToken',
|
12
|
+
'https://www.linkedin.com/uas/oauth/authorize'
|
13
|
+
|
14
|
+
use s::CommonLogger , method(:puts)
|
15
|
+
|
16
|
+
use s::Cache , {}, nil do
|
17
|
+
use s::ErrorHandler , lambda{|env|
|
18
|
+
if (body = env[s::RESPONSE_BODY]).kind_of?(Hash)
|
19
|
+
raise body['message']
|
20
|
+
else
|
21
|
+
raise body
|
22
|
+
end
|
23
|
+
}
|
24
|
+
use s::ErrorDetectorHttp
|
25
|
+
use s::JsonDecode , true
|
26
|
+
run s::Ask
|
27
|
+
end
|
28
|
+
|
29
|
+
use s::Defaults , :data => lambda{{}}
|
30
|
+
|
31
|
+
run s::RestClient
|
32
|
+
end
|
33
|
+
|
34
|
+
module Linkedin::Client
|
35
|
+
include RestCore
|
36
|
+
|
37
|
+
def oauth_token
|
38
|
+
data['oauth_token'] if data.kind_of?(Hash)
|
39
|
+
end
|
40
|
+
def oauth_token= token
|
41
|
+
data['oauth_token'] = token if data.kind_of?(Hash)
|
42
|
+
end
|
43
|
+
def oauth_token_secret
|
44
|
+
data['oauth_token_secret'] if data.kind_of?(Hash)
|
45
|
+
end
|
46
|
+
def oauth_token_secret= secret
|
47
|
+
data['oauth_token_secret'] = secret if data.kind_of?(Hash)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def set_token query
|
52
|
+
self.data = query
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Linkedin.send(:include, RestCore::ClientOauth1)
|
57
|
+
Linkedin.send(:include, Linkedin::Client)
|
@@ -0,0 +1,262 @@
|
|
1
|
+
|
2
|
+
require 'rest-core'
|
3
|
+
require 'rest-core/util/hmac'
|
4
|
+
|
5
|
+
# optional gem
|
6
|
+
begin; require 'rack'; rescue LoadError; end
|
7
|
+
|
8
|
+
RestCore::Builder.client('RestGraph', :data, :app_id, :secret, :old_site) do
|
9
|
+
s = self.class # this is only for ruby 1.8!
|
10
|
+
use s::Timeout , 10
|
11
|
+
|
12
|
+
use s::DefaultSite , 'https://graph.facebook.com/'
|
13
|
+
use s::DefaultHeaders, {'Accept' => 'application/json',
|
14
|
+
'Accept-Language' => 'en-us'}
|
15
|
+
use s::Oauth2Query , 'access_token', nil
|
16
|
+
|
17
|
+
use s::CommonLogger , lambda{|obj|obj}
|
18
|
+
|
19
|
+
use s::Cache , {}, nil do
|
20
|
+
use s::ErrorHandler , lambda{ |env| raise ::RestGraph::Error.call(env) }
|
21
|
+
use s::ErrorDetector , lambda{ |env|
|
22
|
+
if env[s::RESPONSE_BODY].kind_of?(Hash)
|
23
|
+
env[s::RESPONSE_BODY]['error'] ||
|
24
|
+
env[s::RESPONSE_BODY]['error_code']
|
25
|
+
end}
|
26
|
+
|
27
|
+
use s::JsonDecode , true
|
28
|
+
run s::Ask
|
29
|
+
end
|
30
|
+
|
31
|
+
use s::Defaults , :data => lambda{{}},
|
32
|
+
:old_site => 'https://api.facebook.com/'
|
33
|
+
|
34
|
+
run s::RestClient
|
35
|
+
end
|
36
|
+
|
37
|
+
class RestGraph::Error < RuntimeError
|
38
|
+
include RestCore
|
39
|
+
class AccessToken < RestGraph::Error; end
|
40
|
+
class InvalidAccessToken < AccessToken ; end
|
41
|
+
class MissingAccessToken < AccessToken ; end
|
42
|
+
|
43
|
+
attr_reader :error, :url
|
44
|
+
def initialize error, url=''
|
45
|
+
@error, @url = error, url
|
46
|
+
super("#{error.inspect} from #{url}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.call env
|
50
|
+
error, url = env[RESPONSE_BODY], Middleware.request_uri(env)
|
51
|
+
return new(env[FAIL], url) unless error.kind_of?(Hash)
|
52
|
+
if invalid_token?(error)
|
53
|
+
InvalidAccessToken.new(error, url)
|
54
|
+
elsif missing_token?(error)
|
55
|
+
MissingAccessToken.new(error, url)
|
56
|
+
else
|
57
|
+
new(error, url)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.invalid_token? error
|
62
|
+
(%w[OAuthInvalidTokenException
|
63
|
+
OAuthException].include?((error['error'] || {})['type'])) ||
|
64
|
+
(error['error_code'] == 190) # Invalid OAuth 2.0 Access Token
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.missing_token? error
|
68
|
+
(error['error'] || {})['message'] =~ /^An active access token/ ||
|
69
|
+
(error['error_code'] == 104) # Requires valid signature
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module RestGraph::Client
|
74
|
+
include RestCore
|
75
|
+
|
76
|
+
def self.included mod
|
77
|
+
mod.send(:alias_method, :auto_decode , :json_decode )
|
78
|
+
mod.send(:alias_method, :auto_decode=, :json_decode=)
|
79
|
+
end
|
80
|
+
|
81
|
+
def oauth_token
|
82
|
+
data['access_token'] || data['oauth_token'] if data.kind_of?(Hash)
|
83
|
+
end
|
84
|
+
def oauth_token= token
|
85
|
+
data['access_token'] = token if data.kind_of?(Hash)
|
86
|
+
end
|
87
|
+
alias_method :access_token , :oauth_token
|
88
|
+
alias_method :access_token=, :oauth_token=
|
89
|
+
|
90
|
+
def secret_oauth_token ; "#{app_id}|#{secret}" ; end
|
91
|
+
alias_method :secret_access_token, :secret_oauth_token
|
92
|
+
|
93
|
+
def accept ; headers['Accept'] ; end
|
94
|
+
def accept= val; headers['Accept'] = val; end
|
95
|
+
def lang ; headers['Accept-Language'] ; end
|
96
|
+
def lang= val; headers['Accept-Language'] = val; end
|
97
|
+
|
98
|
+
def authorized? ; !!oauth_token ; end
|
99
|
+
|
100
|
+
def next_page hash, opts={}, &cb
|
101
|
+
if hash['paging'].kind_of?(Hash) && hash['paging']['next']
|
102
|
+
request(opts, [:get, URI.encode(hash['paging']['next'])], &cb)
|
103
|
+
else
|
104
|
+
yield(nil) if block_given?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def prev_page hash, opts={}, &cb
|
109
|
+
if hash['paging'].kind_of?(Hash) && hash['paging']['previous']
|
110
|
+
request(opts, [:get, URI.encode(hash['paging']['previous'])], &cb)
|
111
|
+
else
|
112
|
+
yield(nil) if block_given?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
alias_method :previous_page, :prev_page
|
116
|
+
|
117
|
+
def for_pages hash, pages=1, opts={}, kind=:next_page, &cb
|
118
|
+
if pages > 1
|
119
|
+
merge_data(send(kind, hash, opts){ |result|
|
120
|
+
yield(result.freeze) if block_given?
|
121
|
+
for_pages(result, pages - 1, opts, kind, &cb) if result
|
122
|
+
}, hash)
|
123
|
+
else
|
124
|
+
yield(nil) if block_given?
|
125
|
+
hash
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# cookies, app_id, secrect related below
|
130
|
+
|
131
|
+
def parse_rack_env! env
|
132
|
+
env['HTTP_COOKIE'].to_s =~ /fbs_#{app_id}=([^\;]+)/
|
133
|
+
self.data = parse_fbs!($1)
|
134
|
+
end
|
135
|
+
|
136
|
+
def parse_cookies! cookies
|
137
|
+
self.data = parse_fbs!(cookies["fbs_#{app_id}"])
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse_fbs! fbs
|
141
|
+
self.data = check_sig_and_return_data(
|
142
|
+
# take out facebook sometimes there but sometimes not quotes in cookies
|
143
|
+
Rack::Utils.parse_query(fbs.to_s.sub(/^"/, '').sub(/"$/, '')))
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_json! json
|
147
|
+
self.data = json &&
|
148
|
+
check_sig_and_return_data(JsonDecode.json_decode(json))
|
149
|
+
rescue JsonDecode::ParseError
|
150
|
+
self.data = nil
|
151
|
+
end
|
152
|
+
|
153
|
+
def fbs
|
154
|
+
"#{fbs_without_sig(data).join('&')}&sig=#{calculate_sig(data)}"
|
155
|
+
end
|
156
|
+
|
157
|
+
# facebook's new signed_request...
|
158
|
+
|
159
|
+
def parse_signed_request! request
|
160
|
+
sig_encoded, json_encoded = request.split('.')
|
161
|
+
sig, json = [sig_encoded, json_encoded].map{ |str|
|
162
|
+
"#{str.tr('-_', '+/')}==".unpack('m').first
|
163
|
+
}
|
164
|
+
self.data = check_sig_and_return_data(
|
165
|
+
JsonDecode.json_decode(json).merge('sig' => sig)){
|
166
|
+
Hmac.sha256(secret, json_encoded)
|
167
|
+
}
|
168
|
+
rescue JsonDecode::ParseError
|
169
|
+
self.data = nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# oauth related
|
173
|
+
|
174
|
+
def authorize_url opts={}
|
175
|
+
url('oauth/authorize',
|
176
|
+
{:client_id => app_id, :access_token => nil}.merge(opts))
|
177
|
+
end
|
178
|
+
|
179
|
+
def authorize! opts={}
|
180
|
+
query = {:client_id => app_id, :client_secret => secret}.merge(opts)
|
181
|
+
self.data = Rack::Utils.parse_query(
|
182
|
+
request({:auto_decode => false}.merge(opts),
|
183
|
+
[:get, url('oauth/access_token', query)]))
|
184
|
+
end
|
185
|
+
|
186
|
+
# old rest facebook api, i will definitely love to remove them someday
|
187
|
+
|
188
|
+
def old_rest path, query={}, opts={}, &cb
|
189
|
+
uri = url("method/#{path}", {:format => 'json'}.merge(query),
|
190
|
+
{:site => old_site}.merge(opts))
|
191
|
+
if opts[:post]
|
192
|
+
request(
|
193
|
+
opts.merge('cache.key' => uri, 'cache.post' => true),
|
194
|
+
[:post,
|
195
|
+
url("method/#{path}", {:format => 'json'},
|
196
|
+
{:site => old_site}.merge(opts)),
|
197
|
+
{}, query],
|
198
|
+
&cb)
|
199
|
+
else
|
200
|
+
request(opts, [:get, uri], &cb)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def secret_old_rest path, query={}, opts={}, &cb
|
205
|
+
old_rest(path, query, {:secret => true}.merge(opts), &cb)
|
206
|
+
end
|
207
|
+
|
208
|
+
def fql code, query={}, opts={}, &cb
|
209
|
+
old_rest('fql.query', {:query => code}.merge(query), opts, &cb)
|
210
|
+
end
|
211
|
+
|
212
|
+
def fql_multi codes, query={}, opts={}, &cb
|
213
|
+
old_rest('fql.multiquery',
|
214
|
+
{:queries => JsonDecode.json_encode(codes)}.merge(query), opts, &cb)
|
215
|
+
end
|
216
|
+
|
217
|
+
def exchange_sessions query={}, opts={}, &cb
|
218
|
+
q = {:client_id => app_id, :client_secret => secret,
|
219
|
+
:type => 'client_cred'}.merge(query)
|
220
|
+
request(opts, [:post, url('oauth/exchange_sessions', q)], &cb)
|
221
|
+
end
|
222
|
+
|
223
|
+
protected
|
224
|
+
def build_env env={}
|
225
|
+
super(env.inject({}){ |r, (k, v)|
|
226
|
+
case k.to_s
|
227
|
+
when 'auto_decode'; r['json_decode' ] = v
|
228
|
+
when 'secret' ; r['oauth_token' ] = secret_oauth_token
|
229
|
+
when 'cache' ; r['cache.update'] = !!!v
|
230
|
+
else ; r[k.to_s] = v
|
231
|
+
end
|
232
|
+
r
|
233
|
+
})
|
234
|
+
end
|
235
|
+
|
236
|
+
def check_sig_and_return_data cookies
|
237
|
+
cookies if secret && if block_given?
|
238
|
+
yield
|
239
|
+
else
|
240
|
+
calculate_sig(cookies)
|
241
|
+
end == cookies['sig']
|
242
|
+
end
|
243
|
+
|
244
|
+
def calculate_sig cookies
|
245
|
+
Digest::MD5.hexdigest(fbs_without_sig(cookies).join + secret)
|
246
|
+
end
|
247
|
+
|
248
|
+
def fbs_without_sig cookies
|
249
|
+
cookies.reject{ |(k, v)| k == 'sig' }.sort.map{ |a| a.join('=') }
|
250
|
+
end
|
251
|
+
|
252
|
+
def merge_data lhs, rhs
|
253
|
+
[lhs, rhs].each{ |hash|
|
254
|
+
return rhs.reject{ |k, v| k == 'paging' } if
|
255
|
+
!hash.kind_of?(Hash) || !hash['data'].kind_of?(Array)
|
256
|
+
}
|
257
|
+
lhs['data'].unshift(*rhs['data'])
|
258
|
+
lhs
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
RestGraph.send(:include, RestGraph::Client)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
RestCore::Builder.client('Twitter', :data) do
|
3
|
+
s = self.class # this is only for ruby 1.8!
|
4
|
+
use s::Timeout , 10
|
5
|
+
|
6
|
+
use s::DefaultSite , 'https://api.twitter.com/'
|
7
|
+
use s::DefaultHeaders, {'Accept' => 'application/json'}
|
8
|
+
|
9
|
+
use s::Oauth1Header ,
|
10
|
+
'oauth/request_token', 'oauth/access_token', 'oauth/authorize'
|
11
|
+
|
12
|
+
use s::CommonLogger , method(:puts)
|
13
|
+
|
14
|
+
use s::Cache , {}, nil do
|
15
|
+
use s::ErrorHandler , lambda{ |env|
|
16
|
+
if (body = env[s::RESPONSE_BODY]).kind_of?(Hash)
|
17
|
+
raise body['error']
|
18
|
+
else
|
19
|
+
raise body
|
20
|
+
end
|
21
|
+
}
|
22
|
+
use s::ErrorDetectorHttp
|
23
|
+
use s::JsonDecode , true
|
24
|
+
run s::Ask
|
25
|
+
end
|
26
|
+
|
27
|
+
use s::Defaults , :data => lambda{{}}
|
28
|
+
|
29
|
+
run s::RestClient
|
30
|
+
end
|
31
|
+
|
32
|
+
module Twitter::Client
|
33
|
+
include RestCore
|
34
|
+
|
35
|
+
def oauth_token
|
36
|
+
data['oauth_token'] if data.kind_of?(Hash)
|
37
|
+
end
|
38
|
+
def oauth_token= token
|
39
|
+
data['oauth_token'] = token if data.kind_of?(Hash)
|
40
|
+
end
|
41
|
+
def oauth_token_secret
|
42
|
+
data['oauth_token_secret'] if data.kind_of?(Hash)
|
43
|
+
end
|
44
|
+
def oauth_token_secret= secret
|
45
|
+
data['oauth_token_secret'] = secret if data.kind_of?(Hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
def tweet status, opt={}
|
49
|
+
post('1/statuses/update.json', {:status => status}.merge(opt))
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def set_token query
|
54
|
+
self.data = query
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Twitter.send(:include, RestCore::ClientOauth1)
|
59
|
+
Twitter.send(:include, Twitter::Client)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module RestCore::ClientOauth1
|
5
|
+
include RestCore
|
6
|
+
|
7
|
+
def authorize_url!
|
8
|
+
set_token(Rack::Utils.parse_query(
|
9
|
+
post(request_token_path, {}, {}, {:json_decode => false})))
|
10
|
+
|
11
|
+
url(authorize_path, :oauth_token => oauth_token, :format => false)
|
12
|
+
end
|
13
|
+
|
14
|
+
def authorize! verifier
|
15
|
+
set_token(Rack::Utils.parse_query(
|
16
|
+
post(access_token_path, {}, {}, {:verifier => verifier,
|
17
|
+
:json_decode => false})))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def set_token query
|
22
|
+
self.oauth_token = query['oauth_token']
|
23
|
+
self.oauth_token_secret = query['oauth_token_secret']
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
module RestCore
|
3
|
+
EventStruct = Struct.new(:duration, :message) unless
|
4
|
+
RestCore.const_defined?(:EventStruct)
|
5
|
+
|
6
|
+
class Event < EventStruct
|
7
|
+
# self.class.name[/(?<=::)\w+$/] if RUBY_VERSION >= '1.9.2'
|
8
|
+
def name; self.class.name[/::(\w+)$/, 1] ; end
|
9
|
+
def to_s; "spent #{duration} #{name} #{message}"; end
|
10
|
+
end
|
11
|
+
class Event::MultiDone < Event; end
|
12
|
+
class Event::Requested < Event; end
|
13
|
+
class Event::CacheHit < Event; end
|
14
|
+
class Event::CacheCleared < Event; end
|
15
|
+
class Event::Failed < Event; end
|
16
|
+
class Event::WithHeader < Event; end
|
17
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
require 'rest-core'
|
3
|
+
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
module RestCore::Middleware
|
7
|
+
include RestCore
|
8
|
+
|
9
|
+
def self.included mod
|
10
|
+
mod.send(:include, RestCore)
|
11
|
+
mod.send(:attr_reader, :app)
|
12
|
+
return unless mod.respond_to?(:members)
|
13
|
+
src = mod.members.map{ |member| <<-RUBY }
|
14
|
+
def #{member} env
|
15
|
+
if env.key?('#{member}')
|
16
|
+
env['#{member}']
|
17
|
+
else
|
18
|
+
@#{member}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
args = [:app] + mod.members
|
23
|
+
para_list = args.map{ |a| "#{a}=nil"}.join(', ')
|
24
|
+
args_list = args .join(', ')
|
25
|
+
ivar_list = args.map{ |a| "@#{a}" }.join(', ')
|
26
|
+
src << <<-RUBY
|
27
|
+
def initialize #{para_list}
|
28
|
+
#{ivar_list} = #{args_list}
|
29
|
+
end
|
30
|
+
self
|
31
|
+
RUBY
|
32
|
+
accessor = Module.new.module_eval(src.join("\n"), __FILE__, __LINE__)
|
33
|
+
mod.const_set(:Accessor, accessor)
|
34
|
+
mod.send(:include, accessor)
|
35
|
+
end
|
36
|
+
|
37
|
+
def call env ; app.call(env) ; end
|
38
|
+
def fail env, obj; env.merge(FAIL => (env[FAIL] || []) + [obj]); end
|
39
|
+
def log env, obj; env.merge(LOG => (env[LOG] || []) + [obj]); end
|
40
|
+
|
41
|
+
module_function
|
42
|
+
def request_uri env
|
43
|
+
# compacting the hash
|
44
|
+
if (query = (env[REQUEST_QUERY] || {}).select{ |k, v| v }).empty?
|
45
|
+
env[REQUEST_PATH].to_s
|
46
|
+
else
|
47
|
+
q = if env[REQUEST_PATH] =~ /\?/ then '&' else '?' end
|
48
|
+
"#{env[REQUEST_PATH]}#{q}" \
|
49
|
+
"#{query.map{ |(k, v)| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
public :request_uri
|
53
|
+
end
|