sansom 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +173 -0
- data/examples/generic.rb +79 -0
- data/examples/mixin.rb +22 -0
- data/lib/sansom.rb +139 -0
- data/lib/sansom/version.rb +3 -0
- data/sansom.gemspec +23 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7c54015af146ba89239c6199fa38f6d32674f274
|
4
|
+
data.tar.gz: 01b09122f5014d586311449942d84962a51b1a89
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 806566785d4903fb29413b9c4606f259b154b2f6dc946139e20341ed69a08385c533818bbd1a5eab8553c3622387a22e51261a910b309e0e22810c0eba4f6733
|
7
|
+
data.tar.gz: 5805d9c95195d8b5b9ce80f20b0dbee5df425877f28ff63dba3f34dee3198969fd3ce4ee57282ec84e8f2c5f82aca291aa71be03fd8df5bc1aedb6542b4d3914
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
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
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Nathaniel Symer
|
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,173 @@
|
|
1
|
+
Sansom
|
2
|
+
===
|
3
|
+
|
4
|
+
Flexible, versatile, light web framework named after Sansom street in Philly.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
-
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem '.'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install .
|
20
|
+
|
21
|
+
Usage
|
22
|
+
-
|
23
|
+
|
24
|
+
It's pretty simple. Instead of `Class`s storing routes, `Object`s store routes.
|
25
|
+
|
26
|
+
There are two ways you can deploy `Sansom`:
|
27
|
+
|
28
|
+
# app.rb
|
29
|
+
|
30
|
+
#!/usr/bin/env ruby
|
31
|
+
|
32
|
+
require "sansom"
|
33
|
+
|
34
|
+
s = Sansom.new
|
35
|
+
s.get "/" do |r|
|
36
|
+
# r is a Rack::Request
|
37
|
+
[200, { "Content-Type" => "text/plain" }, ["Hello Sansom"]]
|
38
|
+
end
|
39
|
+
s.start
|
40
|
+
|
41
|
+
Or, the more production-ready version:
|
42
|
+
|
43
|
+
# config.ru
|
44
|
+
|
45
|
+
require "sansom"
|
46
|
+
|
47
|
+
s = Sansom.new
|
48
|
+
|
49
|
+
s.get "/" do |r|
|
50
|
+
# r is a Rack::Request
|
51
|
+
[200, { "Content-Type" => "text/plain" }, ["Hello Sansom"]]
|
52
|
+
end
|
53
|
+
|
54
|
+
run s
|
55
|
+
|
56
|
+
But `Sansom` can do more than just that:
|
57
|
+
|
58
|
+
It can be used in a similar fashion to Sinatra:
|
59
|
+
|
60
|
+
# myapi.rb
|
61
|
+
|
62
|
+
#!/usr/bin/env ruby
|
63
|
+
|
64
|
+
require "sansom"
|
65
|
+
|
66
|
+
class MyAPI < Sansom
|
67
|
+
# This method is used to define Sansom routes
|
68
|
+
def template
|
69
|
+
get "/" do |r|
|
70
|
+
[200, { "Content-Type" => "text/plain" }, ["Hello Sansom"]]
|
71
|
+
# r is a Rack::Request
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
And your `config.ru` file
|
77
|
+
|
78
|
+
# config.ru
|
79
|
+
|
80
|
+
require "sansom"
|
81
|
+
require "./myapi"
|
82
|
+
|
83
|
+
run MyAPI.new
|
84
|
+
|
85
|
+
Sansom can also map other instances of Sansom to a route. Check this:
|
86
|
+
|
87
|
+
# myapi.rb
|
88
|
+
|
89
|
+
#!/usr/bin/env ruby
|
90
|
+
|
91
|
+
require "sansom"
|
92
|
+
|
93
|
+
class MyAPI < Sansom
|
94
|
+
# This method is used to define Sansom routes
|
95
|
+
def template
|
96
|
+
get "/" do |r|
|
97
|
+
[200, { "Content-Type" => "text/plain" }, ["Hello Sansom"]]
|
98
|
+
# r is a Rack::Request
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
Let's say you've written a new version of your api. No problem.
|
104
|
+
|
105
|
+
# app.rb
|
106
|
+
|
107
|
+
require "sansom"
|
108
|
+
|
109
|
+
s = Sansom.new
|
110
|
+
s.map "/v1", MyAPI.new
|
111
|
+
s.map "/v2", MyNewAPI.new
|
112
|
+
s.start
|
113
|
+
|
114
|
+
Or maybe you want to mount your Rails/Sinatra/whatever app
|
115
|
+
|
116
|
+
# app.rb
|
117
|
+
|
118
|
+
require "sansom"
|
119
|
+
|
120
|
+
s = Sansom.new
|
121
|
+
s.map "/api", SinatraApp.new
|
122
|
+
s.map "/", RailsApp.new
|
123
|
+
s.start
|
124
|
+
|
125
|
+
Lastly, any object can become a "`Sansom`" through a mixin:
|
126
|
+
|
127
|
+
# mixin.ru
|
128
|
+
|
129
|
+
class Mixin < Hash
|
130
|
+
include Sansomable
|
131
|
+
|
132
|
+
def template
|
133
|
+
get "/sansomable" do |r|
|
134
|
+
[200, { "Content-Type" => "text/plain"}, ["Sansomable Hash"]]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
run Mixin.new
|
140
|
+
|
141
|
+
If you look at how `Sansom` is defined, it makes sense:
|
142
|
+
|
143
|
+
Sansom = Class.new Object
|
144
|
+
Sansom.include Sansomable
|
145
|
+
|
146
|
+
Matching
|
147
|
+
-
|
148
|
+
|
149
|
+
`Sansom` uses trees to match routes. It follows a certain set of rules:
|
150
|
+
|
151
|
+
- A matching order is enforced:
|
152
|
+
1. The route matching the path and verb
|
153
|
+
2. The first Subsansom that matches the route & verb
|
154
|
+
3. The first mounted non-`Sansom` rack app matching the route
|
155
|
+
|
156
|
+
Notes
|
157
|
+
-
|
158
|
+
|
159
|
+
- `Sansom` does not pollute _any_ `Object` methods, including `initialize`
|
160
|
+
- `Sansom` is under **140** lines of code at the time of writing. This includes
|
161
|
+
* Everything above
|
162
|
+
* Custom routing
|
163
|
+
|
164
|
+
Contributing
|
165
|
+
-
|
166
|
+
|
167
|
+
1. Fork it
|
168
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
169
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
170
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
171
|
+
5. Create a new Pull Request
|
172
|
+
|
173
|
+
**Please make sure you don't add tons and tons of code. Part of `Sansom`'s beauty is is brevity.**
|
data/examples/generic.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "sansom"
|
5
|
+
|
6
|
+
class Sansom
|
7
|
+
def food_response r
|
8
|
+
[200, { "Content-Type" => "text/plain"}, [{ :type => self.class.to_s, :name => r.path_info.split("/").reject(&:empty?).last}.to_json]]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Meat < Sansom
|
13
|
+
def template
|
14
|
+
get "/pork" do |r|
|
15
|
+
food_response r
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class NonAnimal < Sansom
|
21
|
+
def template
|
22
|
+
get "/quinoa" do |r|
|
23
|
+
food_response r
|
24
|
+
end
|
25
|
+
|
26
|
+
get "/tahini" do |r|
|
27
|
+
food_response r
|
28
|
+
end
|
29
|
+
|
30
|
+
get "/squash" do |r|
|
31
|
+
food_response r
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class AnimalProducts < Sansom
|
37
|
+
def template
|
38
|
+
get "/eggs" do |r|
|
39
|
+
food_response r
|
40
|
+
end
|
41
|
+
|
42
|
+
get "/milk" do |r|
|
43
|
+
food_response r
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class FoodTypes < Sansom
|
49
|
+
def template
|
50
|
+
map "/carnivorous", Meat.new
|
51
|
+
map "/vegetarian", NonAnimal.new
|
52
|
+
map "/vegetarian", AnimalProducts.new
|
53
|
+
map "/vegan", NonAnimal.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Food < Sansom
|
58
|
+
def template
|
59
|
+
get "/sushi" do |r|
|
60
|
+
[200, { "Content-Type" => "text/plain"}, ["Quite delicious, especially cucumber"]]
|
61
|
+
end
|
62
|
+
|
63
|
+
map "/types", FoodTypes.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
s = Sansom.new
|
68
|
+
|
69
|
+
s.get "/" do |r|
|
70
|
+
[200, { "Content-Type" => "text/plain"}, ["root"]]
|
71
|
+
end
|
72
|
+
|
73
|
+
s.get "/something" do |r|
|
74
|
+
[200, { "Content-Type" => "text/plain"}, ["something"]]
|
75
|
+
end
|
76
|
+
|
77
|
+
s.map "/food", Food.new
|
78
|
+
|
79
|
+
s.start 2000
|
data/examples/mixin.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "sansom"
|
4
|
+
|
5
|
+
class Mixin < Hash
|
6
|
+
include Sansomable
|
7
|
+
|
8
|
+
def template
|
9
|
+
get "/sansomable" do |r|
|
10
|
+
[200, { "Content-Type" => "text/plain"}, ["Sansomable Hash"]]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
s = Sansom.new
|
16
|
+
|
17
|
+
s.get "/" do |r|
|
18
|
+
[200, { "Content-Type" => "text/plain"}, ["root"]]
|
19
|
+
end
|
20
|
+
|
21
|
+
s.map "/mixins", Mixin.new
|
22
|
+
s.start 3002
|
data/lib/sansom.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
require "tree" # rubytree
|
5
|
+
|
6
|
+
module Sansomable
|
7
|
+
class TreeContent
|
8
|
+
attr_accessor :items
|
9
|
+
def initialize
|
10
|
+
@items = []
|
11
|
+
@map = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(k,v)
|
15
|
+
@items << v if k == :map
|
16
|
+
@map[k] = v unless k == :map
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](k)
|
20
|
+
@items[k] if Numeric === k
|
21
|
+
@map[k] unless Numeric === k
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
InvalidRouteError = Class.new StandardError
|
26
|
+
NoRoutesError = Class.new StandardError
|
27
|
+
InclusionError = Class.new StandardError
|
28
|
+
|
29
|
+
HTTP_VERBS = ["GET","HEAD","POST","PUT","DELETE","PATCH","OPTIONS"].freeze
|
30
|
+
HANDLERS = ["puma", "unicorn", "thin", "webrick"].freeze
|
31
|
+
NOT_FOUND = [404, {"Content-Type" => "text/plain"}, ["Not found."]].freeze
|
32
|
+
|
33
|
+
def tree
|
34
|
+
@tree ||= nil
|
35
|
+
if @tree.nil?
|
36
|
+
@tree = Tree::TreeNode.new("ROOT", "ROOT")
|
37
|
+
template if respond_to? :template
|
38
|
+
end
|
39
|
+
@tree
|
40
|
+
end
|
41
|
+
|
42
|
+
def match http_method, path
|
43
|
+
components = s_parse_path(path)
|
44
|
+
matched_components = []
|
45
|
+
|
46
|
+
walk = components.inject(tree) do |node, component|
|
47
|
+
child = node[component]
|
48
|
+
|
49
|
+
if child.nil?
|
50
|
+
node
|
51
|
+
else
|
52
|
+
matched_components << component unless component == "/"
|
53
|
+
child
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
tc = walk.content
|
58
|
+
|
59
|
+
return nil if tc == "ROOT"
|
60
|
+
|
61
|
+
matched_path = "/" + matched_components.join("/")
|
62
|
+
|
63
|
+
match = tc[http_method] # Check for route
|
64
|
+
match ||= tc.items.select(&method(:sansom?)).reject { |item| item.match(http_method, s_truncate_path(path, matched_path)).nil? }.first rescue nil # Check subsansoms
|
65
|
+
match ||= tc.items.reject(&method(:sansom?)).first rescue nil # Check for mounted rack apps
|
66
|
+
[match, matched_path]
|
67
|
+
end
|
68
|
+
|
69
|
+
def call env
|
70
|
+
return NOT_FOUND if tree.children.empty?
|
71
|
+
|
72
|
+
r = Rack::Request.new env
|
73
|
+
|
74
|
+
m = match r.request_method, r.path_info
|
75
|
+
item = m.first
|
76
|
+
|
77
|
+
if item.nil?
|
78
|
+
NOT_FOUND
|
79
|
+
elsif Proc === item
|
80
|
+
item.call r
|
81
|
+
elsif sansom? item
|
82
|
+
item.call(env.dup.merge({ "PATH_INFO" => s_truncate_path(r.path_info, m.last) }))
|
83
|
+
else
|
84
|
+
raise InvalidRouteError, "Invalid route handler, it must be a block (proc/lambda) or a subclass of Sansom."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def start port=3001
|
89
|
+
raise NoRoutesError if tree.children.empty?
|
90
|
+
Rack::Handler.pick(HANDLERS).run self, :Port => port
|
91
|
+
end
|
92
|
+
|
93
|
+
def method_missing(meth, *args, &block)
|
94
|
+
_args = args.dup.push block
|
95
|
+
super unless _args.count >= 2
|
96
|
+
|
97
|
+
path = _args[0].dup
|
98
|
+
item = _args[1].dup
|
99
|
+
|
100
|
+
return super if item == self
|
101
|
+
|
102
|
+
verb = meth.to_s.strip.upcase
|
103
|
+
return super unless HTTP_VERBS.include?(verb) || meth == :map
|
104
|
+
verb = :map if meth == :map
|
105
|
+
|
106
|
+
components = s_parse_path path
|
107
|
+
components.each_with_index.inject(tree) do |node,(component, idx)|
|
108
|
+
child = node[component]
|
109
|
+
|
110
|
+
if child.nil?
|
111
|
+
newvalue = Tree::TreeNode.new(component, TreeContent.new)
|
112
|
+
node << newvalue
|
113
|
+
child = newvalue
|
114
|
+
end
|
115
|
+
|
116
|
+
child.content[verb] = item if idx == components.count-1
|
117
|
+
child
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def sansom? obj
|
124
|
+
return true if Sansom === obj
|
125
|
+
return true if obj.class.included_modules.include? Sansomable
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
def s_parse_path path
|
130
|
+
path.split("/").reject(&:empty?).unshift("/")
|
131
|
+
end
|
132
|
+
|
133
|
+
def s_truncate_path truncated, truncator
|
134
|
+
"/" + s_parse_path(truncated)[s_parse_path(truncator).count..-1].join("/")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
Sansom = Class.new Object
|
139
|
+
Sansom.include Sansomable
|
data/sansom.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sansom/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sansom"
|
8
|
+
spec.version = Sansom::VERSION
|
9
|
+
spec.authors = ["Nathaniel Symer"]
|
10
|
+
spec.email = ["nate@natesymer.com"]
|
11
|
+
spec.summary = %q{Flexible, versatile, light web framework named after Sansom street in Philly.}
|
12
|
+
spec.description = %q{Flexible, versatile, light web framework named after Sansom street in Philly. It's under 140 lines of code & and it's lightning fast.}
|
13
|
+
spec.homepage = "http://github.com/fhsjaagshs/sansom"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_dependency "rubytree", "~> 0.9"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sansom
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathaniel Symer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubytree
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.9'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.9'
|
41
|
+
description: Flexible, versatile, light web framework named after Sansom street in
|
42
|
+
Philly. It's under 140 lines of code & and it's lightning fast.
|
43
|
+
email:
|
44
|
+
- nate@natesymer.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- examples/generic.rb
|
54
|
+
- examples/mixin.rb
|
55
|
+
- lib/sansom.rb
|
56
|
+
- lib/sansom/version.rb
|
57
|
+
- sansom.gemspec
|
58
|
+
homepage: http://github.com/fhsjaagshs/sansom
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.2.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Flexible, versatile, light web framework named after Sansom street in Philly.
|
82
|
+
test_files: []
|