rufus-jig 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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