rested 0.2.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.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.swo
2
+ *.swp
3
+ pkg
data/README.rdoc ADDED
@@ -0,0 +1,96 @@
1
+ = rested
2
+
3
+ * http://github.com/chetan/rested
4
+
5
+ == DESCRIPTION:
6
+
7
+ Ruby library built on top of httpclient for working with RESTful APIs.
8
+
9
+ == DOCUMENTATION:
10
+
11
+ Documentation is available online at at rdoc.info[http://rdoc.info/projects/chetan/rested]
12
+
13
+ == FEATURES:
14
+
15
+ == SYNOPSIS:
16
+
17
+ class Foo < Rested::Entity
18
+ base_url "http://api.example.com/entities/"
19
+ endpoint "foo"
20
+ user "user@example.com"
21
+ pass "foobar"
22
+
23
+ field :id, :baz, :bar
24
+ end
25
+
26
+ foo = Foo.find(1)
27
+ foo.baz = "frobnicator"
28
+ foo.save!
29
+
30
+ moe = Foo.new
31
+ moe.bar = "curly"
32
+ moe.save!
33
+ puts "new id is #{moe.id}"
34
+
35
+ # to upload a file
36
+ moe.add_file("param_name", "/path/to/file")
37
+ moe.save!
38
+
39
+ # non-entity based example
40
+
41
+ class Baz < Rested::Base
42
+ base_url "http://api.example.com/methods/"
43
+ user "user@example.com"
44
+ pass "foobar"
45
+
46
+ def self.search(terms)
47
+ params = {}
48
+ params["terms"] = terms
49
+ ret = post("/search", params)
50
+ ret["search_results"]
51
+ end
52
+
53
+ end
54
+
55
+
56
+ == REQUIREMENTS:
57
+
58
+ * httpclient
59
+ * json
60
+
61
+ == INSTALL:
62
+
63
+ With gemcutter:
64
+
65
+ sudo gem install rested
66
+
67
+ Without gemcutter:
68
+
69
+ git clone git://github.com/chetan/rested.git
70
+ cd rested
71
+ sudo rake install
72
+
73
+ == LICENSE:
74
+
75
+ (The MIT License)
76
+
77
+ Copyright (c) 2010 Better Advertising, Inc.
78
+
79
+ Permission is hereby granted, free of charge, to any person obtaining
80
+ a copy of this software and associated documentation files (the
81
+ 'Software'), to deal in the Software without restriction, including
82
+ without limitation the rights to use, copy, modify, merge, publish,
83
+ distribute, sublicense, and/or sell copies of the Software, and to
84
+ permit persons to whom the Software is furnished to do so, subject to
85
+ the following conditions:
86
+
87
+ The above copyright notice and this permission notice shall be
88
+ included in all copies or substantial portions of the Software.
89
+
90
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
91
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
92
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
93
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
94
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
95
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
96
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+
2
+ begin
3
+ require 'rubygems'
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "rested"
7
+ gemspec.summary = "Ruby library for working with RESTful APIs"
8
+ gemspec.description = "Ruby library built on top of httpclient for working with RESTful APIs."
9
+ gemspec.email = "chetan@betteradvertising.com"
10
+ gemspec.homepage = ""
11
+ gemspec.authors = ["Chetan Sarva"]
12
+ gemspec.add_dependency('httpclient', '>= 2.1.5.2')
13
+ gemspec.add_dependency('json', '>= 1.4.2')
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require "rake/testtask"
21
+ desc "Run unit tests"
22
+ Rake::TestTask.new("test") { |t|
23
+ #t.libs << "test"
24
+ t.ruby_opts << "-rubygems"
25
+ t.pattern = "test/**/*_test.rb"
26
+ t.verbose = false
27
+ t.warning = false
28
+ }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,97 @@
1
+
2
+ require 'rubygems'
3
+ require 'httpclient'
4
+ require 'json'
5
+
6
+ module Rested
7
+
8
+ module BaseMethods
9
+
10
+ # override this method to customize HTTPClient
11
+ def setup_client
12
+ @client = ::HTTPClient.new
13
+ if self.user and self.pass then
14
+ @client.set_auth(self.base_url, self.user, self.pass)
15
+ end
16
+ @client.debug_dev = STDOUT if Rested.debug
17
+ @client
18
+ end
19
+
20
+ def client
21
+ @client ||= setup_client()
22
+ end
23
+
24
+ def get(uri, params = nil)
25
+ url = self.base_url + uri
26
+ Rested.log_out{"GET #{url}"}
27
+ Rested.log_out{" params: " + params.inspect}
28
+ handle_response(self.client.get(url, params))
29
+ end
30
+
31
+ def post(uri, params = nil)
32
+ url = self.base_url + uri
33
+ Rested.log_out{"POST #{url}"}
34
+ Rested.log_out{" params: " + params.inspect}
35
+ handle_response(self.client.post(url, params))
36
+ end
37
+
38
+ def delete(uri)
39
+ url = self.base_url + uri
40
+ Rested.log_out{"DELETE #{url}"}
41
+ res = self.client.delete(url)
42
+ handle_error(res) if message.status >= 400
43
+ return res.status == 200
44
+ end
45
+
46
+ def handle_response(message)
47
+ handle_error(message) if message.status >= 400
48
+ Rested.log_in{ puts; "HTTP/#{message.version} #{message.status} #{message.reason}"}
49
+ Rested.log_in{ message.header.all.each{|h| Rested.log_in("#{h[0]}: #{h[1]}")}; puts }
50
+ decode_response(message)
51
+ end
52
+
53
+ def decode_response(message)
54
+ ct = message.contenttype
55
+ if ct =~ /json|javascript/ then
56
+ decode_json_response(message)
57
+ elsif ct =~ /xml/ then
58
+ decode_xml_response(message)
59
+ else
60
+ Rested.log_in { "\n" + message.content }
61
+ raise "Unknown response type"
62
+ end
63
+ end
64
+
65
+ def decode_json_response(message)
66
+ begin
67
+ JSON.load(message.content)
68
+ rescue => ex
69
+ nil
70
+ end
71
+ end
72
+
73
+ def decode_xml_response(message)
74
+ raise NotImplementedError # TODO
75
+ end
76
+
77
+ def handle_error(message)
78
+ Rested.log_in{ puts; "HTTP/#{message.version} #{message.status} #{message.reason}"}
79
+ Rested.log_in{ message.header.all.each{|h| Rested.log_in("#{h[0]}: #{h[1]}")}; puts }
80
+ raise Rested::Error.new(message)
81
+ end
82
+
83
+ end
84
+
85
+ class Base
86
+
87
+ rattr_accessor :base_url, :user, :pass
88
+
89
+ class << self
90
+ include BaseMethods
91
+ end
92
+
93
+ include BaseMethods
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,59 @@
1
+ module Rested
2
+
3
+ @debug = false
4
+
5
+ def self.debug(val=nil)
6
+ return @debug unless val
7
+ if val == true then
8
+ @debug = STDOUT
9
+ else
10
+ @debug = val
11
+ end
12
+ end
13
+
14
+ def self.debug=(val)
15
+ self.debug(val)
16
+ end
17
+
18
+ def self.log(msg = nil, &block)
19
+ return unless @debug
20
+ if block_given? then
21
+ s = yield
22
+ return if not s.kind_of? String
23
+ @debug.puts(s)
24
+ elsif not msg.nil? then
25
+ @debug.puts(msg)
26
+ end
27
+ end
28
+
29
+ def self.log_do(msg = nil, &block)
30
+ if block_given? then
31
+ s = yield
32
+ return if not s.kind_of? String
33
+ log { "* " + s }
34
+ else
35
+ log("* #{msg}")
36
+ end
37
+ end
38
+
39
+ def self.log_in(msg = nil, &block)
40
+ if block_given? then
41
+ s = yield
42
+ return if not s.kind_of? String
43
+ log { "< " + s }
44
+ else
45
+ log("< #{msg}")
46
+ end
47
+ end
48
+
49
+ def self.log_out(msg = nil, &block)
50
+ if block_given? then
51
+ s = yield
52
+ return if not s.kind_of? String
53
+ log { "> " + s }
54
+ else
55
+ log("> #{msg}")
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,247 @@
1
+ require File.dirname(__FILE__) + '/ext'
2
+ require File.dirname(__FILE__) + '/base'
3
+
4
+ module Rested
5
+
6
+ class Entity < Base
7
+
8
+ rattr_accessor :endpoint, :id_field
9
+ attr_accessor :errors
10
+ Entity.id_field(:id)
11
+
12
+ class << self
13
+
14
+ attr_reader :before_filters, :after_filters
15
+
16
+ def before_filters
17
+ @before_filters ||= []
18
+ end
19
+
20
+ def after_filters
21
+ @after_filters ||= []
22
+ end
23
+
24
+ def before_save(&block)
25
+ before_filters << block
26
+ end
27
+
28
+ def after_save(&block)
29
+ after_filters << block
30
+ end
31
+
32
+ def id_field
33
+ @id_field ||= inherit_static_from_super(:id_field) || nil
34
+ end
35
+
36
+ def inherit_static_from_super(sym)
37
+ if superclass.respond_to? sym then
38
+ val = superclass.send(sym)
39
+ return val if val.kind_of? Symbol
40
+ return val.dup if not val.nil?
41
+ end
42
+ return nil
43
+ end
44
+
45
+ def fields
46
+ @fields ||= inherit_static_from_super(:fields) || []
47
+ end
48
+
49
+ def field(*args)
50
+ args.each do |f|
51
+ add_field(f) unless fields.include? f
52
+ end
53
+ end
54
+
55
+ def delimited_fields
56
+ @delimited_fields ||= {}
57
+ end
58
+
59
+ def delimited_field(field, delimiter = ',')
60
+ unless fields.include? field
61
+ delimited_fields[field] = delimiter
62
+ add_field(field)
63
+ end
64
+ end
65
+
66
+ def add_field(field)
67
+ self.fields << field
68
+ attr_accessor field
69
+ end
70
+
71
+ def find(id = nil, masquerade = nil)
72
+ uri = self.endpoint
73
+ uri += "/#{id}" if not id.nil?
74
+ begin
75
+ json = get(uri, :masquerade => masquerade)
76
+ rescue Rested::Error => ex
77
+ if ex.message =~ /Invalid/ then
78
+ raise ObjectNotFound.new(ex.http_response)
79
+ end
80
+ end
81
+ if id.nil? then
82
+ # return as list
83
+ return json.values.first.map { |j| new(j) }
84
+ end
85
+ return nil if json.values.empty?
86
+ return new(json.values.first)
87
+ end
88
+
89
+ def list
90
+ find()
91
+ end
92
+
93
+ end
94
+
95
+ def files
96
+ @files ||= {}
97
+ end
98
+
99
+ def add_file(name, file)
100
+ if file.kind_of? String then
101
+ raise IOError.new("File not found: #{file}") if not File.exists? file
102
+ file = File.new(file)
103
+ elsif file.is_a? Tempfile
104
+ file = File.new(file.path)
105
+ end
106
+ return if not file.kind_of? File
107
+ self.files[name] = file
108
+ end
109
+
110
+ def initialize(*args)
111
+ if args.kind_of? Hash then
112
+ h = args
113
+ elsif args.kind_of? Array and args.first.kind_of? Hash then
114
+ h = args.first
115
+ end
116
+ set_values(h) if h
117
+ self.errors = []
118
+ end
119
+
120
+ def set_values(h)
121
+ h.each_pair do |name, value|
122
+ writer_method = "#{name}="
123
+ value = parse_value(name, value)
124
+ if respond_to?(writer_method)
125
+ send(writer_method, value)
126
+ else
127
+ self[name.to_s] = value
128
+ end
129
+ end
130
+ end
131
+
132
+ def parse_value(name, value)
133
+ if !value.nil? then
134
+ if delimited_fields.include?(name.to_sym) then
135
+ value = value.split(delimited_fields[name.to_sym]) if value.is_a?(String)
136
+ value = value.map(&:to_i) if value.first.is_a?(String) && value.all?{ |v| v.to_i.to_s == v }
137
+ else
138
+ value = value.to_i if value.is_a?(String) && value.to_i.to_s == value
139
+ end
140
+ value
141
+ end
142
+ end
143
+
144
+ def id_val
145
+ self.send(id_field)
146
+ end
147
+
148
+ def id_val=(val)
149
+ self.send("#{id_field.to_s}=", val)
150
+ end
151
+
152
+ def new_record?
153
+ self.new?
154
+ end
155
+
156
+ def new?
157
+ self.id_val.nil?
158
+ end
159
+
160
+ def [](name)
161
+ begin
162
+ send(name)
163
+ rescue NoMethodError
164
+ nil
165
+ end
166
+ end
167
+
168
+ def []=(name, value)
169
+ begin
170
+ send("#{name}=", value)
171
+ rescue NoMethodError
172
+ self.class.add_field(name)
173
+ send("#{name}=", value)
174
+ end
175
+ end
176
+
177
+ def to_h
178
+ h = {}
179
+ fields.each do |f|
180
+ val = self.send(f)
181
+ h[f] = val && delimited_fields.include?(f) ? val.join(delimited_fields[f]) : val
182
+ end
183
+ h
184
+ end
185
+
186
+ def to_json
187
+ JSON.generate(to_h())
188
+ end
189
+
190
+ def to_s
191
+ to_json()
192
+ end
193
+
194
+ def update_attributes(attributes = {})
195
+ attributes.each_pair do |field, value|
196
+ send("#{field}=", value)
197
+ end
198
+ save
199
+ end
200
+
201
+ def save
202
+ begin
203
+ save!
204
+ rescue Rested::Error => e
205
+ self.errors = e.validations
206
+ false
207
+ end
208
+ end
209
+
210
+ def save!(params = nil)
211
+ self.class.before_filters.each do |f|
212
+ f.call(self)
213
+ end
214
+ uri = self.endpoint
215
+ uri += "/#{self.id_val}" unless new?
216
+ params = to_h() if not params
217
+ params.delete(self.id_field) if new?
218
+ unless self.files.empty?
219
+ params.merge!(self.files)
220
+ @files = {}
221
+ end
222
+ ret = self.post(uri, params)
223
+ if new? then
224
+ self.id_val = self.class.new(ret.values.first).id_val
225
+ end
226
+ self.class.after_filters.each do |f|
227
+ f.call(self)
228
+ end
229
+ true
230
+ end
231
+
232
+ def delete!
233
+ return if new?
234
+ uri = self.endpoint + "/#{self.id_val}"
235
+ self.delete(uri)
236
+ true
237
+ end
238
+
239
+ def fields
240
+ @fields ||= self.class.fields
241
+ end
242
+
243
+ def delimited_fields
244
+ @delimited_fields ||= self.class.delimited_fields
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,57 @@
1
+
2
+ module Rested
3
+
4
+ class Error < ::Exception
5
+
6
+ attr_accessor :status, :reason, :http_response, :message, :validations
7
+
8
+ def initialize(res)
9
+
10
+ hash = Rested::Base.decode_response(res)
11
+ if hash.include? "error" then
12
+ self.message = hash["error"]
13
+ end
14
+ self.validations = Validations.new(hash['validations'])
15
+ self.status = res.status
16
+ self.reason = res.reason
17
+ self.http_response = res
18
+
19
+ end
20
+
21
+ def to_s
22
+ "#{self.status} #{self.reason}" + (self.message.nil? ? "" : ": #{self.message}")
23
+ end
24
+
25
+ # def self.handle(res)
26
+ # case res.status
27
+ # when 400
28
+ # raise "Bad request"
29
+ # when 401
30
+ # raise "401 Unauthorized"
31
+ # when 403
32
+ # raise "403 Forbidden"
33
+ # when 404
34
+ # raise "404 Not Found"
35
+ # when 405
36
+ # raise "405 Method Not Allowed"
37
+ # else
38
+ # raise "#{res.status} #{res.reason}" if res.status >= 400
39
+ # end
40
+ # end
41
+
42
+ end
43
+
44
+ class ObjectNotFound < Error
45
+ attr_accessor :id
46
+
47
+ def initialize(res)
48
+ super(res)
49
+ self.id = $1 if res.content =~ /Invalid Object.*?(\d+)/
50
+ end
51
+
52
+ def to_s
53
+ "Invalid Object ID '#{self.id}'"
54
+ end
55
+ end
56
+
57
+ end
data/lib/rested/ext.rb ADDED
@@ -0,0 +1,97 @@
1
+
2
+ # orginally cribbed from Rails edge. modified a bunch.
3
+
4
+ class Hash
5
+ # By default, only instances of Hash itself are extractable.
6
+ # Subclasses of Hash may implement this method and return
7
+ # true to declare themselves as extractable. If a Hash
8
+ # is extractable, Array#extract_options! pops it from
9
+ # the Array when it is the last element of the Array.
10
+ def extractable_options?
11
+ instance_of?(Hash)
12
+ end
13
+ end if not {}.respond_to? :extractable_options?
14
+
15
+ class Array
16
+ # Extracts options from a set of arguments. Removes and returns the last
17
+ # element in the array if it's a hash, otherwise returns a blank hash.
18
+ #
19
+ # def options(*args)
20
+ # args.extract_options!
21
+ # end
22
+ #
23
+ # options(1, 2) # => {}
24
+ # options(1, 2, :a => :b) # => {:a=>:b}
25
+ def extract_options!
26
+ if last.is_a?(Hash) && last.extractable_options?
27
+ pop
28
+ else
29
+ {}
30
+ end
31
+ end
32
+ end if not [].respond_to? :extract_options!
33
+
34
+ # Extends the class object with class and instance accessors for attributes,
35
+ # just like the native attr* accessors for instance attributes. Attributes can be
36
+ # set at the class level and overriden at the instance level as well.
37
+ #
38
+ # class Person
39
+ # cattr_accessor :hair_colors
40
+ # end
41
+ #
42
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
43
+ class Class
44
+ def rattr_reader(*syms)
45
+ options = syms.extract_options!
46
+ syms.each do |sym|
47
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
48
+ unless defined? @#{sym}
49
+ @#{sym} = nil
50
+ end
51
+
52
+ def self.#{sym}(#{sym} = nil)
53
+ return @#{sym} unless #{sym}
54
+ @#{sym} = #{sym}
55
+ end
56
+ EOS
57
+
58
+ unless options[:instance_reader] == false
59
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
60
+ def #{sym}
61
+ return @#{sym} if @#{sym}
62
+ self.class.#{sym}
63
+ end
64
+ EOS
65
+ end
66
+ end
67
+ end
68
+
69
+ def rattr_writer(*syms)
70
+ options = syms.extract_options!
71
+ syms.each do |sym|
72
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
73
+ unless defined? @#{sym}
74
+ @#{sym} = nil
75
+ end
76
+
77
+ def self.#{sym}=(obj)
78
+ @#{sym} = obj
79
+ end
80
+ EOS
81
+
82
+ unless options[:instance_writer] == false
83
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
84
+ def #{sym}=(obj)
85
+ @#{sym} = obj
86
+ end
87
+ EOS
88
+ end
89
+ self.send("#{sym}=", yield) if block_given?
90
+ end
91
+ end
92
+
93
+ def rattr_accessor(*syms, &blk)
94
+ rattr_reader(*syms)
95
+ rattr_writer(*syms, &blk)
96
+ end
97
+ end if not Class.respond_to? :rattr_accessor
@@ -0,0 +1,25 @@
1
+ module Rested
2
+ class Validations
3
+ def initialize(validations_hash)
4
+ self.validations_hash = validations_hash || {}
5
+ end
6
+
7
+ def full_messages
8
+ validations_hash.inject([]) do |messages, validation|
9
+ messages << "#{humanize(validation.first)} #{validation.last}"
10
+ end
11
+ end
12
+
13
+ def count
14
+ validations_hash.size
15
+ end
16
+
17
+ private
18
+
19
+ def humanize(field)
20
+ field.gsub(/[A-Z]/){ " #{$&.downcase}"}.capitalize
21
+ end
22
+
23
+ attr_accessor :validations_hash
24
+ end
25
+ end
data/lib/rested.rb ADDED
@@ -0,0 +1,7 @@
1
+
2
+ require File.dirname(__FILE__) + '/rested/debug'
3
+ require File.dirname(__FILE__) + '/rested/ext'
4
+ require File.dirname(__FILE__) + '/rested/error'
5
+ require File.dirname(__FILE__) + '/rested/base'
6
+ require File.dirname(__FILE__) + '/rested/entity'
7
+ require File.dirname(__FILE__) + '/rested/validations'
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rested
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Chetan Sarva
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-21 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: httpclient
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 119
30
+ segments:
31
+ - 2
32
+ - 1
33
+ - 5
34
+ - 2
35
+ version: 2.1.5.2
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: json
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 3
47
+ segments:
48
+ - 1
49
+ - 4
50
+ - 2
51
+ version: 1.4.2
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ description: Ruby library built on top of httpclient for working with RESTful APIs.
55
+ email: chetan@betteradvertising.com
56
+ executables: []
57
+
58
+ extensions: []
59
+
60
+ extra_rdoc_files:
61
+ - README.rdoc
62
+ files:
63
+ - .gitignore
64
+ - README.rdoc
65
+ - Rakefile
66
+ - VERSION
67
+ - lib/rested.rb
68
+ - lib/rested/base.rb
69
+ - lib/rested/debug.rb
70
+ - lib/rested/entity.rb
71
+ - lib/rested/error.rb
72
+ - lib/rested/ext.rb
73
+ - lib/rested/validations.rb
74
+ has_rdoc: true
75
+ homepage: ""
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options:
80
+ - --charset=UTF-8
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ requirements: []
102
+
103
+ rubyforge_project:
104
+ rubygems_version: 1.3.7
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Ruby library for working with RESTful APIs
108
+ test_files: []
109
+