sinatra-rabbit 1.0.5 → 1.0.6

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/README.md CHANGED
@@ -5,12 +5,15 @@ Sinatra::Rabbit
5
5
 
6
6
  Sinatra::Rabbit is a [Sinatra](http://www.sinatrarb.com) extensions that
7
7
  makes designing a [REST API](http://en.wikipedia.org/wiki/Representational_State_Transfer)
8
- much easier.
8
+ much easier and more fun.
9
9
 
10
- Rabbit treats REST resource as 'collection'. Every collection then
11
- could have multiple operations. This simple DSL is then transformed into
12
- a proper modular [Sinatra::Base](http://www.sinatrarb.com/intro#Serving%20a%20Modular%20Application)
13
- application that can be launched within any [Rack](http://rack.rubyforge.org/) container (including Rails).
10
+ Rabbit maps REST resources to 'collections'. Every collection then could define
11
+ CRUD and other operations to manipulate with resource. Rabbit will handle
12
+ parameter validation and capability checks for you, so you can focus on the
13
+ structure and design of your REST API.
14
+
15
+ Once your REST API is designed, Rabbit will make it available as modular Sinatra
16
+ application thus mountable to any Rack compatible container (Rails, Padrino...)
14
17
 
15
18
  Original Rabbit idea has been taken from the [Deltacloud API](http://deltacloud.org)
16
19
  project. However this is an complete rewrite of the original Deltacloud Rabbit.
@@ -25,54 +28,59 @@ An example application (modular Deltacloud API) can be found here:
25
28
  Features
26
29
  -------
27
30
 
28
- * Automatically generate proper path and use right HTTP method for all CRUD operations:
31
+ * Automatically generate proper path and use the right HTTP method for all CRUD operations:
29
32
  * `operation :create` is being mapped as 'POST /:collection'
30
33
  * `operation :index` is being mapped as 'GET /:collection'
31
34
  * `operation :show` is being mapped as 'GET /:collection/:id'
32
- * `operation :update` is being mapped as 'PUT /:collection/:id'
35
+ * `operation :update` is being mapped as 'PATCH /:collection/:id'
33
36
  * `operation :destroy` is being mapped as 'DELETE /:collection/:id'
34
- * Use of Sinatra route conditions
37
+ * `action :reboot` is being mapped as 'POST /:collection/:id/reboot'
38
+ * Automatically set the :id parameter for routes like :show or :destroy
39
+ * Automatically validates the request parameter values
40
+ * Allow to change the HTTP method (`action :reboot, :http_method => :post`)
41
+ * Allow to check if operation is supported by the current resource
35
42
  <pre>
36
43
  <code>
37
- operation :stop, :if => driver.support_stop_operation? do
38
- description "Stop virtual machine"
39
- param :id, :string, :required, "Virtual Machine ID"
40
- control do
41
- # ...
42
- end
44
+ check_capability :for => lambda { |m| driver.respond_to? m }
45
+
46
+ collection :vms do
47
+ action :stop, :with_capability => :stop do
48
+ control do
49
+ # ...
50
+ end
51
+ end
43
52
  end
44
53
  </code>
45
54
  </pre>
46
- * Support operation specific features
47
- * Support REST subcollections
55
+ * Allow to define groups of parameters as features and make them available if
56
+ the current resource support them
57
+ * Support the 'sub-collections' that might be defined on resouce
48
58
  <pre>
49
59
  <code>
50
60
  collection :buckets do
51
61
  collection :blobs, :with_id => :bucket_id do
52
- description "Blobs are binary objects stored in buckets"
53
-
62
+
54
63
  # GET /buckets/:bucket_id/blobs/:id
55
64
  operation :show do
56
- description "Show content of blob"
57
- param :id, :string, :required
58
65
  control { #... }
59
66
  end
60
67
  end
61
68
 
62
69
  # GET /buckets/:id
63
70
  operation :show do
64
- description "Show content of bucket"
65
- param :id, :string, :required
66
71
  control { #... }
67
72
  end
68
73
  end
69
74
  </code>
70
75
  </pre>
71
- * Generate HEAD routes for all operations and collections
76
+ * Generate HEAD routes for all operations and collections to check if the route
77
+ is available
72
78
  * Generate OPTIONS routes for all operations and collections
73
79
  * `OPTIONS /:collection` will return list of all operations (using 'Allow' header)
74
80
  * `OPTIONS /:collection/:operation` will return list of parameters defined for given operation (using 'Allow' header)
75
- * Include bunch of tests to prove it work correctly
81
+ * Allow to manipulate with the REST structure programmatically, to generate
82
+ documentation or collect statistics
83
+ * Include bunch of tests to prove it all works correctly
76
84
 
77
85
  Configuration
78
86
  -------
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rake/testtask'
2
+
3
+ namespace :test do
4
+
5
+ desc 'Run all tests suites'
6
+ task :all do
7
+ Rake::Task['test:app'].invoke
8
+ Rake::Task['test:dsl'].invoke
9
+ Rake::Task['test:docs'].invoke
10
+ Rake::Task['test:param'].invoke
11
+ end
12
+
13
+ Rake::TestTask.new(:app) do |t|
14
+ t.test_files = FileList['tests/app_test.rb']
15
+ t.verbose = false
16
+ t.options = "-v"
17
+ end
18
+
19
+ Rake::TestTask.new(:dsl) do |t|
20
+ t.test_files = FileList['tests/dsl_test.rb']
21
+ t.verbose = false
22
+ #t.options = "-v"
23
+ end
24
+
25
+ Rake::TestTask.new(:docs) do |t|
26
+ t.test_files = FileList['tests/docs_test.rb']
27
+ t.verbose = false
28
+ end
29
+
30
+ end
31
+
32
+ desc "Run RSpec with code coverage"
33
+ task :coverage do
34
+ ENV["COVERAGE"] = "true"
35
+ Rake::Task["test:all"].execute
36
+ end
37
+
38
+ desc "Reinstall gem"
39
+ task :reinstall do
40
+ puts %x{rm -rf sinatra-rabbit-*.gem}
41
+ puts %x{gem uninstall sinatra-rabbit --all -I -x}
42
+ puts %x{gem build sinatra-rabbit.gemspec}
43
+ puts %x{gem install sinatra-rabbit-*.gem --local}
44
+ end
@@ -0,0 +1,8 @@
1
+ %html
2
+ %head
3
+ %body
4
+ %h1=self.class.name
5
+ %ul
6
+ - self.class.collections.each do |c|
7
+ %li
8
+ %a{:href => c.docs_url }=c.name
@@ -0,0 +1,24 @@
1
+ %html
2
+ %head
3
+ %body
4
+ %h1=@collection.name.gsub('Sinatra::Rabbit::', '')
5
+ %blockquote=@collection.description
6
+ .url=@collection.full_path
7
+ .features
8
+ - @collection.features.each do |f|
9
+ %p.feature=f.name
10
+ %table.operations
11
+ %thead
12
+ %tr
13
+ %th Name
14
+ %th Type
15
+ %th Valid values
16
+ %th Description
17
+ %tbody
18
+ - @collection.operations.each do |o|
19
+ %tr
20
+ %td.name
21
+ %a{:href => o.docs_url }
22
+ %td.method=o.http_method.to_s.upcase
23
+ %td.url
24
+ %a{:href=>o.full_path}=o.full_path
@@ -0,0 +1,26 @@
1
+ %html
2
+ %head
3
+ %body
4
+ %h1=@operation.name.gsub('Sinatra::Rabbit::', '')
5
+ %blockquote=@operation.description
6
+ .url=@operation.full_path
7
+ - if @operation.required_capability
8
+ %p{:class => :requires}=@operation.required_capability
9
+ - @operation.features.each do |f|
10
+ .feature
11
+ .name=f.name
12
+ %blockquote=f.description
13
+ %table.parameters
14
+ %thead
15
+ %tr
16
+ %th Name
17
+ %th Type
18
+ %th Valid values
19
+ %th Description
20
+ %tbody
21
+ - (@operation.params + @operation.features_params).each do |p|
22
+ %tr
23
+ %td{ :class => p.required? && 'required'}=p.name
24
+ %td=p.klass
25
+ %td=p.values
26
+ %td=p.description
@@ -14,14 +14,6 @@
14
14
  # License for the specific language governing permissions and limitations
15
15
  # under the License.
16
16
 
17
- unless Kernel.respond_to?(:require_relative)
18
- module Kernel
19
- def require_relative(path)
20
- require File.join(File.dirname(caller[0]), path.to_str)
21
- end
22
- end
23
- end
24
-
25
17
  require_relative './dsl'
26
18
  require_relative './param'
27
19
  require_relative './base_collection'
@@ -98,7 +90,10 @@ module Sinatra
98
90
  #
99
91
  def self.included(base)
100
92
  configuration[:root_path] = '/'
101
- base.register(DSL)
93
+ base.register(DSL) if base.respond_to? :register
94
+ base.get '/docs' do
95
+ haml File.read(File.join(File.dirname(__FILE__), '..', 'docs', 'api.haml'))
96
+ end
102
97
  end
103
98
 
104
99
  class Collection < BaseCollection
@@ -107,31 +102,57 @@ module Sinatra
107
102
 
108
103
  def self.generate(name, parent_collection=nil, &block)
109
104
  @collection_name = name.to_sym
110
- @collections ||= []
111
105
  @parent_collection = parent_collection
112
106
  class_eval(&block)
113
- send(:head, full_path, {}) { status 200 } unless Rabbit.disabled? :head_routes
114
- send(:options, full_path, {}) do
115
- [200, { 'Allow' => operations.map { |o| o.operation_name }.join(',') }, '']
116
- end unless Rabbit.disabled? :options_routes
107
+ generate_head_route unless Rabbit.disabled? :head_routes
108
+ generate_option_route unless Rabbit.disabled? :options_routes
109
+ generate_docs_route unless Rabbit.disabled? :docs
117
110
  self
118
111
  end
119
112
 
113
+ def self.generate_option_route
114
+ base_class.options '/docs' + full_path do
115
+ header 'Allow' => operations.map { |o| o.operation_name }.join(',')
116
+ status 200
117
+ end
118
+ end
119
+
120
+ def self.generate_head_route
121
+ base_class.head full_path do
122
+ status 200
123
+ end
124
+ end
125
+
126
+ def self.docs_url
127
+ (root_path || '/') + 'docs/' + route_for(path)
128
+ end
129
+
130
+ def self.generate_docs_route
131
+ collection = self
132
+ base_class.get docs_url do
133
+ @collection = collection
134
+ haml File.read(File.join(File.dirname(__FILE__), '..', 'docs', 'collection.haml'))
135
+ end
136
+ end
137
+
138
+ # Define new collection using the name
139
+ #
140
+ # opts[:with_id] Define :id used if the collection is a subcollection
141
+ #
120
142
  def self.collection(name, opts={}, &block)
121
- unless block_given?
122
- return @collections.find { |c| c.collection_name == name }
143
+ return collections.find { |c| c.collection_name == name } if not block_given?
144
+ new_collection = BaseCollection.collection_class(name, self) do |c|
145
+ c.base_class = self.base_class
146
+ c.with_id!(opts.delete(:with_id)) if opts.has_key?(:with_id)
147
+ c.no_member! if opts.has_key?(:no_member)
148
+ c.generate(name, self, &block)
123
149
  end
124
- current_collection = BaseCollection.collection_class(name, self)
125
- current_collection.set_base_class(self.base_class)
126
- current_collection.with_id!(opts.delete(:with_id)) if opts.has_key? :with_id
127
- current_collection.no_member! if opts.has_key? :no_member
128
- current_collection.generate(name, self, &block)
129
- @collections << current_collection
150
+ collections << new_collection
130
151
  end
131
152
 
132
153
  def self.parent_routes
133
154
  return '' if @parent_collection.nil?
134
- route = ["#{@parent_collection.collection_name}"]
155
+ route = [ @parent_collection.collection_name.to_s ]
135
156
  current_parent = @parent_collection
136
157
  while current_parent = current_parent.parent_collection
137
158
  route << current_parent.collection_name
@@ -139,7 +160,7 @@ module Sinatra
139
160
  route.reverse.join('/')+'/'
140
161
  end
141
162
 
142
- def self.set_base_class(klass)
163
+ def self.base_class=(klass)
143
164
  @klass = klass
144
165
  end
145
166
 
@@ -169,26 +190,37 @@ module Sinatra
169
190
  parent_routes + with_id_param + ((@no_member) ? '' : collection_name.to_s)
170
191
  end
171
192
 
172
- def self.base_class;@klass;end
173
- def self.full_path;root_path + route_for(path);end
193
+ def self.base_class
194
+ @klass
195
+ end
196
+
174
197
  def self.collection_name; @collection_name; end
175
198
  def self.parent_collection; @parent_collection; end
176
- def self.collections; @collections; end
199
+
200
+ def self.subcollection?
201
+ !parent_collection.nil?
202
+ end
203
+
204
+ def self.collections
205
+ @collections ||= []
206
+ end
207
+
208
+ def self.full_path
209
+ (root_path || '') + route_for(path)
210
+ end
177
211
 
178
212
  def self.description(text=nil)
179
213
  return @description if text.nil?
180
214
  @description = text
181
215
  end
182
216
 
183
- def self.documentation
184
- Rabbit::Documentation.for_collection(self, operations)
217
+ def self.[](obj_id)
218
+ collections.find { |c| c.collection_name == obj_id } || operation(obj_id)
185
219
  end
186
220
 
187
-
188
221
  def self.operation(operation_name, opts={}, &block)
189
- @operations ||= []
190
222
  # Return operation when no block is given
191
- return @operations.find { |o| o.operation_name == operation_name } unless block_given?
223
+ return operations.find { |o| o.operation_name == operation_name } unless block_given?
192
224
 
193
225
  # Check if current operation is not already registred
194
226
  if operation_registred?(operation_name)
@@ -196,13 +228,8 @@ module Sinatra
196
228
  end
197
229
 
198
230
  # Create operation class
199
- operation = operation_class(self, operation_name).generate(self, operation_name, opts, &block)
200
- @operations << operation
201
-
202
- # Generate HEAD routes
203
- unless Rabbit.disabled? :head_routes
204
- send(:head, root_path + route_for(path, operation_name, {})) { status 200 }
205
- end
231
+ new_operation = operation_class(self, operation_name).generate(self, operation_name, opts, &block)
232
+ operations << new_operation
206
233
 
207
234
  # Add route conditions if defined
208
235
  if opts.has_key? :if
@@ -215,24 +242,21 @@ module Sinatra
215
242
  end
216
243
 
217
244
  # Make the full_path method on operation return currect operation path
218
- operation.set_route(root_path + route_for(path, operation_name, :id_name => @with_id || ':id'))
245
+ new_operation.route = root_path + route_for(path, operation_name, :id_name => @with_id || ':id')
219
246
 
220
247
  # Change the HTTP method to POST automatically for 'action' operations
221
- if opts[:http_method]
222
- operation.http_method(opts.delete(:http_method))
223
- end
248
+ new_operation.http_method = opts.delete(:http_method) if opts[:http_method]
224
249
 
225
- # Define Sinatra::Base route
250
+ # Remove :with_capability from Sinatra route options
226
251
  route_options = opts.clone
227
252
  route_options.delete :with_capability
228
- base_class.send(operation.http_method || http_method_for(operation_name), operation.full_path, route_options, &operation.control)
229
253
 
230
- # Generate OPTIONS routes
231
- unless Rabbit.disabled? :options_routes
232
- send(:options, root_path + route_for(path, operation_name, :member), {}) do
233
- [200, { 'Allow' => operation.params.map { |p| p.to_s }.join(',') }, '']
234
- end
235
- end
254
+ # Define Sinatra route and generate OPTIONS route if enabled
255
+ base_class.send(new_operation.http_method || http_method_for(operation_name), new_operation.full_path, route_options, &new_operation.control)
256
+
257
+ new_operation.generate_option_route(root_path + route_for(path, operation_name, :no_id_and_member)) unless Rabbit.disabled?(:options_routes)
258
+ new_operation.generate_head_route(root_path + route_for(path, operation_name, :member)) unless Rabbit.disabled?(:head_routes)
259
+ new_operation.generate_docs_route(new_operation.docs_url) unless Rabbit.disabled?(:doc_routes)
236
260
  self
237
261
  end
238
262
 
@@ -241,23 +265,71 @@ module Sinatra
241
265
  operation(action_name, opts, &block)
242
266
  end
243
267
 
244
- def self.operations; @operations; end
268
+ def self.operations
269
+ @operations ||= []
270
+ end
271
+
272
+ def self.features_for(operation_name)
273
+ features.select { |f| f.operations.map { |o| o.name}.include?(operation_name) }
274
+ end
245
275
 
246
- class Operation
276
+ class Operation < BaseCollection
247
277
 
248
- def self.set_route(path)
278
+ def self.docs_url
279
+ @collection.root_path + ['docs', @collection.collection_name, operation_name].join('/')
280
+ end
281
+
282
+ def self.route=(path)
249
283
  @operation_path = path
250
284
  end
251
285
 
252
- def self.http_method(method=nil)
253
- @method ||= method || BaseCollection.http_method_for(@name)
286
+ def self.http_method
287
+ @method ||= BaseCollection.http_method_for(@name)
288
+ end
289
+
290
+ def self.http_method=(method)
291
+ @method = method
292
+ end
293
+
294
+ def self.generate_head_route(path)
295
+ @collection.base_class.head path do
296
+ status 200
297
+ end
298
+ end
299
+
300
+ def self.generate_docs_route(path)
301
+ operation = self
302
+ @collection.base_class.get path do
303
+ @operation = operation
304
+ haml File.read(File.join(File.dirname(__FILE__), '..', 'docs', 'operation.haml'))
305
+ end
306
+ end
307
+
308
+ def self.generate_option_route(path)
309
+ operation_params = params.map { |p| p.to_s }.join(',')
310
+ @collection.base_class.options path do
311
+ headers 'Allow' => operation_params
312
+ status 200
313
+ end
314
+ end
315
+
316
+ def self.features
317
+ @collection.features_for(operation_name)
318
+ end
319
+
320
+ def self.features_params
321
+ features.map { |f| f.operations.map { |o| o.params_array } }.flatten
254
322
  end
255
323
 
256
324
  def self.generate(collection, name, opts={}, &block)
257
325
  @name, @params, @collection = name, [], collection
258
326
  @options = opts
259
- http_method(@options.delete(:http_method)) if @options.has_key? :http_method
260
- @collection.features.select { |f| f.operations.map { |o| o.name}.include?(@name) }.each do |feature|
327
+
328
+ if @options.has_key?(:http_method)
329
+ @method = @options.delete(:http_method)
330
+ end
331
+
332
+ features.each do |feature|
261
333
  if Sinatra::Rabbit.configuration[:check_features]
262
334
  next unless Sinatra::Rabbit.configuration[:check_features].call(collection.collection_name, feature.name)
263
335
  end
@@ -265,6 +337,7 @@ module Sinatra
265
337
  instance_eval(&o.params)
266
338
  end
267
339
  end
340
+
268
341
  if Sinatra::Rabbit::STANDARD_OPERATIONS.has_key? name
269
342
  required_params = Sinatra::Rabbit::STANDARD_OPERATIONS[name][:required_params]
270
343
  required_params.each do |p|
@@ -279,24 +352,27 @@ module Sinatra
279
352
  def self.full_path; @operation_path; end
280
353
  def self.operation_name; @name; end
281
354
 
355
+ def self.required_capability
356
+ @options[:with_capability]
357
+ end
358
+
282
359
  def self.has_capability?
283
360
  @capability ||= Sinatra::Rabbit.configuration[:check_capability]
284
- if @capability and @options.has_key?(:with_capability)
285
- @capability.call(@options[:with_capability])
361
+ if @capability and required_capability
362
+ @capability.call(required_capability)
286
363
  else
287
364
  true
288
365
  end
289
366
  end
290
367
 
291
368
  def self.description(text=nil)
292
- return @description if text.nil?
293
- @description = text
369
+ @description ||= text
294
370
  end
295
371
 
296
372
  def self.control(&block)
297
373
  params_def = @params
298
374
  if not has_capability?
299
- @control = Proc.new { [412, {}, "The required capability to execute this operation is missing"] }
375
+ @control = Proc.new { [412, { 'Expect' => @options[:with_capability]}, "The required capability to execute this operation is missing"] }
300
376
  else
301
377
  @control ||= Proc.new do
302
378
  begin
@@ -324,7 +400,7 @@ module Sinatra
324
400
  private
325
401
 
326
402
  def self.operation_registred?(name)
327
- @operations.any? { |o| o.name == name }
403
+ operations.any? { |o| o.name == name }
328
404
  end
329
405
 
330
406
  # Create an unique class name for all operations within Collection class
@@ -22,19 +22,21 @@ module Sinatra
22
22
  enable :method_overide
23
23
 
24
24
  def self.route_for(collection, operation_name=nil, member=:member)
25
- if operation_name
25
+ unless operation_name.nil?
26
26
  o = Sinatra::Rabbit::STANDARD_OPERATIONS[operation_name]
27
27
  if o
28
+ o = o.clone
28
29
  o[:member] = false if member == :no_member
29
30
  o[:collection] = true if member == :no_id
30
- if member == :no_id_and_member
31
+ if member == :no_id_and_member or member == :docs
31
32
  o[:collection] = true
32
33
  o[:member] = true
33
34
  end
34
35
  end
35
36
  operation_path = (o && o[:member]) ? operation_name.to_s : nil
36
37
  operation_path = operation_name.to_s unless o
37
- id_param = (o && o[:collection]) ? nil : (member.kind_of?(Hash) ? member[:id_name] : ':id')
38
+ id_param = (o && o[:collection]) ? nil : (member.kind_of?(Hash) ? member[:id_name] : ":id")
39
+ id_param.gsub!(':', '') if id_param and member == :docs
38
40
  [route_for(collection), id_param, operation_path].compact.join('/')
39
41
  else
40
42
  collection.to_s
@@ -49,10 +51,11 @@ module Sinatra
49
51
  def self.collection_class(name, parent_class=nil)
50
52
  klass = parent_class || Sinatra::Rabbit
51
53
  begin
52
- klass.const_get(name.to_s.camelize + 'Collection')
54
+ yield k = klass.const_get(name.to_s.camelize + 'Collection')
53
55
  rescue NameError
54
- klass.const_set(name.to_s.camelize + 'Collection', Class.new(Collection))
56
+ yield k = klass.const_set(name.to_s.camelize + 'Collection', Class.new(Collection))
55
57
  end
58
+ return k
56
59
  end
57
60
 
58
61
  def self.root_path
@@ -30,20 +30,20 @@ module Sinatra
30
30
  # end
31
31
  # end
32
32
  def collection(name, &block)
33
- return @collections.find { |c| c.collection_name == name } unless block_given?
34
- @collections ||= []
35
- current_collection = BaseCollection.collection_class(name)
36
- current_collection.set_base_class(self)
37
- current_collection.generate(name, &block)
38
- @collections << current_collection
39
- Sinatra::Rabbit::DSL.register_collection(current_collection)
33
+ return self[name] unless block_given?
34
+ current_collection = BaseCollection.collection_class(name) do |c|
35
+ c.base_class = self
36
+ c.generate(name, &block)
37
+ end
38
+ collections << current_collection
39
+ DSL << current_collection
40
40
  use current_collection
41
41
  end
42
42
 
43
43
  # Return all defined collections
44
44
  #
45
45
  def collections
46
- @collections
46
+ @collections ||= []
47
47
  end
48
48
 
49
49
  def self.register_collection(c, &block)
@@ -51,6 +51,14 @@ module Sinatra
51
51
  @collections << c
52
52
  end
53
53
 
54
+ def self.<<(c)
55
+ register_collection(c)
56
+ end
57
+
58
+ def [](collection)
59
+ collections.find { |c| c.collection_name == collection }
60
+ end
61
+
54
62
  end
55
63
  end
56
64
  end
@@ -22,11 +22,18 @@ unless "".respond_to? :camelize
22
22
  end
23
23
  end
24
24
 
25
+ unless Kernel.respond_to?(:require_relative)
26
+ module Kernel
27
+ def require_relative(path)
28
+ require File.join(File.dirname(caller[0]), path.to_str)
29
+ end
30
+ end
31
+ end
32
+
25
33
  class << Sinatra::Base
26
34
  def options(path, opts={}, &bk)
27
35
  route 'OPTIONS', path, opts, &bk
28
36
  end
29
37
  end
30
38
 
31
- $:.unshift File.join(File::dirname(__FILE__), '.')
32
- require 'rabbit/base'
39
+ require_relative './rabbit/base'
@@ -29,12 +29,15 @@ Gem::Specification.new do |s|
29
29
  a simple REST API using easy to undestand DSL.
30
30
  EOF
31
31
 
32
- s.version = '1.0.5'
32
+ s.version = '1.0.6'
33
33
  s.date = Time.now
34
34
  s.summary = %q{Sinatra REST API DSL}
35
35
  s.files = FileList[
36
36
  'lib/sinatra/*.rb',
37
37
  'lib/sinatra/rabbit/*.rb',
38
+ 'lib/sinatra/docs/*.haml',
39
+ 'tests/*.rb',
40
+ 'Rakefile',
38
41
  'LICENSE',
39
42
  'README.md',
40
43
  'sinatra-rabbit.gemspec',