mwmitchell-pepper 0.3.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 Matt Mitchell - goodieboy@gmail.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,103 @@
1
+ Welcome to the pepper wiki!
2
+ Pepper is a Ruby, DSL based framework for creating RESTful web applications. It adheres to the Ruby Rack specification.
3
+
4
+ Pepper treats every fragment in a URL as a "context". Each context can be mapped to a block or another Pepper enabled module. For example, a URL of:
5
+
6
+ <pre>/users/1/images/1</pre>
7
+
8
+ could translate to a context block tree of:
9
+
10
+ <pre>
11
+ <code>
12
+ map 'users' do
13
+ map :digit do
14
+ get{"The value of this user is #{value}"}
15
+ map 'images' do
16
+ map :digit do
17
+ get{"The ID of this image is #{value}"}
18
+ end
19
+ end
20
+ end
21
+ end
22
+ </code>
23
+ </pre>
24
+
25
+ Because each block maps to a single url fragment, "routing" is not needed. The value of a context's url fragment can be accessed by its :value attribute. You can also name the fragment and propagate to the request params like:
26
+
27
+ <pre>
28
+ <code>
29
+ map :user_id=>/\d+/ do
30
+ puts params[:user_id]
31
+ end
32
+ </code>
33
+ </pre>
34
+
35
+ The :user_id param will then be available as a global request param to other contexts and "actions".
36
+
37
+ The fragment value passed to :map can be a string, a regular expression or a symbol that matches one of the built-in rules; :digit, :word, etc.
38
+
39
+ Standard HTTP methods (GET, POST, PUT, etc.) are used as the "actions". Each of the actions methods can accept a "rule" hash. The keys map to the REQUEST values (but down-cased and symbolized). So you could execute an action only if it matched the IP, or SERVER_NAME. The value of the rule can be a Regular Expression or a string.
40
+
41
+ Here is an example:
42
+
43
+ <pre>
44
+ <code>
45
+ require 'pepper'
46
+
47
+ module SeriousApp
48
+
49
+ class Contact # A delegate/sub-app
50
+
51
+ include Pepper::Context::Delegate
52
+
53
+ build do
54
+ before :post do
55
+ # a before :post block. Default is :all.
56
+ end
57
+
58
+ get{'GET request'}
59
+ post{'POST request'}
60
+
61
+ # request for /contact/:word
62
+ map :word do
63
+ get{"GET request for /contact/#{value}"}
64
+ end
65
+ end
66
+ end
67
+
68
+ class Root
69
+
70
+ include Pepper::App
71
+
72
+ build do
73
+ get{'A simple GET to /'}
74
+ post{'A POST to /'}
75
+
76
+ # /about/
77
+ map 'about' do
78
+ get{'GET request for /about'}
79
+ end
80
+
81
+ # /notes
82
+ map 'notes' do
83
+ get(:server_name=>/^myspecialdomain\.com$/) do
84
+ 'GET request for /notes, but only allowing a SERVER_NAME of "myspecialdomain.com"'}
85
+ end
86
+ end
87
+
88
+ # forwarding /contact to the Contact class
89
+ map 'contact', {}, Contact
90
+ end
91
+
92
+ end
93
+ end
94
+ </code>
95
+ </pre>
96
+
97
+ You then instantiate the Root class, and pass it to Rack's "run" method.
98
+
99
+ <pre>
100
+ <code>
101
+ run SeriousApp::Root.new
102
+ </code>
103
+ </pre>
@@ -0,0 +1,377 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+
4
+ $:.unshift File.dirname(__FILE__) unless $:.include? File.dirname(__FILE__)
5
+
6
+ module Pepper
7
+
8
+ VERSION='0.3.1'
9
+
10
+ class Request < Rack::Request
11
+ def method_sym
12
+ request_method.to_s.downcase.to_sym
13
+ end
14
+ def path_info_slices
15
+ ['/'] + path_info.to_s.gsub(/\/+/, '/').sub(/^\/|\/$/, '').split('/')
16
+ end
17
+ end
18
+
19
+ module Context
20
+
21
+ class Action
22
+
23
+ #
24
+ # The main context class needs to be able to resfresh this when reloading this action instance
25
+ #
26
+ attr_accessor :app
27
+
28
+ attr :method
29
+ attr :options
30
+ attr :block
31
+
32
+ def initialize(method, app, options={}, &block)
33
+ @app=app
34
+ @method=method
35
+ @options=options
36
+ @block=block
37
+ end
38
+
39
+ def app; @app end
40
+ def request; app.request end
41
+ def params; request.params end
42
+
43
+ def method_missing(m,*args,&block)
44
+ @app.send(m, *args, &block) if [:header, :headers].include?(m)
45
+ end
46
+
47
+ def execute!; instance_eval(&@block) end
48
+
49
+ end
50
+
51
+ #
52
+ #
53
+ #
54
+ module InstanceMethods
55
+
56
+ #
57
+ # The main app class needs to be able to set this
58
+ #
59
+ attr_accessor :app
60
+
61
+ #
62
+ # attributes that are set by the "initialize" method
63
+ #
64
+ # can be a string, regexp, hash or symbol
65
+ # * string - the current path slice will be compared using ==
66
+ # * regexp - the current path slice will be compared using =~
67
+ # * hash - if the value matches, the current slice is assigned to the request.params using the key
68
+ # * the value can in-turn be any string, regexp, hash or symbol
69
+ # * symbol - can be used to match preset rules found in the +PATTERNS+ hash
70
+ # if the symbol is not found in +PATTERNS+, it's converted to a string and uses ==
71
+ #
72
+ attr :pattern
73
+ # The parent context
74
+ attr :parent
75
+ # the options passed into +map+ method calls
76
+ attr :options
77
+ # optional module name to delegate logic for a given sub-path
78
+ attr :delegate
79
+ # the block that holds the executable context code
80
+ attr :block
81
+
82
+ def method_missing(m,*args,&block)
83
+ @app.send(m, *args, &block) if [:header, :headers].include?(m)
84
+ end
85
+
86
+ #
87
+ # Create the HTTP action methods...
88
+ #
89
+ HTTP_METHODS=%W(get post put delete)
90
+ HTTP_METHODS.each do |m|
91
+ class_eval <<-EOF
92
+ def #{m}(options={}, &block)
93
+ if a=actions.detect {|a|a.method==:#{m}.to_sym and a.options==options}
94
+ # refresh the app - this is needed because the root context is a "static" instance
95
+ a.app=@app
96
+ return
97
+ end
98
+ actions << Action.new(:#{m}, @app, options, &block)
99
+ end
100
+ EOF
101
+ end
102
+
103
+ #
104
+ # "attributes" that are set after initialization
105
+ #
106
+
107
+ # returns the child contexts
108
+ def children; @children||=[]; end
109
+ # returns the value from the path slices that this context matched
110
+ def value; @value||=nil; end
111
+ # the base application instance
112
+ def app; @app; end
113
+ # returns instance of Rack::Request
114
+ def request; app.request; end
115
+ # shortcut for request.params
116
+ def params; request.params; end
117
+ #
118
+ def actions; @actions||=[] end
119
+ #
120
+ def before_blocks; @before_blocks||={} end
121
+ #
122
+ def after_blocks; @after_blocks||={} end
123
+
124
+ def before(action=:all, &block)
125
+ before_blocks[action]=block
126
+ end
127
+
128
+ def after(action=:all, &block)
129
+ after_blocks[action]=block
130
+ end
131
+
132
+ #
133
+ # Finds the action (get, post, put etc.) base on:
134
+ # * the method
135
+ # * the env/hash values
136
+ #
137
+ def find_action(method, env={})
138
+ actions.each do |a|
139
+ next if a.method != method
140
+ return a if a.options.nil? or a.options.size==0
141
+ a.options.each_pair do |k,v|
142
+ k=k.to_s.upcase
143
+ return a if env[k]==v or env[k]=~v
144
+ end
145
+ end
146
+ nil
147
+ end
148
+
149
+ PATTERNS={
150
+ :digit=>/^\d+$/,
151
+ :word=>/^\w+$/
152
+ }
153
+
154
+ #
155
+ #
156
+ #
157
+ def match?(pattern, value)
158
+ ok = simple_match?(pattern, value)
159
+ return ok if ok
160
+ # if hash, use the value as the pattern and set the request param[key] to the value argument
161
+ return (params[pattern.keys.first] = match?(pattern[pattern.keys.first], value)) if pattern.is_a?(Hash)
162
+ # if symbol
163
+ if pattern.is_a?(Symbol)
164
+ # lookup the symbol in the PATTERNS hash
165
+ return value if PATTERNS[pattern] and match?(PATTERNS[pattern], value)
166
+ # There was no match, convert to string
167
+ simple_match?(pattern.to_s, value)
168
+ end
169
+ end
170
+
171
+ #
172
+ #
173
+ #
174
+ def simple_match?(pattern, value)
175
+ # if string, simple comparison
176
+ return value if (pattern.is_a?(String) and value == pattern)
177
+ # if regexp, regx comparison
178
+ return value if (pattern.is_a?(Regexp) and value =~ pattern)
179
+ end
180
+
181
+ #
182
+ # Recursively looks for matching context instances returning the last one that matches.
183
+ #
184
+ def resolve(slices)
185
+ return unless match?(pattern, slices.first)
186
+ @value=slices.shift
187
+ catch :delegate do
188
+ instance_eval(&@block) if @block
189
+ end
190
+ if delegate
191
+ raise 'Only module based delegates are allowed' unless delegate.class==Module
192
+ return delegate.delegate!(self, [@value]+slices)
193
+ end
194
+ return self if slices.size==0
195
+ children.each do |child|
196
+ if found=child.resolve(slices)
197
+ return found
198
+ end
199
+ end
200
+ nil
201
+ end
202
+
203
+ #
204
+ # Shortcut assigning a delegate
205
+ #
206
+ def delegate_to(handler)
207
+ @delegate=handler
208
+ throw :delegate
209
+ end
210
+
211
+ def child_exists?(pattern, options)
212
+ children.detect{|child|child.pattern==pattern and child.options==options}
213
+ end
214
+
215
+ #
216
+ # The main method for creating child contexts
217
+ #
218
+ def map(pattern, options={}, delegate=nil, &block)
219
+ if child=child_exists?(pattern, options)
220
+ # refresh the app
221
+ child.app=@app
222
+ return child
223
+ else
224
+ children << Context::Base.new(pattern, options, delegate, self, &block)
225
+ children.last.app=app
226
+ end
227
+ end
228
+
229
+ end # end InstanceMethods
230
+
231
+ #
232
+ #
233
+ #
234
+
235
+ #
236
+ # Used for handling areas of your application, within the context of some path.
237
+ # For example, if you had a blog area of your site, you could off-load the logic
238
+ # from the main app to a "delegate" and keep the main app clear of too much clutter.
239
+ # It would actually be possible for your main app to do nothing but delegate.
240
+ #
241
+ module Delegate
242
+
243
+ #
244
+ # Shortcut for:
245
+ # extend Pepper::Context::Delegate::ClassMethods
246
+ #
247
+ def self.included(base)
248
+ base.extend ClassMethods
249
+ end
250
+
251
+ #
252
+ # Pepper::Context::Delegate::ClassMethods
253
+ #
254
+ module ClassMethods
255
+
256
+ #
257
+ # Bring in the standard context methods
258
+ #
259
+ include Context::InstanceMethods
260
+
261
+ #
262
+ # define the standard build method so the delegate can start creating the context tree
263
+ #
264
+ def build(options={}, &block)
265
+ @options=options
266
+ @block=block
267
+ end
268
+
269
+ #
270
+ # Sets up this module as a delegate handler
271
+ # delegating_for - Pepper::Context::Base instance
272
+ # slices - array of path fragments
273
+ #
274
+ def delegate!(delegating_for, slices)
275
+ @app=delegating_for.app
276
+ @parent=delegating_for.parent
277
+ # Set the @pattern to the delegating_for's value so that when resolve is called, there will be a match
278
+ @pattern=delegating_for.value
279
+ # add this delegate to the delegating_for.parent.children
280
+ delegating_for.parent.children.unshift self
281
+ # find the matching context based on the slices
282
+ return resolve(slices)
283
+ end
284
+ end
285
+
286
+ end
287
+
288
+ #
289
+ # The base context class that gets instantiated for each "map" call
290
+ #
291
+ class Base
292
+
293
+ include Context::InstanceMethods
294
+
295
+ #
296
+ #
297
+ #
298
+ def initialize(pattern, options={}, delegate=nil, parent=nil, &block)
299
+ @pattern=pattern
300
+ @options=options
301
+ @delegate=delegate
302
+ @block=block
303
+ @parent=parent
304
+ end
305
+
306
+ end
307
+
308
+ end
309
+
310
+ #########
311
+
312
+ module App
313
+
314
+ #
315
+ # Shortcut for:
316
+ # include Pepper::App::InstanceMethods
317
+ # extend Pepper::App::ClassMethods
318
+ #
319
+ def self.included(base)
320
+ base.extend ClassMethods
321
+ base.send :include, InstanceMethods
322
+ end
323
+
324
+ #
325
+ # The class methods; provides the main "build" method
326
+ #
327
+ module ClassMethods
328
+
329
+ attr :root
330
+
331
+ #
332
+ # The initial build, class method
333
+ #
334
+ def build(options={}, &block)
335
+ @root=Context::Base.new('/', options, nil, nil, &block)
336
+ end
337
+
338
+ end
339
+
340
+ #
341
+ # The interface for Rack to connect to using the +call+ method
342
+ #
343
+ module InstanceMethods
344
+
345
+ class Error404 < RuntimeError; end
346
+ class ErrorMethodNotSupported < RuntimeError; end
347
+ class ErrorRootContextNotFound < RuntimeError; end
348
+
349
+ def root; self.class.root end
350
+ def request; @request end
351
+
352
+ def response; @response; end
353
+
354
+ def header(name, value)
355
+ response[name]=value
356
+ end
357
+
358
+ def headers(hash)
359
+ hash.each_pair {|k,v|header(k,v)}
360
+ end
361
+
362
+ def call(rack_env)
363
+ @response=Rack::Response.new
364
+ @request=Request.new(rack_env)
365
+ raise ErrorRootContextNotFound if root.nil?
366
+ root.app=self
367
+ context = root.resolve(request.path_info_slices)
368
+ raise Error404 if context.nil?
369
+ action = context.find_action(request.method_sym, request.env)
370
+ raise ErrorMethodNotSupported if action.nil?
371
+ response.write action.execute!
372
+ response.finish
373
+ end
374
+ end
375
+ end
376
+
377
+ end
@@ -0,0 +1,54 @@
1
+ module Pepper::Demo
2
+
3
+ module Canning
4
+ include Pepper::Context::Delegate
5
+ build do
6
+ get do
7
+ 'CANNING PEPPERS: Small peppers may be left whole; large peppers may be quartered. Remove cores and seeds; blister and peel peppers; flatten whole peppers. Add ½ teaspoon of salt to each pint jar, if desired. Fill jars loosely with peppers and add fresh boiled water, leaving 1-inch headspace. Adjust lids and process half-pints or pints for 35 minutes at 11 pounds pressure in a dial gauge canner or at 10 pounds pressure in a weighted gauge canner.'
8
+ end
9
+ end
10
+ end
11
+
12
+ class Pickled
13
+
14
+ include Pepper::App
15
+
16
+ build do
17
+
18
+ header 'Content-Type', 'text/html'
19
+
20
+ get do
21
+ header 'Content-Type', 'text/plain'
22
+ 'CAUTION: When working with hot peppers, wear plastic gloves while handling them, or wash hands thoroughly with soap and water before touching your face.'
23
+ end
24
+
25
+ post{'We are creating a pickled pepper just for you. You will learn to love her.'}
26
+ put{'Adding a little more vinegar... removing some fuzz here and there.'}
27
+ delete{'Bad! Bad! Bad pickled pepper!'}
28
+
29
+ map :peter do
30
+ get{"I am Peter, the pickled-pepper-packer, I want to tell you my story."}
31
+ map :piper do
32
+ map :packed do
33
+ map :a do
34
+ map :pack do
35
+ map :of do
36
+ map :pickled do
37
+ map :peppers do
38
+ get{'zwiggle'}
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ map(:how_to_can) {delegate_to Canning}
49
+
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,7 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'pepper'
3
+ require 'spec'
4
+
5
+ def new_rack_env(path='/', attrs={})
6
+ Rack::MockRequest.env_for(path, attrs)
7
+ end
@@ -0,0 +1,69 @@
1
+ require File.join(File.dirname(__FILE__), 'common.rb')
2
+ require 'pepper/demo'
3
+
4
+ #
5
+ #
6
+ #
7
+ describe Pepper::Demo::Pickled do
8
+
9
+ before :each do
10
+ @app_class = Pepper::Demo::Pickled
11
+ @app=@app_class.new
12
+ end
13
+
14
+ it '(class) should respond to build' do
15
+ @app_class.should respond_to(:build)
16
+ end
17
+
18
+ it "should respond to :call" do
19
+ @app.should respond_to(:call)
20
+ end
21
+
22
+ it "should have a nil :request attribute until :call is executed" do
23
+ @app.request.should == nil
24
+ end
25
+
26
+ it "should have a :request attribute after calling the call method" do
27
+ @app.call(new_rack_env)
28
+ @app.should respond_to(:request)
29
+ @app.request.class.should == Pepper::Request
30
+ end
31
+
32
+ it "should not fail when requesting a POST, GET, PUT or DELETE to /" do
33
+ r=@app.call(new_rack_env)
34
+ r[0].should == 200
35
+ r[1].should == {'Content-Type'=>'text/plain'}
36
+ r[2].body.should == ['CAUTION: When working with hot peppers, wear plastic gloves while handling them, or wash hands thoroughly with soap and water before touching your face.']
37
+ #
38
+ r=@app.call(new_rack_env('/', {'REQUEST_METHOD'=>'POST'}))
39
+ r[0].should == 200
40
+ r[1].should == {'Content-Type'=>'text/html'}
41
+ r[2].body.should == ['We are creating a pickled pepper just for you. You will learn to love her.']
42
+ #
43
+ r=@app.call(new_rack_env('/', {'REQUEST_METHOD'=>'PUT'}))
44
+ r[0].should == 200
45
+ r[1].should == {'Content-Type'=>'text/html'}
46
+ r[2].body.should == ['Adding a little more vinegar... removing some fuzz here and there.']
47
+ #
48
+ r=@app.call(new_rack_env('/', {'REQUEST_METHOD'=>'DELETE'}))
49
+ r[0].should == 200
50
+ r[1].should == {'Content-Type'=>'text/html'}
51
+ r[2].body.should == ['Bad! Bad! Bad pickled pepper!']
52
+ end
53
+
54
+ it "should raise Error404 when requesting an invalid path" do
55
+ lambda {@app.call(new_rack_env('/blah'))}.should raise_error(@app.class::Error404)
56
+ end
57
+
58
+ it "should raise ErrorMethodNotSupported when requesting a valid path, but no matching request method action" do
59
+ lambda {@app.call(new_rack_env('/peter', {'REQUEST_METHOD'=>'DELETE'}))}.should raise_error(@app.class::ErrorMethodNotSupported)
60
+ end
61
+
62
+ it 'should support unlimited nesting depth' do
63
+ @app.call(new_rack_env('/peter/piper/packed/a/pack/of/pickled/peppers'))[2].body.should == ['zwiggle']
64
+ end
65
+
66
+ it 'should support delegating from one context block to another' do
67
+ @app.call(new_rack_env('/how_to_can'))[2].body.should == ['CANNING PEPPERS: Small peppers may be left whole; large peppers may be quartered. Remove cores and seeds; blister and peel peppers; flatten whole peppers. Add ½ teaspoon of salt to each pint jar, if desired. Fill jars loosely with peppers and add fresh boiled water, leaving 1-inch headspace. Adjust lids and process half-pints or pints for 35 minutes at 11 pounds pressure in a dial gauge canner or at 10 pounds pressure in a weighted gauge canner.']
68
+ end
69
+ end
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__), 'common.rb')
2
+
3
+ #
4
+ #
5
+ #
6
+ describe Pepper::Request do
7
+ it '(path_info_fragments return) should return an array' do
8
+ r=Pepper::Request.new(new_rack_env('/'))
9
+ r.path_info_slices.class.should == Array
10
+ end
11
+
12
+ it '(path_info_fragments return) should always start with a /' do
13
+ r=Pepper::Request.new(new_rack_env('a/test//to/a/path/'))
14
+ r.path_info_slices[0].should == '/'
15
+ r=Pepper::Request.new(new_rack_env('/a/test//to/a/path/'))
16
+ r.path_info_slices[0].should == '/'
17
+ end
18
+
19
+ it 'should remove duplicate slashes when responding to path_info_fragments' do
20
+ r=Pepper::Request.new(new_rack_env('a/test//to/a/path/'))
21
+ r.path_info_slices.should == ['/', 'a', 'test', 'to', 'a', 'path']
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mwmitchell-pepper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Matt Mitchell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-11 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rack
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.3.0
23
+ version:
24
+ description: Pepper is a Ruby, DSL based framework for creating RESTful web applications. It adheres to the Ruby Rack specification.
25
+ email: goodieboy@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - LICENSE
33
+ files:
34
+ - lib/pepper.rb
35
+ - lib/pepper/demo.rb
36
+ - README
37
+ - LICENSE
38
+ has_rdoc: true
39
+ homepage: http://github.com/mwmitchell/pepper
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --main
43
+ - README
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.0.1
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: Pepper is a Ruby, DSL based framework for creating RESTful web applications. It adheres to the Ruby Rack specification.
65
+ test_files:
66
+ - spec/common.rb
67
+ - spec/demo.spec.rb
68
+ - spec/request.spec.rb