rest-core 2.0.0 → 2.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 +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:
|