rufus-jig 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,351 @@
1
+ #--
2
+ # Copyright (c) 2009-2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+ require 'uri'
26
+
27
+ require 'rufus/lru' # gem install rufus-lru
28
+
29
+ require 'rufus/jig/path'
30
+
31
+
32
+ module Rufus::Jig
33
+
34
+ # The classical helper method, does a full copy of the given object.
35
+ # Thanks Marshal.
36
+ #
37
+ def self.marshal_copy (o)
38
+
39
+ Marshal.load(Marshal.dump(o))
40
+ end
41
+
42
+ # Keeping track of the HTTP status code and of the error message.
43
+ #
44
+ class HttpError < RuntimeError
45
+
46
+ attr_reader :status
47
+
48
+ def initialize (status, message)
49
+
50
+ @status = status
51
+ super(message)
52
+ end
53
+ end
54
+
55
+ # The base for the Rufus::Jig::Http class.
56
+ #
57
+ class HttpCore
58
+
59
+ # mostly for debugging purposes
60
+ #
61
+ attr_reader :last_response
62
+
63
+ # the path => [ etag, decoded_body] client cache
64
+ #
65
+ attr_reader :cache
66
+
67
+ # the options for the http client
68
+ #
69
+ attr_reader :options
70
+
71
+ # host and port, vanilla
72
+ #
73
+ attr_reader :host, :port
74
+
75
+ # The class of the error that should be raised when a request is not 2xx.
76
+ #
77
+ attr_accessor :error_class
78
+
79
+ def initialize (host, port, opts)
80
+
81
+ @host = host
82
+ @port = port
83
+ @options = opts
84
+
85
+ @cache = LruHash.new((opts[:cache_size] || 35).to_i)
86
+
87
+ if pf = @options[:prefix]
88
+ @options[:prefix] = "/#{pf}" if (not pf.match(/^\//))
89
+ end
90
+
91
+ @error_class = opts[:error_class] || HttpError
92
+ end
93
+
94
+ def get (path, opts={})
95
+
96
+ request(:get, path, nil, opts)
97
+ end
98
+
99
+ def post (path, data, opts={})
100
+
101
+ request(:post, path, data, opts)
102
+ end
103
+
104
+ def put (path, data, opts={})
105
+
106
+ request(:put, path, data, opts)
107
+ end
108
+
109
+ def delete (path, opts={})
110
+
111
+ request(:delete, path, nil, opts)
112
+ end
113
+
114
+ protected
115
+
116
+ def from_cache (path, opts)
117
+
118
+ if et = opts[:etag]
119
+
120
+ cached = @cache[path]
121
+
122
+ if cached && cached.first != et
123
+ #
124
+ # cached version is perhaps stale
125
+ #
126
+ cached = nil
127
+ opts.delete(:etag)
128
+ end
129
+
130
+ cached
131
+
132
+ else
133
+
134
+ nil
135
+ end
136
+ end
137
+
138
+ def request (method, path, data, opts={})
139
+
140
+ raw = raw_expected?(opts)
141
+
142
+ path = add_prefix(path, opts)
143
+ path = add_params(path, opts)
144
+
145
+ cached = from_cache(path, opts)
146
+ opts.delete(:etag) if not cached
147
+
148
+ opts = rehash_options(opts)
149
+ data = repack_data(data, opts)
150
+
151
+ r = send("do_#{method}", path, data, opts)
152
+
153
+ @last_response = r
154
+
155
+ unless raw
156
+
157
+ return Rufus::Jig.marshal_copy(cached.last) if r.status == 304
158
+ return nil if method == :get && r.status == 404
159
+
160
+ raise @error_class.new(r.status, r.body) \
161
+ if r.status >= 400 && r.status < 600
162
+ end
163
+
164
+ b = decode_body(r, opts)
165
+
166
+ do_cache(method, path, r, b, opts)
167
+
168
+ raw ? r : b
169
+ end
170
+
171
+ def raw_expected? (opts)
172
+
173
+ raw = opts[:raw]
174
+
175
+ raw == false ? false : raw || @options[:raw]
176
+ end
177
+
178
+ # Should work with GET and POST/PUT options
179
+ #
180
+ def rehash_options (opts)
181
+
182
+ opts['Accept'] ||= (opts.delete(:accept) || 'application/json')
183
+
184
+ if ct = opts.delete(:content_type)
185
+ opts['Content-Type'] = ct
186
+ end
187
+ if opts['Content-Type'] == :json
188
+ opts['Content-Type'] = 'application/json'
189
+ end
190
+
191
+ if et = opts.delete(:etag)
192
+ opts['If-None-Match'] = et
193
+ end
194
+
195
+ opts
196
+ end
197
+
198
+ def add_prefix (path, opts)
199
+
200
+ elts = [ path ]
201
+
202
+ if path.match(/^[^\/]/) && prefix = @options[:prefix]
203
+ elts.unshift(prefix)
204
+ end
205
+
206
+ Path.join(*elts)
207
+ end
208
+
209
+ def add_params (path, opts)
210
+
211
+ if params = opts[:params]
212
+
213
+ return path if params.empty?
214
+
215
+ params = params.inject([]) { |a, (k, v)|
216
+ a << "#{k}=#{v}"; a
217
+ }.join("&")
218
+
219
+ return path.index('?') ? "#{path}&#{params}" : "#{path}?#{params}"
220
+ end
221
+
222
+ path
223
+ end
224
+
225
+ def repack_data (data, opts)
226
+
227
+ return data if data.nil? || data.is_a?(String)
228
+
229
+ return Rufus::Jig::Json.encode(data) \
230
+ if (opts['Content-Type'] || '').match(/^application\/json/)
231
+
232
+ data.to_s
233
+ end
234
+
235
+ def do_cache (method, path, response, body, opts)
236
+
237
+ if method == :delete || (opts[:cache] == false)
238
+ @cache.delete(path)
239
+ elsif et = response.headers['Etag']
240
+ @cache[path] = [ et, Rufus::Jig.marshal_copy(body) ]
241
+ end
242
+ end
243
+
244
+ def decode_body (response, opts)
245
+
246
+ b = response.body
247
+ ct = response.headers['Content-Type']
248
+
249
+ if ct && ct.match(/^application\/json/)
250
+ Rufus::Jig::Json.decode(b)
251
+ else
252
+ b
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+
259
+ if defined?(Patron) # gem install patron
260
+
261
+ class Rufus::Jig::Http < Rufus::Jig::HttpCore
262
+
263
+ def initialize (host, port, opts={})
264
+
265
+ super(host, port, opts)
266
+
267
+ @patron = Patron::Session.new
268
+ @patron.base_url = "#{host}:#{port}"
269
+
270
+ @patron.headers['User-Agent'] =
271
+ opts[:user_agent] || "#{self.class} #{Rufus::Jig::VERSION}"
272
+ end
273
+
274
+ protected
275
+
276
+ def do_get (path, data, opts)
277
+
278
+ @patron.get(path, opts)
279
+ end
280
+
281
+ def do_post (path, data, opts)
282
+
283
+ @patron.post(path, data, opts)
284
+ end
285
+
286
+ def do_put (path, data, opts)
287
+
288
+ @patron.put(path, data, opts)
289
+ end
290
+
291
+ def do_delete (path, data, opts)
292
+
293
+ @patron.delete(path, opts)
294
+ end
295
+ end
296
+
297
+ else
298
+
299
+ # TODO : use Net:HTTP
300
+
301
+ raise "alternative to Patron not yet integrated :( gem install patron"
302
+
303
+ end
304
+
305
+ #--
306
+ #
307
+ # re-opening the HTTP class to add some class methods
308
+ #
309
+ #++
310
+ class Rufus::Jig::Http
311
+
312
+ # Makes sense of arguments and extract an array that goes like
313
+ # [ http, path, payload, opts ].
314
+ #
315
+ # Typical input :
316
+ #
317
+ # a = Rufus::Jig::Http.extract_http(false, 'http://127.0.0.1:5984')
318
+ # a = Rufus::Jig::Http.extract_http(false, '127.0.0.1', 5984, '/')
319
+ # a = Rufus::Jig::Http.extract_http(true, 'http://127.0.0.1:5984', :payload)
320
+ #
321
+ def self.extract_http (payload_expected, *args)
322
+
323
+ http = case args.first
324
+
325
+ when Rufus::Jig::Http
326
+ args.shift
327
+
328
+ when /^http:\/\//
329
+ u = URI.parse(args.shift)
330
+ args.unshift(u.path)
331
+ Rufus::Jig::Http.new(u.host, u.port)
332
+
333
+ else
334
+ Rufus::Jig::Http.new(args.shift, args.shift)
335
+ end
336
+
337
+ path = args.shift
338
+ path = '/' if path == ''
339
+
340
+ payload = payload_expected ? args.shift : nil
341
+
342
+ opts = args.shift || {}
343
+
344
+ raise(
345
+ ArgumentError.new("option Hash expected, not #{opts.inspect}")
346
+ ) unless opts.is_a?(Hash)
347
+
348
+ [ http, path, payload, opts ]
349
+ end
350
+ end
351
+
@@ -0,0 +1,93 @@
1
+ #--
2
+ # Copyright (c) 2009-2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Rufus::Jig
27
+
28
+ #
29
+ # Dealing with multiple JSON libs. Favours yajl-ruby.
30
+ #
31
+ # Lifted from http://github.com/jmettraux/ruote
32
+ #
33
+ module Json
34
+
35
+ # The JSON / JSON pure decoder
36
+ #
37
+ JSON = [
38
+ lambda { |o| o.to_json },
39
+ lambda { |s| ::JSON.parse(s) }
40
+ ]
41
+
42
+ # The Rails ActiveSupport::JSON decoder
43
+ #
44
+ ACTIVE_SUPPORT = [
45
+ lambda { |o| o.to_json },
46
+ lambda { |s| ActiveSupport::JSON.decode(s) }
47
+ ]
48
+
49
+ # http://github.com/brianmario/yajl-ruby/
50
+ #
51
+ YAJL = [
52
+ lambda { |o| Yajl::Encoder.encode(o) },
53
+ lambda { |s| Yajl::Parser.parse(s) }
54
+ ]
55
+
56
+ # The "raise an exception because there's no backend" backend
57
+ #
58
+ NONE = [ lambda { |s| raise 'no JSON backend found' } ] * 2
59
+
60
+ @backend = if defined?(::Yajl)
61
+ YAJL
62
+ elsif defined?(::JSON)
63
+ JSON
64
+ elsif defined?(ActiveSupport::JSON)
65
+ ACTIVE_SUPPORT
66
+ else
67
+ NONE
68
+ end
69
+
70
+ # Forces a decoder JSON/ACTIVE_SUPPORT or any lambda pair that knows
71
+ # how to deal with JSON.
72
+ #
73
+ def self.backend= (b)
74
+
75
+ @backend = b
76
+ end
77
+
78
+ # Encodes the given object to JSON.
79
+ #
80
+ def self.encode (o)
81
+
82
+ @backend[0].call(o)
83
+ end
84
+
85
+ # Decodes the given JSON string.
86
+ #
87
+ def self.decode (s)
88
+
89
+ @backend[1].call(s)
90
+ end
91
+ end
92
+ end
93
+
@@ -0,0 +1,99 @@
1
+ #--
2
+ # Copyright (c) 2009-2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Rufus::Jig
27
+
28
+ #
29
+ # Some helper methods for URI paths.
30
+ #
31
+ module Path
32
+
33
+ # Given a path and a hash of options, ...
34
+ #
35
+ # p Rufus::Jig::Path.add_params('/toto', :q => 'nada')
36
+ # # => "/toto?q=nada"
37
+ #
38
+ # p Rufus::Jig::Path.add_params('/toto?rev=2', :q => 'nada')
39
+ # # => "/toto?rev=2&q=nada"
40
+ #
41
+ # p Rufus::Jig::Path.add_params('/toto', :q => 'nada', 'x' => 2)
42
+ # # => "/toto?q=nada&x=2"
43
+ #
44
+ def self.add_params (path, h)
45
+
46
+ params = h.collect { |k, v| "#{k}=#{v}" }.join('&')
47
+
48
+ sep = path.index('?') ? '&' : '?'
49
+
50
+ params.length > 0 ? "#{path}#{sep}#{params}" : path
51
+ end
52
+
53
+ # Joins paths into a '/' prefixed path.
54
+ #
55
+ # p Rufus::Jig::Path.join('division', 'customer', :restricted => true)
56
+ # # => '/division/customer?restricted=true'
57
+ #
58
+ def self.join (*elts)
59
+
60
+ elts, params = if elts.last.is_a?(Hash)
61
+ [ elts[0..-2], elts.last ]
62
+ else
63
+ [ elts, {} ]
64
+ end
65
+
66
+ r = elts.collect { |e|
67
+
68
+ e = to_path(e)
69
+
70
+ if m = e.match(/^(.*)\/$/)
71
+ m[1]
72
+ else
73
+ e
74
+ end
75
+ }.join
76
+
77
+ add_params(r, params)
78
+ end
79
+
80
+ # Makes sure there is a forward slash in the given string.
81
+ #
82
+ def self.to_path (s)
83
+
84
+ s.match(/^\//) ? s : "/#{s}"
85
+ end
86
+
87
+ # Removes any forward slashes at the beginning of the given string.
88
+ #
89
+ def self.to_name (s)
90
+
91
+ if m = s.match(/^\/+(.+)$/)
92
+ m[1]
93
+ else
94
+ s
95
+ end
96
+ end
97
+ end
98
+ end
99
+
data/lib/rufus/jig.rb ADDED
@@ -0,0 +1,38 @@
1
+ #--
2
+ # Copyright (c) 2009-2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Rufus
27
+ module Jig
28
+
29
+ require 'rufus/jig/http'
30
+ require 'rufus/jig/json'
31
+
32
+ VERSION = '0.0.1'
33
+
34
+ autoload :Couch, 'rufus/jig/couch'
35
+ autoload :CouchError, 'rufus/jig/couch'
36
+ end
37
+ end
38
+
data/lib/rufus-jig.rb ADDED
@@ -0,0 +1,3 @@
1
+
2
+ require 'rufus/jig'
3
+
data/put.txt ADDED
@@ -0,0 +1,25 @@
1
+
2
+ $ curl -vX PUT 'http://localhost:5984/nada/doc3' -d '{"ok":"surf"}'
3
+ * About to connect() to localhost port 5984 (#0)
4
+ * Trying 127.0.0.1... connected
5
+ * Connected to localhost (127.0.0.1) port 5984 (#0)
6
+ > PUT /nada/doc3 HTTP/1.1
7
+ > User-Agent: curl/7.19.6 (i386-apple-darwin10.0.0) libcurl/7.19.6 zlib/1.2.3
8
+ > Host: localhost:5984
9
+ > Accept: */*
10
+ > Content-Length: 13
11
+ > Content-Type: application/x-www-form-urlencoded
12
+ >
13
+ < HTTP/1.1 201 Created
14
+ < Server: CouchDB/0.10.0 (Erlang OTP/R13B)
15
+ < Location: http://localhost:5984/nada/doc3
16
+ < Etag: "1-34de3386a65dc370c7989ec08155a4d2"
17
+ < Date: Sun, 01 Nov 2009 05:43:35 GMT
18
+ < Content-Type: text/plain;charset=utf-8
19
+ < Content-Length: 67
20
+ < Cache-Control: must-revalidate
21
+ <
22
+ {"ok":true,"id":"doc3","rev":"1-34de3386a65dc370c7989ec08155a4d2"}
23
+ * Connection #0 to host localhost left intact
24
+ * Closing connection #0
25
+
data/test/test.rb ADDED
@@ -0,0 +1,28 @@
1
+
2
+ #
3
+ # testing rufus-jig
4
+ #
5
+ # Sat Oct 31 22:44:08 JST 2009
6
+ #
7
+
8
+ def load_tests (prefix)
9
+
10
+ dp = File.dirname(__FILE__)
11
+
12
+ Dir.new(dp).entries.select { |e|
13
+ e.match(/^#{prefix}\_.*\.rb$/)
14
+ }.sort.each { |e|
15
+ load("#{dp}/#{e}")
16
+ }
17
+ end
18
+
19
+ set = if ARGV.include?('--all')
20
+ %w[ ut ct ]
21
+ elsif ARGV.include?('--couch')
22
+ %w[ ct ]
23
+ else
24
+ %w[ ut ]
25
+ end
26
+
27
+ set.each { |prefix| load_tests(prefix) }
28
+
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rufus-jig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - John Mettraux
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-09 00:00:00 +09:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "\n Json Internet Get.\n\n An HTTP client, greedy with JSON content, GETting conditionally.\n\n Uses Patron and Yajl-ruby whenever possible.\n "
17
+ email: jmettraux@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - CHANGELOG.txt
25
+ - CREDITS.txt
26
+ - LICENSE.txt
27
+ files:
28
+ - lib/rufus/jig/couch.rb
29
+ - lib/rufus/jig/http.rb
30
+ - lib/rufus/jig/json.rb
31
+ - lib/rufus/jig/path.rb
32
+ - lib/rufus/jig.rb
33
+ - lib/rufus-jig.rb
34
+ - CHANGELOG.txt
35
+ - CREDITS.txt
36
+ - LICENSE.txt
37
+ - TODO.txt
38
+ - delete.txt
39
+ - put.txt
40
+ - README.rdoc
41
+ has_rdoc: true
42
+ homepage: http://github.com/jmettraux/rufus-jig
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project: rufus
65
+ rubygems_version: 1.3.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: An HTTP client, greedy with JSON content, GETting conditionally.
69
+ test_files:
70
+ - test/test.rb