rest-core 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -5
- data/CHANGES.md +14 -1
- data/README.md +180 -7
- data/example/multi.rb +6 -4
- data/example/simple.rb +2 -2
- data/example/use-cases.rb +4 -0
- data/lib/rest-core.rb +2 -2
- data/lib/rest-core/engine/em-http-request.rb +5 -6
- data/lib/rest-core/engine/future/future.rb +11 -10
- data/lib/rest-core/engine/future/future_fiber.rb +1 -8
- data/lib/rest-core/version.rb +1 -1
- data/rest-core.gemspec +5 -3
- data/task/.gitignore +1 -0
- data/task/gemgem.rb +267 -0
- metadata +5 -4
data/.gitignore
CHANGED
data/CHANGES.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# CHANGES
|
2
2
|
|
3
|
-
## rest-core 2.0.
|
3
|
+
## rest-core 2.0.1 -- 2013-01-08
|
4
|
+
|
5
|
+
### Bugs fixes
|
6
|
+
|
7
|
+
* Don't walk into parent's constants in `RC.eagerload`.
|
8
|
+
* Also rescue `NameError` in `RC.eagerload`.
|
9
|
+
|
10
|
+
### Enhancement
|
11
|
+
|
12
|
+
* Remove unnecessary `future.wrap` in `EmHttpRequest`.
|
13
|
+
* Introduce Future#callback_in_async.
|
14
|
+
* We would never double resume the fiber, so no need to rescue `FiberError`.
|
15
|
+
|
16
|
+
## rest-core 2.0.0 -- 2012-10-31
|
4
17
|
|
5
18
|
This is a major release which introduces some incompatible changes.
|
6
19
|
This is intended to cleanup some internal implementation and introduce
|
data/README.md
CHANGED
@@ -83,6 +83,8 @@ YourClient = RC::Builder.client do
|
|
83
83
|
end
|
84
84
|
```
|
85
85
|
|
86
|
+
### Basic Usage:
|
87
|
+
|
86
88
|
And use it with per-instance basis (clients could have different
|
87
89
|
configuration, e.g. different cache time or timeout time):
|
88
90
|
|
@@ -96,17 +98,71 @@ client.get('cardinalblue') # cache miss
|
|
96
98
|
client.get('cardinalblue') # cache hit
|
97
99
|
```
|
98
100
|
|
101
|
+
### Concurrent Requests with Futures:
|
102
|
+
|
99
103
|
You can also make concurrent requests easily:
|
100
104
|
(see "Advanced Concurrent HTTP Requests -- Embrace the Future" for detail)
|
101
105
|
|
102
106
|
``` ruby
|
103
|
-
a = [client.get('cardinalblue')
|
107
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
104
108
|
puts "It's not blocking... but doing concurrent requests underneath"
|
105
|
-
p a # here we want the values, so it blocks here
|
109
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
106
110
|
puts "DONE"
|
107
111
|
```
|
108
112
|
|
109
|
-
|
113
|
+
### Exception Handling for Futures:
|
114
|
+
|
115
|
+
Note that since the API call would only block whenever you're looking at
|
116
|
+
the response, it won't raise any exception at the time the API was called.
|
117
|
+
So if you want to block and handle the exception at the time API was called,
|
118
|
+
you would do something like this:
|
119
|
+
|
120
|
+
``` ruby
|
121
|
+
begin
|
122
|
+
response = client.get('bad-user').tap{} # .tap{} is the point
|
123
|
+
do_the_work(response)
|
124
|
+
rescue => e
|
125
|
+
puts "Got an exception: #{e}"
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
The trick here is forcing the future immediately give you the exact response,
|
130
|
+
so that rest-core could see the response and raise the exception. You can
|
131
|
+
call whatever methods on the future to force this behaviour, but since `tap{}`
|
132
|
+
is a method from `Kernel` (which is included in `Object`), it's always
|
133
|
+
available and would return the original value, so it is the easiest method
|
134
|
+
to be remembered and used.
|
135
|
+
|
136
|
+
If you know the response must be a string, then you can also use `to_s`.
|
137
|
+
Like this:
|
138
|
+
|
139
|
+
``` ruby
|
140
|
+
begin
|
141
|
+
response = client.get('bad-user').to_s
|
142
|
+
do_the_work(response)
|
143
|
+
rescue => e
|
144
|
+
puts "Got an exception: #{e}"
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
Or you can also do this:
|
149
|
+
|
150
|
+
``` ruby
|
151
|
+
begin
|
152
|
+
response = client.get('bad-user')
|
153
|
+
response.class # simply force it to load
|
154
|
+
do_the_work(response)
|
155
|
+
rescue => e
|
156
|
+
puts "Got an exception: #{e}"
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
The point is simply making a method call to force it load, whatever method
|
161
|
+
should work.
|
162
|
+
|
163
|
+
### Concurrent Requests with Callbacks:
|
164
|
+
|
165
|
+
On the other hand, callback mode also available:
|
110
166
|
|
111
167
|
``` ruby
|
112
168
|
client.get('cardinalblue'){ |v| p v }
|
@@ -115,6 +171,53 @@ client.wait # we block here to wait for the request done
|
|
115
171
|
puts "DONE"
|
116
172
|
```
|
117
173
|
|
174
|
+
### Exception Handling for Callbacks:
|
175
|
+
|
176
|
+
What about exception handling in callback mode? You know that we cannot
|
177
|
+
raise any exception in the case of using a callback. So rest-core would
|
178
|
+
pass the exception object into your callback. You can handle the exception
|
179
|
+
like this:
|
180
|
+
|
181
|
+
``` ruby
|
182
|
+
client.get('bad-user') do |response|
|
183
|
+
if response.kind_of?(Exception)
|
184
|
+
puts "Got an exception: #{response}"
|
185
|
+
else
|
186
|
+
do_the_work(response)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
puts "It's not blocking... but doing concurrent requests underneath"
|
190
|
+
client.wait # we block here to wait for the request done
|
191
|
+
puts "DONE"
|
192
|
+
```
|
193
|
+
|
194
|
+
### More Control with `request_full`:
|
195
|
+
|
196
|
+
You can also use `request_full` to retrieve everything including response
|
197
|
+
status, response headers, and also other rest-core options. But since using
|
198
|
+
this interface is like using Rack directly, you have to build the env
|
199
|
+
manually. To help you build the env manually, everything has a default,
|
200
|
+
including the path.
|
201
|
+
|
202
|
+
``` ruby
|
203
|
+
client.request_full({})[RC::RESPONSE_BODY] # {"message"=>"Not Found"}
|
204
|
+
# This would print something like this:
|
205
|
+
# RestCore: Auto picked: RestCore::RestClient
|
206
|
+
# RestCore: Future picked: RestCore::Future::FutureThread
|
207
|
+
# RestCore: spent 1.135713 Requested GET https://api.github.com/users//
|
208
|
+
|
209
|
+
client.request_full(RC::REQUEST_PATH => 'cardinalblue')[RC::RESPONSE_STATUS]
|
210
|
+
client.request_full(RC::REQUEST_PATH => 'cardinalblue')[RC::RESPONSE_HEADERS]
|
211
|
+
# Headers are normalized with all upper cases and
|
212
|
+
# dashes are replaced by underscores.
|
213
|
+
|
214
|
+
# To make POST (or any other request methods) request:
|
215
|
+
client.request_full(RC::REQUEST_PATH => 'cardinalblue',
|
216
|
+
RC::REQUEST_METHOD => :post)[RC::RESPONSE_STATUS] # 404
|
217
|
+
```
|
218
|
+
|
219
|
+
### Examples:
|
220
|
+
|
118
221
|
Runnable example is at: [example/simple.rb][]. Please see [rest-more][]
|
119
222
|
for more complex examples to build clients, and [slides][] from
|
120
223
|
[rubyconf.tw/2011][rubyconf.tw] for concepts.
|
@@ -124,6 +227,74 @@ for more complex examples to build clients, and [slides][] from
|
|
124
227
|
[slides]: http://www.godfat.org/slide/2011-08-27-rest-core.html
|
125
228
|
[rubyconf.tw]: http://rubyconf.tw/2011/#6
|
126
229
|
|
230
|
+
## Playing Around:
|
231
|
+
|
232
|
+
You can also play around with `RC::Universal` client, which has installed
|
233
|
+
_all_ reasonable middlewares built-in rest-core. So the above example could
|
234
|
+
also be achieved by:
|
235
|
+
|
236
|
+
``` ruby
|
237
|
+
require 'rest-core'
|
238
|
+
client = RC::Universal.new(:site => 'https://api.github.com/users/',
|
239
|
+
:json_response => true,
|
240
|
+
:log_method => method(:puts))
|
241
|
+
client.get('cardinalblue')
|
242
|
+
```
|
243
|
+
|
244
|
+
`RC::Universal` is defined as:
|
245
|
+
|
246
|
+
``` ruby
|
247
|
+
module RestCore
|
248
|
+
Universal = Builder.client do
|
249
|
+
use Timeout , 0
|
250
|
+
|
251
|
+
use DefaultSite , nil
|
252
|
+
use DefaultHeaders, {}
|
253
|
+
use DefaultQuery , {}
|
254
|
+
use DefaultPayload, {}
|
255
|
+
use JsonRequest , false
|
256
|
+
use AuthBasic , nil, nil
|
257
|
+
|
258
|
+
use FollowRedirect, 10
|
259
|
+
use CommonLogger , method(:puts)
|
260
|
+
use Cache , {}, 600 do
|
261
|
+
use ErrorHandler, nil
|
262
|
+
use ErrorDetectorHttp
|
263
|
+
use JsonResponse, false
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
```
|
268
|
+
|
269
|
+
If you have both [rib][] and [rest-more][] installed, you can also play
|
270
|
+
around with an interactive shell, like this:
|
271
|
+
|
272
|
+
``` shell
|
273
|
+
rib rest-core
|
274
|
+
```
|
275
|
+
|
276
|
+
And you will be entering a rib shell, which `self` is an instance of
|
277
|
+
`RC::Universal` you can play:
|
278
|
+
|
279
|
+
rest-core>> get 'https://api.github.com/users/cardinalblue'
|
280
|
+
|
281
|
+
will print out the response from Github. You can also do this to make
|
282
|
+
calling Github easier:
|
283
|
+
|
284
|
+
rest-core>> self.site = 'https://api.github.com/users/'
|
285
|
+
rest-core>> self.json_response = true
|
286
|
+
|
287
|
+
Then it would do exactly like the original example:
|
288
|
+
|
289
|
+
rest-core>> get 'cardinalblue' # you get a nice parsed hash
|
290
|
+
|
291
|
+
This is mostly for fun and experimenting, so it's only included in
|
292
|
+
[rest-more][] and [rib][]. Please make sure you have both of them
|
293
|
+
installed before trying this.
|
294
|
+
|
295
|
+
[rib]: https://github.com/godfat/rib
|
296
|
+
[rest-more]: https://github.com/cardinalblue/rest-more
|
297
|
+
|
127
298
|
## List of built-in Middlewares:
|
128
299
|
|
129
300
|
* `RC::AuthBasic`
|
@@ -177,9 +348,9 @@ end
|
|
177
348
|
|
178
349
|
client = YourClient.new
|
179
350
|
puts "rest-client with threads doing concurrent requests"
|
180
|
-
a = [client.get('cardinalblue')
|
351
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
181
352
|
puts "It's not blocking... but doing concurrent requests underneath"
|
182
|
-
p a # here we want the values, so it blocks here
|
353
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
183
354
|
puts "DONE"
|
184
355
|
```
|
185
356
|
|
@@ -237,7 +408,8 @@ client = YourClient.new
|
|
237
408
|
puts "eventmachine with threads doing concurrent requests"
|
238
409
|
EM.run{
|
239
410
|
Thread.new{
|
240
|
-
|
411
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
412
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
241
413
|
puts "DONE"
|
242
414
|
EM.stop
|
243
415
|
}
|
@@ -261,7 +433,8 @@ client = YourClient.new
|
|
261
433
|
puts "eventmachine with fibers doing concurrent requests"
|
262
434
|
EM.run{
|
263
435
|
Fiber.new{
|
264
|
-
|
436
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
437
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
265
438
|
puts "DONE"
|
266
439
|
EM.stop
|
267
440
|
}
|
data/example/multi.rb
CHANGED
@@ -12,9 +12,9 @@ end
|
|
12
12
|
|
13
13
|
client = YourClient.new
|
14
14
|
puts "rest-client with threads doing concurrent requests"
|
15
|
-
a = [client.get('cardinalblue')
|
15
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
16
16
|
puts "It's not blocking... but doing concurrent requests underneath"
|
17
|
-
p a # here we want the values, so it blocks here
|
17
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
18
18
|
puts "DONE"
|
19
19
|
|
20
20
|
puts; puts
|
@@ -22,7 +22,8 @@ puts; puts
|
|
22
22
|
puts "eventmachine with threads doing concurrent requests"
|
23
23
|
EM.run{
|
24
24
|
Thread.new{
|
25
|
-
|
25
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
26
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
26
27
|
puts "DONE"
|
27
28
|
EM.stop
|
28
29
|
}
|
@@ -34,7 +35,8 @@ puts; puts
|
|
34
35
|
puts "eventmachine with fibers doing concurrent requests"
|
35
36
|
EM.run{
|
36
37
|
Fiber.new{
|
37
|
-
|
38
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
39
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
38
40
|
puts "DONE"
|
39
41
|
EM.stop
|
40
42
|
}.resume
|
data/example/simple.rb
CHANGED
@@ -16,9 +16,9 @@ p client.get('cardinalblue') # cache hit
|
|
16
16
|
client.cache = false
|
17
17
|
|
18
18
|
puts "concurrent requests"
|
19
|
-
a = [client.get('cardinalblue')
|
19
|
+
a = [client.get('cardinalblue'), client.get('godfat')]
|
20
20
|
puts "It's not blocking... but doing concurrent requests underneath"
|
21
|
-
p a # here we want the values, so it blocks here
|
21
|
+
p a.map{ |r| r['name'] } # here we want the values, so it blocks here
|
22
22
|
puts "DONE"
|
23
23
|
|
24
24
|
puts "callback"
|
data/example/use-cases.rb
CHANGED
@@ -66,12 +66,16 @@ def_use_case 'pure_ruby_nested_concurrent_requests' do
|
|
66
66
|
%w[rubytaiwan godfat].each{ |user|
|
67
67
|
c.get("/users/#{user}/repos", :per_page => 100){ |repos|
|
68
68
|
rs = repos.reject{ |r| r['fork'] }
|
69
|
+
rs = [{}] if rs.size == 1 # out of API limit :(
|
69
70
|
most_watched = rs.max_by{ |r| r['watchers'] }['name']
|
70
71
|
most_size = rs.max_by{ |r| r['size'] }['name']
|
71
72
|
|
72
73
|
watch_contri = c.get("/repos/#{user}/#{most_watched}/contributors")
|
73
74
|
size_contri = c.get("/repos/#{user}/#{most_size}/contributors")
|
74
75
|
|
76
|
+
watch_contri = [{}] if watch_contri.size == 1 # out of API limit :(
|
77
|
+
size_contri = [{}] if size_contri.size == 1 # out of API limit :(
|
78
|
+
|
75
79
|
most_watched_most_contri = watch_contri.max_by{ |c| c['contributions'] }
|
76
80
|
most_size_most_contri = size_contri.max_by{ |c| c['contributions'] }
|
77
81
|
|
data/lib/rest-core.rb
CHANGED
@@ -71,10 +71,10 @@ module RestCore
|
|
71
71
|
def self.eagerload const=self, loaded={}
|
72
72
|
return if loaded[const.name]
|
73
73
|
loaded[const.name] = true
|
74
|
-
const.constants.each{ |n|
|
74
|
+
const.constants(false).each{ |n|
|
75
75
|
begin
|
76
76
|
c = const.const_get(n)
|
77
|
-
rescue LoadError => e
|
77
|
+
rescue LoadError, NameError => e
|
78
78
|
warn "RestCore: WARN: #{e} for #{const}\n" \
|
79
79
|
" from #{e.backtrace.grep(/top.+required/).first}"
|
80
80
|
end
|
@@ -17,18 +17,17 @@ class RestCore::EmHttpRequest
|
|
17
17
|
merge(env[REQUEST_HEADERS]))
|
18
18
|
|
19
19
|
client.callback{
|
20
|
-
future.
|
21
|
-
|
22
|
-
|
23
|
-
client.response_header)}}
|
20
|
+
future.on_load(client.response,
|
21
|
+
client.response_header.status,
|
22
|
+
client.response_header)}
|
24
23
|
|
25
|
-
client.errback{future.
|
24
|
+
client.errback{future.on_error(client.error) }
|
26
25
|
|
27
26
|
env[TIMER].on_timeout{
|
28
27
|
(client.instance_variable_get(:@callbacks)||[]).clear
|
29
28
|
(client.instance_variable_get(:@errbacks )||[]).clear
|
30
29
|
client.close
|
31
|
-
future.
|
30
|
+
future.on_error(env[TIMER].error)
|
32
31
|
} if env[TIMER]
|
33
32
|
|
34
33
|
env.merge(RESPONSE_BODY => future.proxy_body,
|
@@ -58,21 +58,22 @@ class RestCore::Future
|
|
58
58
|
["Future picked: #{self.class}"]))
|
59
59
|
end
|
60
60
|
|
61
|
+
def callback_in_async
|
62
|
+
callback
|
63
|
+
rescue Exception => e
|
64
|
+
# nothing we can do here for an asynchronous exception,
|
65
|
+
# so we just log the error
|
66
|
+
logger = method(:warn) # TODO: add error_log_method
|
67
|
+
logger.call "RestCore: ERROR: #{e}\n from #{e.backtrace.inspect}"
|
68
|
+
end
|
69
|
+
|
61
70
|
def on_load body, status, headers
|
62
71
|
env[TIMER].cancel if env[TIMER]
|
63
72
|
synchronize{
|
64
73
|
self.body, self.status, self.headers = body, status, headers
|
65
|
-
begin
|
66
|
-
# under ASYNC callback, should call immediate
|
67
|
-
next_tick{ callback } if immediate
|
68
|
-
rescue Exception => e
|
69
|
-
# nothing we can do here for an asynchronous exception,
|
70
|
-
# so we just log the error
|
71
|
-
logger = method(:warn) # TODO: add error_log_method
|
72
|
-
logger.call "RestCore: ERROR: #{e}\n" \
|
73
|
-
" from #{e.backtrace.inspect}"
|
74
|
-
end
|
75
74
|
}
|
75
|
+
# under ASYNC callback, should call immediately
|
76
|
+
next_tick{ callback_in_async } if immediate
|
76
77
|
resume # client or response might be waiting
|
77
78
|
end
|
78
79
|
|
@@ -22,14 +22,7 @@ class RestCore::Future::FutureFiber < RestCore::Future
|
|
22
22
|
fibers.clear
|
23
23
|
current_fibers.each{ |f|
|
24
24
|
next unless f.alive?
|
25
|
-
next_tick{
|
26
|
-
begin
|
27
|
-
f.resume
|
28
|
-
rescue FiberError
|
29
|
-
# whenever timeout, it would be already resumed,
|
30
|
-
# and we have no way to tell if it's already resumed or not!
|
31
|
-
end
|
32
|
-
}
|
25
|
+
next_tick{ f.resume }
|
33
26
|
}
|
34
27
|
resume
|
35
28
|
end
|
data/lib/rest-core/version.rb
CHANGED
data/rest-core.gemspec
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "rest-core"
|
5
|
-
s.version = "2.0.
|
5
|
+
s.version = "2.0.1"
|
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 = "
|
11
|
+
s.date = "2013-01-08"
|
12
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-more]: https://github.com/cardinalblue/rest-more"
|
13
13
|
s.email = ["dev (XD) cardinalblue.com"]
|
14
14
|
s.files = [
|
@@ -71,6 +71,8 @@ Gem::Specification.new do |s|
|
|
71
71
|
"lib/rest-core/version.rb",
|
72
72
|
"lib/rest-core/wrapper.rb",
|
73
73
|
"rest-core.gemspec",
|
74
|
+
"task/.gitignore",
|
75
|
+
"task/gemgem.rb",
|
74
76
|
"test/test_auth_basic.rb",
|
75
77
|
"test/test_builder.rb",
|
76
78
|
"test/test_cache.rb",
|
@@ -92,7 +94,7 @@ Gem::Specification.new do |s|
|
|
92
94
|
"test/test_wrapper.rb"]
|
93
95
|
s.homepage = "https://github.com/cardinalblue/rest-core"
|
94
96
|
s.require_paths = ["lib"]
|
95
|
-
s.rubygems_version = "1.8.
|
97
|
+
s.rubygems_version = "1.8.23"
|
96
98
|
s.summary = "Modular Ruby clients interface for REST APIs"
|
97
99
|
s.test_files = [
|
98
100
|
"test/test_auth_basic.rb",
|
data/task/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.rbc
|
data/task/gemgem.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Gemgem
|
5
|
+
class << self
|
6
|
+
attr_accessor :dir, :spec
|
7
|
+
end
|
8
|
+
|
9
|
+
module_function
|
10
|
+
def create
|
11
|
+
yield(spec = Gem::Specification.new{ |s|
|
12
|
+
s.authors = ['Lin Jen-Shin (godfat)']
|
13
|
+
s.email = ['godfat (XD) godfat.org']
|
14
|
+
|
15
|
+
s.description = description.join
|
16
|
+
s.summary = description.first
|
17
|
+
|
18
|
+
s.rubygems_version = Gem::VERSION
|
19
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
20
|
+
s.files = gem_files
|
21
|
+
s.test_files = gem_files.grep(%r{^test/(.+?/)*test_.+?\.rb$})
|
22
|
+
s.executables = Dir['bin/*'].map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = %w[lib]
|
24
|
+
})
|
25
|
+
spec.homepage ||= "https://github.com/godfat/#{spec.name}"
|
26
|
+
spec
|
27
|
+
end
|
28
|
+
|
29
|
+
def readme
|
30
|
+
path = %w[README.md README].find{ |name|
|
31
|
+
File.exist?("#{Gemgem.dir}/#{name}")
|
32
|
+
}
|
33
|
+
@readme ||=
|
34
|
+
if path
|
35
|
+
ps = "##{File.read(path)}".
|
36
|
+
scan(/((#+)[^\n]+\n\n.+?(?=\n\n\2[^#\n]+\n))/m).map(&:first)
|
37
|
+
ps.inject({'HEADER' => ps.first}){ |r, s, i|
|
38
|
+
r[s[/\w+/]] = s
|
39
|
+
r
|
40
|
+
}
|
41
|
+
else
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def description
|
47
|
+
@description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines.to_a
|
48
|
+
end
|
49
|
+
|
50
|
+
def changes
|
51
|
+
path = %w[CHANGES.md CHANGES].find{ |name|
|
52
|
+
File.exist?("#{Gemgem.dir}/#{name}")
|
53
|
+
}
|
54
|
+
@changes ||=
|
55
|
+
if path
|
56
|
+
date = '\d+{4}\-\d+{2}\-\d{2}'
|
57
|
+
File.read(path).match(
|
58
|
+
/([^\n]+#{date}\n\n(.+?))(?=\n\n[^\n]+#{date}\n|\Z)/m)[1]
|
59
|
+
else
|
60
|
+
''
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def ann_md
|
65
|
+
"#{readme['HEADER'].sub(/([\w\-]+)/, "[\\1](#{spec.homepage})")}\n\n" \
|
66
|
+
"##{readme['DESCRIPTION'][/[^\n]+\n\n[^\n]+/]}\n\n" \
|
67
|
+
"### CHANGES:\n\n" \
|
68
|
+
"###{changes}\n\n" \
|
69
|
+
"##{readme['INSTALLATION']}\n\n" +
|
70
|
+
if readme['SYNOPSIS'] then "##{readme['SYNOPSIS'][/[^\n]+\n\n[^\n]+/]}"
|
71
|
+
else '' end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ann_html
|
75
|
+
gem 'nokogiri'
|
76
|
+
gem 'kramdown'
|
77
|
+
|
78
|
+
IO.popen('kramdown', 'r+') do |md|
|
79
|
+
md.puts Gemgem.ann_md
|
80
|
+
md.close_write
|
81
|
+
require 'nokogiri'
|
82
|
+
html = Nokogiri::XML.parse("<gemgem>#{md.read}</gemgem>")
|
83
|
+
html.css('*').each{ |n| n.delete('id') }
|
84
|
+
html.root.children.to_html
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def ann_email
|
89
|
+
"#{readme['HEADER'].sub(/([\w\-]+)/, "\\1 <#{spec.homepage}>")}\n\n" \
|
90
|
+
"#{readme['DESCRIPTION']}\n\n" \
|
91
|
+
"#{readme['INSTALLATION']}\n\n" +
|
92
|
+
if readme['SYNOPSIS'] then "##{readme['SYNOPSIS']}\n\n" else '' end +
|
93
|
+
"## CHANGES:\n\n" \
|
94
|
+
"##{changes}\n\n"
|
95
|
+
end
|
96
|
+
|
97
|
+
def gem_tag
|
98
|
+
"#{spec.name}-#{spec.version}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def write
|
102
|
+
File.open("#{dir}/#{spec.name}.gemspec", 'w'){ |f|
|
103
|
+
f << split_lines(spec.to_ruby) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def split_lines ruby
|
107
|
+
ruby.gsub(/(.+?)\[(.+?)\]/){ |s|
|
108
|
+
if $2.index(',')
|
109
|
+
"#{$1}[\n #{$2.split(',').map(&:strip).join(",\n ")}]"
|
110
|
+
else
|
111
|
+
s
|
112
|
+
end
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
def all_files
|
117
|
+
@all_files ||= find_files(Pathname.new(dir)).map{ |file|
|
118
|
+
if file.to_s =~ %r{\.git/|\.git$}
|
119
|
+
nil
|
120
|
+
else
|
121
|
+
file.to_s
|
122
|
+
end
|
123
|
+
}.compact.sort
|
124
|
+
end
|
125
|
+
|
126
|
+
def gem_files
|
127
|
+
@gem_files ||= all_files - ignored_files
|
128
|
+
end
|
129
|
+
|
130
|
+
def ignored_files
|
131
|
+
@ignored_file ||= all_files.select{ |path| ignore_patterns.find{ |ignore|
|
132
|
+
path =~ ignore && !git_files.include?(path)}}
|
133
|
+
end
|
134
|
+
|
135
|
+
def git_files
|
136
|
+
@git_files ||= if File.exist?("#{dir}/.git")
|
137
|
+
`git ls-files`.split("\n")
|
138
|
+
else
|
139
|
+
[]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# protected
|
144
|
+
def find_files path
|
145
|
+
path.children.select(&:file?).map{|file| file.to_s[(dir.size+1)..-1]} +
|
146
|
+
path.children.select(&:directory?).map{|dir| find_files(dir)}.flatten
|
147
|
+
end
|
148
|
+
|
149
|
+
def ignore_patterns
|
150
|
+
@ignore_files ||= expand_patterns(
|
151
|
+
gitignore.split("\n").reject{ |pattern|
|
152
|
+
pattern.strip == ''
|
153
|
+
}).map{ |pattern| %r{^([^/]+/)*?#{Regexp.escape(pattern)}(/[^/]+)*?$} }
|
154
|
+
end
|
155
|
+
|
156
|
+
def expand_patterns pathes
|
157
|
+
pathes.map{ |path|
|
158
|
+
if path !~ /\*/
|
159
|
+
path
|
160
|
+
else
|
161
|
+
expand_patterns(
|
162
|
+
Dir[path] +
|
163
|
+
Pathname.new(File.dirname(path)).children.select(&:directory?).
|
164
|
+
map{ |prefix| "#{prefix}/#{File.basename(path)}" })
|
165
|
+
end
|
166
|
+
}.flatten
|
167
|
+
end
|
168
|
+
|
169
|
+
def gitignore
|
170
|
+
if File.exist?(path = "#{dir}/.gitignore")
|
171
|
+
File.read(path)
|
172
|
+
else
|
173
|
+
''
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
namespace :gem do
|
179
|
+
|
180
|
+
desc 'Install gem'
|
181
|
+
task :install => [:build] do
|
182
|
+
sh("#{Gem.ruby} -S gem install pkg/#{Gemgem.gem_tag}")
|
183
|
+
end
|
184
|
+
|
185
|
+
desc 'Build gem'
|
186
|
+
task :build => [:spec] do
|
187
|
+
sh("#{Gem.ruby} -S gem build #{Gemgem.spec.name}.gemspec")
|
188
|
+
sh("mkdir -p pkg")
|
189
|
+
sh("mv #{Gemgem.gem_tag}.gem pkg/")
|
190
|
+
end
|
191
|
+
|
192
|
+
desc 'Release gem'
|
193
|
+
task :release => [:spec, :check, :build] do
|
194
|
+
sh("git tag #{Gemgem.gem_tag}")
|
195
|
+
sh("git push")
|
196
|
+
sh("git push --tags")
|
197
|
+
sh("#{Gem.ruby} -S gem push pkg/#{Gemgem.gem_tag}.gem")
|
198
|
+
end
|
199
|
+
|
200
|
+
task :check do
|
201
|
+
ver = Gemgem.spec.version.to_s
|
202
|
+
|
203
|
+
if ENV['VERSION'].nil?
|
204
|
+
puts("\e[35mExpected " \
|
205
|
+
"\e[33mVERSION\e[35m=\e[33m#{ver}\e[0m")
|
206
|
+
exit(1)
|
207
|
+
|
208
|
+
elsif ENV['VERSION'] != ver
|
209
|
+
puts("\e[35mExpected \e[33mVERSION\e[35m=\e[33m#{ver} " \
|
210
|
+
"\e[35mbut got\n " \
|
211
|
+
"\e[33mVERSION\e[35m=\e[33m#{ENV['VERSION']}\e[0m")
|
212
|
+
exit(2)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
end # of gem namespace
|
217
|
+
|
218
|
+
desc 'Run tests in memory'
|
219
|
+
task :test do
|
220
|
+
require 'bacon'
|
221
|
+
Bacon.extend(Bacon::TestUnitOutput)
|
222
|
+
Bacon.summary_on_exit
|
223
|
+
$LOAD_PATH.unshift('lib')
|
224
|
+
Dir['./test/**/test_*.rb'].each{ |file| require file[0..-4] }
|
225
|
+
end
|
226
|
+
|
227
|
+
desc 'Run tests with shell'
|
228
|
+
task 'test:shell', :RUBY_OPTS do |t, args|
|
229
|
+
files = Dir['test/**/test_*.rb'].join(' ')
|
230
|
+
|
231
|
+
cmd = [Gem.ruby, args[:RUBY_OPTS],
|
232
|
+
'-I', 'lib', '-S', 'bacon', '--quiet', files]
|
233
|
+
|
234
|
+
sh(cmd.compact.join(' '))
|
235
|
+
end
|
236
|
+
|
237
|
+
desc 'Generate ann markdown'
|
238
|
+
task 'ann:md' => ['gem:spec'] do
|
239
|
+
puts Gemgem.ann_md
|
240
|
+
end
|
241
|
+
|
242
|
+
desc 'Generate ann html'
|
243
|
+
task 'ann:html' => ['gem:spec'] do
|
244
|
+
puts Gemgem.ann_html
|
245
|
+
end
|
246
|
+
|
247
|
+
desc 'Generate ann email'
|
248
|
+
task 'ann:email' => ['gem:spec'] do
|
249
|
+
puts Gemgem.ann_email
|
250
|
+
end
|
251
|
+
|
252
|
+
desc 'Generate rdoc'
|
253
|
+
task :doc => ['gem:spec'] do
|
254
|
+
sh("yardoc -o rdoc --main README.md" \
|
255
|
+
" --files #{Gemgem.spec.extra_rdoc_files.join(',')}")
|
256
|
+
end
|
257
|
+
|
258
|
+
desc 'Remove ignored files'
|
259
|
+
task :clean => ['gem:spec'] do
|
260
|
+
trash = "~/.Trash/#{Gemgem.spec.name}/"
|
261
|
+
sh "mkdir -p #{trash}" unless File.exist?(File.expand_path(trash))
|
262
|
+
Gemgem.ignored_files.each{ |file| sh "mv #{file} #{trash}" }
|
263
|
+
end
|
264
|
+
|
265
|
+
task :default do
|
266
|
+
puts `#{Gem.ruby} -S #{$PROGRAM_NAME} -T`
|
267
|
+
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: 2.0.
|
4
|
+
version: 2.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2013-01-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rest-client
|
@@ -110,6 +110,8 @@ files:
|
|
110
110
|
- lib/rest-core/version.rb
|
111
111
|
- lib/rest-core/wrapper.rb
|
112
112
|
- rest-core.gemspec
|
113
|
+
- task/.gitignore
|
114
|
+
- task/gemgem.rb
|
113
115
|
- test/test_auth_basic.rb
|
114
116
|
- test/test_builder.rb
|
115
117
|
- test/test_cache.rb
|
@@ -149,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
151
|
version: '0'
|
150
152
|
requirements: []
|
151
153
|
rubyforge_project:
|
152
|
-
rubygems_version: 1.8.
|
154
|
+
rubygems_version: 1.8.23
|
153
155
|
signing_key:
|
154
156
|
specification_version: 3
|
155
157
|
summary: Modular Ruby clients interface for REST APIs
|
@@ -173,4 +175,3 @@ test_files:
|
|
173
175
|
- test/test_timeout.rb
|
174
176
|
- test/test_universal.rb
|
175
177
|
- test/test_wrapper.rb
|
176
|
-
has_rdoc:
|