rack-action 0.0.1

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,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: