rack-action 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3@rack-action --create
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-action.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Paul Barry
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Rack::Action
2
+
3
+ Rack action is a small, simple framework for generating Rack responses. A basic rack action looks like this:
4
+
5
+ require 'rack/action'
6
+
7
+ class MyAction < Rack::Action
8
+ def respond
9
+ "Hello, World!"
10
+ end
11
+ end
12
+
13
+ run MyAction
14
+
15
+ See the docs for Rack::Action for more details
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'rack-action'
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install rack-action
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/rack/*.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ require 'rack-action'
2
+
3
+ app = Class.new(Rack::Action) do
4
+ def respond
5
+ pretty_json env.except("rack.input", "rack.errors")
6
+ end
7
+ end
8
+
9
+ run app
@@ -0,0 +1 @@
1
+ require 'rack/action'
@@ -0,0 +1,291 @@
1
+ require 'time'
2
+ require 'json'
3
+ require 'rack'
4
+ require 'rack/filters'
5
+
6
+ module Rack
7
+ # Rack::Action provides functionality to generate a Rack response.
8
+ # To use Rack::Action, you should subclass Rack::Action and provide
9
+ # your own implementation of {#respond}. The simplest Rack action is
10
+ # one that just returns a string from respond:
11
+ #
12
+ # require 'rack/action'
13
+ #
14
+ # class MyAction < Rack::Action
15
+ # def respond
16
+ # "Hello, World!"
17
+ # end
18
+ # end
19
+ #
20
+ # run MyAction
21
+ #
22
+ # The class itself is a rack app, so the previous code example is a valid
23
+ # rackup file. Rack::Action is meant to be used with one action per
24
+ # page/endpoint in your application, so it is typically used in conjuction
25
+ # with something like Rack::Router, which would look something like this:
26
+ #
27
+ # require 'rack/action'
28
+ # require 'rack/router'
29
+ #
30
+ # class FooAction < Rack::Action
31
+ # def respond
32
+ # "foo"
33
+ # end
34
+ # end
35
+ #
36
+ # class BarAction < Rack::Action
37
+ # def respond
38
+ # "bar"
39
+ # end
40
+ # end
41
+ #
42
+ # router = Rack::Router.new do
43
+ # get "/foo" => FooAction
44
+ # get "/bar" => BarAction
45
+ # end
46
+ #
47
+ # run router
48
+ #
49
+ # Rack::Action makes an instance of Rack::Request and Rack::Response available
50
+ # which can be used to set headers, cookies, etc.
51
+ #
52
+ # class ArticleAction < Rack::Action
53
+ # def respond
54
+ # article = Article.find(params["id"])
55
+ # response['Content-Type'] = "text/xml"
56
+ # article.to_xml
57
+ # end
58
+ # end
59
+ #
60
+ # You can use before filters to do things before respond is called:
61
+ #
62
+ # class AccountAction < Rack::Action
63
+ # before_filter :load_current_user
64
+ #
65
+ # def load_current_user
66
+ # @current_user = User.find(params["id"])
67
+ # end
68
+ #
69
+ # def respond
70
+ # "Welcome Back, #{@current_user.name}"
71
+ # end
72
+ # end
73
+ #
74
+ # and you can of course share functionality across actions with inheritance:
75
+ #
76
+ # class ApplicationAction < Rack::Action
77
+ # before_filter :login_required
78
+ #
79
+ # def login_required
80
+ # redirect_to "/login" unless logged_in?
81
+ # end
82
+ # end
83
+ #
84
+ # class PublicAction < ApplicationAction
85
+ # skip_before_filter :login_required
86
+ #
87
+ # def respond
88
+ # "Hello"
89
+ # end
90
+ # end
91
+ #
92
+ # class PrivateAction < ApplicationAction
93
+ # def respond
94
+ # "It's A Secret To Everybody."
95
+ # end
96
+ # end
97
+ #
98
+ # Before filters will execute in the order they are defined. If a before
99
+ # filter writes to the response, subsequent filters will not be executed
100
+ # and the respond method will not be executed. As long as no before filters
101
+ # write to the response, execution of subsequent filters and the respond
102
+ # method will be called.
103
+ class Action
104
+ extend Filters
105
+
106
+ # @private
107
+ RACK_ROUTE_PARAMS = 'route.route_params'.freeze
108
+ # @private
109
+ CONTENT_TYPE = 'Content-Type'.freeze
110
+ # @private
111
+ APPLICATION_JSON = 'application/json'.freeze
112
+ # @private
113
+ LOCATION = 'Location'.freeze
114
+ # @private
115
+ DEFAULT_RESPONSE = "Default Rack::Action Response"
116
+
117
+ # This implements the Rack interface
118
+ #
119
+ # @param [Hash] env The Rack environment
120
+ # @return [Rack::Response] A Rack response
121
+ def self.call(env)
122
+ new(env).call
123
+ end
124
+
125
+ attr_accessor :env, :request, :response, :params
126
+
127
+ def initialize(env)
128
+ @env = env
129
+ end
130
+
131
+ def request
132
+ @request ||= Rack::Request.new(env)
133
+ end
134
+
135
+ def response
136
+ @response ||= Rack::Response.new
137
+ end
138
+
139
+ def params
140
+ @params ||= request.params.merge(env[RACK_ROUTE_PARAMS] || {})
141
+ end
142
+
143
+ # This is the main method responsible for generating a Rack response.
144
+ # You typically won't need to override this method of call it directly.
145
+ # First this will run the before filters for this action.
146
+ # If none of the before filters generate a response, this will call
147
+ # {#respond} to generate a response.
148
+ # All after filters for this action are called once the response
149
+ # is genenated. Finally the response is returned.
150
+ #
151
+ # @return [Array<Numeric, Hash, #each>] A Rack response
152
+ def call
153
+ run_before_filters
154
+ run_respond
155
+ run_after_filters
156
+ finish_response
157
+ end
158
+
159
+ # This is the main method that you should override in your action.
160
+ # You can either write to the response during this method, or simply
161
+ # return a string, which will be written to the response if the
162
+ # response is still empty after this is called.
163
+ #
164
+ # @return [String] The Rack response or a String
165
+ def respond
166
+ DEFAULT_RESPONSE
167
+ end
168
+
169
+ # This is a convenience method that sets the Content-Type headers
170
+ # and writes the JSON String to the response.
171
+ #
172
+ # @param [Hash] data The data
173
+ # @return [String] The JSON
174
+ def json(data)
175
+ response[CONTENT_TYPE] = APPLICATION_JSON
176
+ response.write JSON.generate(data)
177
+ end
178
+
179
+ # This is a convenience method that sets the Content-Type headers
180
+ # and writes the pretty-formatted JSON String to the response.
181
+ #
182
+ # @param [Hash] data The data
183
+ # @return [String] The JSON
184
+ def pretty_json(data)
185
+ response[CONTENT_TYPE] = APPLICATION_JSON
186
+ response.write JSON.pretty_generate(data)
187
+ end
188
+
189
+ # This is a convenience method that forms an absolute URL based on the
190
+ # url parameter, which can be a relative or absolute URL, and then
191
+ # sets the headers and the body appropriately to do a 302 redirect.
192
+ #
193
+ # @see #absolute_url
194
+ # @return [String] The absolute url
195
+ def redirect_to(url, options={})
196
+ full_url = absolute_url(url, options)
197
+ response[LOCATION] = full_url
198
+ response.status = 302
199
+ response.write ''
200
+ full_url
201
+ end
202
+
203
+ # Generate an absolute url from the url. If the url is already
204
+ # an absolute url, this will return it unchanged.
205
+ #
206
+ # @param [String] url The URL
207
+ # @param [Hash] options The options to use to generate the absolute URL
208
+ # @option options [true, false] :https If https should be used,
209
+ # uses rack.url_scheme from the Rack env to determine the default
210
+ # @option options [String] :host The host to use,
211
+ # uses SERVER_NAME form the Rack env for the default
212
+ # @option options [String, Numeric] :port The port to use,
213
+ # users SERVER_PORT from the Rack env for the default
214
+ # @return [String] The absolute url
215
+ def absolute_url(url, options={})
216
+ URL.new(env, url, options).to_absolute
217
+ end
218
+
219
+ protected
220
+ def run_before_filters
221
+ self.class.before_filters.each do |filter|
222
+ send(filter)
223
+ return unless response.empty?
224
+ end
225
+ end
226
+
227
+ def run_respond
228
+ return if !response.empty?
229
+ body = respond
230
+ if response.empty?
231
+ response.write body
232
+ else
233
+ body
234
+ end
235
+ end
236
+
237
+ def run_after_filters
238
+ self.class.after_filters.each do |filter|
239
+ send(filter)
240
+ end
241
+ end
242
+
243
+ def finish_response
244
+ response.finish
245
+ end
246
+
247
+ # @private
248
+ class URL
249
+ HTTPS = 'https'.freeze
250
+ HTTP_PREFIX = 'http://'.freeze
251
+ HTTPS_PREFIX = 'https://'.freeze
252
+ ABSOLUTE_URL_REGEX = /\Ahttps?:\/\//
253
+ RACK_URL_SCHEME = 'rack.url_scheme'
254
+ SERVER_NAME = 'SERVER_NAME'.freeze
255
+ SERVER_PORT = 'SERVER_PORT'.freeze
256
+ DEFAULT_HTTP_PORT = 80
257
+ DEFAULT_HTTPS_PORT = 443
258
+
259
+ attr_accessor :env, :url, :https, :host, :port
260
+
261
+ def initialize(env, url, options={})
262
+ @env = env
263
+ @url = url.to_s
264
+ @options = options || {}
265
+ @https = options.fetch(:https, env[RACK_URL_SCHEME] == HTTPS)
266
+ @host = options.fetch(:host, env[SERVER_NAME])
267
+ @port = Integer(options.fetch(:port, env[SERVER_PORT]))
268
+ end
269
+
270
+ def to_absolute
271
+ if url =~ ABSOLUTE_URL_REGEX
272
+ url
273
+ else
274
+ absolute_url = [prefix]
275
+ absolute_url << (port == default_port ? host : "#{host}:#{port}")
276
+ absolute_url << url
277
+ ::File.join(*absolute_url)
278
+ end
279
+ end
280
+
281
+ def prefix
282
+ https ? HTTPS_PREFIX : HTTP_PREFIX
283
+ end
284
+
285
+ def default_port
286
+ https ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT
287
+ end
288
+ end
289
+
290
+ end
291
+ end
@@ -0,0 +1,40 @@
1
+ module Rack
2
+ module Filters
3
+ def before_filters
4
+ @before_filters ||= if superclass.respond_to?(:before_filters)
5
+ superclass.before_filters.dup
6
+ else
7
+ []
8
+ end
9
+ end
10
+
11
+ def before_filter(method)
12
+ unless before_filters.include?(method)
13
+ before_filters << method
14
+ end
15
+ end
16
+
17
+ def skip_before_filter(method)
18
+ before_filters.delete(method)
19
+ end
20
+
21
+ def after_filters
22
+ @after_filters ||= if superclass.respond_to?(:after_filters)
23
+ superclass.after_filters.dup
24
+ else
25
+ []
26
+ end
27
+ end
28
+
29
+ def after_filter(method)
30
+ unless after_filters.include?(method)
31
+ after_filters << method
32
+ end
33
+ end
34
+
35
+ def skip_after_filter(method)
36
+ after_filters.delete(method)
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1 @@
1
+ require 'rack/action'
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Paul Barry"]
5
+ gem.email = ["mail@paulbarry.com"]
6
+ gem.description = %q{a small, simple framework for generating Rack responses}
7
+ gem.summary = %q{a small, simple framework for generating Rack responses}
8
+ gem.homepage = "http://github.com/pjb3/rack-action"
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "rack-action"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = "0.0.1"
16
+
17
+ gem.add_runtime_dependency "rack"
18
+ end
@@ -0,0 +1,192 @@
1
+ require 'rack_action'
2
+ require 'rack_test'
3
+
4
+ class Rack::ActionTest < RackTest
5
+
6
+ def test_default_respond
7
+ app = Class.new(Rack::Action)
8
+
9
+ response = get app, "/"
10
+
11
+ assert_equal 200, response.status
12
+ assert_equal Rack::Action::DEFAULT_RESPONSE.length, response.length
13
+ assert_equal 'text/html', response["Content-Type"]
14
+ assert_equal [Rack::Action::DEFAULT_RESPONSE], response.body
15
+ end
16
+
17
+ def test_custom_respond
18
+ app = Class.new(Rack::Action) do
19
+ def respond
20
+ "bananas"
21
+ end
22
+ end
23
+
24
+ response = get app, "/"
25
+
26
+ assert_equal 200, response.status
27
+ assert_equal 7, response.length
28
+ assert_equal 'text/html', response["Content-Type"]
29
+ assert_equal ['bananas'], response.body
30
+ end
31
+
32
+ def test_json_respond
33
+ app = Class.new(Rack::Action) do
34
+ def respond
35
+ json :hello => "world"
36
+ end
37
+ end
38
+ expected = %{{"hello":"world"}}
39
+
40
+ response = get app, "/"
41
+
42
+ assert_equal 200, response.status
43
+ assert_equal expected.length, response.length
44
+ assert_equal 'application/json', response["Content-Type"]
45
+ assert_equal [expected], response.body
46
+ end
47
+
48
+ def test_pretty_json_respond
49
+ app = Class.new(Rack::Action) do
50
+ def respond
51
+ pretty_json :hello => "world"
52
+ end
53
+ end
54
+ expected = %{{
55
+ "hello": "world"
56
+ }}
57
+
58
+ response = get app, "/"
59
+
60
+ assert_equal 200, response.status
61
+ assert_equal expected.length.to_s, response["Content-Length"]
62
+ assert_equal 'application/json', response["Content-Type"]
63
+ assert_equal [expected], response.body
64
+ end
65
+
66
+ def test_before_filter_set_instance_variable
67
+ app = Class.new(Rack::Action) do
68
+ def respond
69
+ @message
70
+ end
71
+
72
+ def set_message
73
+ @message = "Hello, World!"
74
+ end
75
+ end
76
+ app.before_filter :set_message
77
+
78
+ response = get app, "/"
79
+
80
+ assert_equal 200, response.status
81
+ assert_equal ["Hello, World!"], response.body
82
+ end
83
+
84
+ def test_before_filter_set_response
85
+ app = Class.new(Rack::Action) do
86
+ def respond
87
+ fail "respond should not be called if a before filter sets the response"
88
+ end
89
+
90
+ def set_response
91
+ response.write "test"
92
+ end
93
+ end
94
+ app.before_filter :set_response
95
+
96
+ response = get app, "/"
97
+
98
+ assert_equal 200, response.status
99
+ assert_equal ["test"], response.body
100
+ end
101
+
102
+ def test_redirect
103
+ app = Class.new(Rack::Action) do
104
+ def respond
105
+ fail "respond should not be called if a before filter sets the response"
106
+ end
107
+
108
+ def login_required
109
+ redirect_to "/login"
110
+ end
111
+ end
112
+ app.before_filter :login_required
113
+
114
+ response = get app, "/"
115
+
116
+ assert_equal 302, response.status
117
+ assert_equal "http://example.com/login", response["Location"]
118
+ end
119
+
120
+ def test_redirect_non_default_port
121
+ app = Class.new(Rack::Action) do
122
+ def respond
123
+ fail "respond should not be called if a before filter sets the response"
124
+ end
125
+
126
+ def login_required
127
+ redirect_to "/login"
128
+ end
129
+ end
130
+ app.before_filter :login_required
131
+
132
+ response = get app, "/", "SERVER_PORT" => "3000"
133
+
134
+ assert_equal 302, response.status
135
+ assert_equal "http://example.com:3000/login", response["Location"]
136
+ end
137
+
138
+ def test_redirect_non_default_port_option
139
+ app = Class.new(Rack::Action) do
140
+ def respond
141
+ fail "respond should not be called if a before filter sets the response"
142
+ end
143
+
144
+ def login_required
145
+ redirect_to "/login", :port => 3000
146
+ end
147
+ end
148
+ app.before_filter :login_required
149
+
150
+ response = get app, "/"
151
+
152
+ assert_equal 302, response.status
153
+ assert_equal "http://example.com:3000/login", response["Location"]
154
+ end
155
+
156
+ def test_secure_redirect
157
+ app = Class.new(Rack::Action) do
158
+ def respond
159
+ fail "respond should not be called if a before filter sets the response"
160
+ end
161
+
162
+ def login_required
163
+ redirect_to "/login"
164
+ end
165
+ end
166
+ app.before_filter :login_required
167
+
168
+ response = get app, "/", "SERVER_PORT" => "443", "rack.url_scheme" => "https"
169
+
170
+ assert_equal 302, response.status
171
+ assert_equal "https://example.com/login", response["Location"]
172
+ end
173
+
174
+ def test_redirect_absolute_url
175
+ app = Class.new(Rack::Action) do
176
+ def respond
177
+ fail "respond should not be called if a before filter sets the response"
178
+ end
179
+
180
+ def login_required
181
+ redirect_to "http://test.com/login"
182
+ end
183
+ end
184
+ app.before_filter :login_required
185
+
186
+ response = get app, "/"
187
+
188
+ assert_equal 302, response.status
189
+ assert_equal "http://test.com/login", response["Location"]
190
+ end
191
+
192
+ end
data/test/rack_test.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'test/unit'
2
+ require 'stringio'
3
+
4
+ class RackTest < Test::Unit::TestCase
5
+
6
+ DEFAULT_HOST = "example.com".freeze
7
+ DEFAULT_PORT = 80
8
+
9
+ DEFAULT_ENV = {
10
+ "SCRIPT_NAME" => "",
11
+ "QUERY_STRING" => "",
12
+ "SERVER_NAME" => DEFAULT_HOST,
13
+ "SERVER_PORT" => DEFAULT_PORT.to_s,
14
+ "HTTP_HOST" => DEFAULT_HOST,
15
+ "HTTP_ACCEPT" => "*/*",
16
+ "rack.input" => StringIO.new,
17
+ "rack.url_scheme" => "http"
18
+ }.freeze
19
+
20
+ def default_env
21
+ DEFAULT_ENV
22
+ end
23
+
24
+ def request(app, method, path, env={})
25
+ resp = app.call(DEFAULT_ENV.merge({
26
+ "REQUEST_METHOD" => method.to_s.upcase,
27
+ "PATH_INFO" => path
28
+ }.merge(env)))
29
+ resp.is_a?(Rack::Response) ? resp : Rack::Response.new(resp[2], resp[0], resp[1])
30
+ end
31
+
32
+ def get(app, path, env={})
33
+ request(app, :get, path, env)
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-action
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Paul Barry
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: a small, simple framework for generating Rack responses
31
+ email:
32
+ - mail@paulbarry.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - .rvmrc
39
+ - .yardopts
40
+ - Gemfile
41
+ - LICENSE
42
+ - README.md
43
+ - Rakefile
44
+ - config.ru
45
+ - lib/rack-action.rb
46
+ - lib/rack/action.rb
47
+ - lib/rack/filters.rb
48
+ - lib/rack_action.rb
49
+ - rack-action.gemspec
50
+ - test/rack/action_test.rb
51
+ - test/rack_test.rb
52
+ homepage: http://github.com/pjb3/rack-action
53
+ licenses: []
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.23
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: a small, simple framework for generating Rack responses
76
+ test_files:
77
+ - test/rack/action_test.rb
78
+ - test/rack_test.rb
79
+ has_rdoc: