sansom 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -11
- data/changelog.md +7 -1
- data/lib/sansom/pine.rb +68 -67
- data/lib/sansom.rb +33 -72
- data/sansom.gemspec +7 -6
- metadata +7 -9
- data/examples/before.rb +0 -20
- data/examples/generic.rb +0 -79
- data/examples/mixin.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ea8ea9f12513294f151757423264d44870d210f
|
4
|
+
data.tar.gz: 405f6c890367eaa2c961cc4520f58b1bdc7ddc6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f773bca14fef8bdfb91ba0147d97d952fc72189bd4c8d368e76c5920cf7d9c57c4073fdbd216e97c560f58c1e86481ca9737898d6090fb1246096a01e3e87532
|
7
|
+
data.tar.gz: da9038bc8791abbbf2aed558613d3b922e6dd95eb8feea4cc7e2d09e668536c565e0bd6b5d8fecfcc00907adfd0c0bb1c7b2bc7302152fdccd837831d0c33097
|
data/README.md
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
Sansom
|
2
2
|
===
|
3
3
|
|
4
|
-
|
4
|
+
Scientific, philosophical, abstract web 'picowork' named after Sansom street in Philly, where it was made.
|
5
5
|
|
6
|
-
|
6
|
+
Philosophy
|
7
7
|
-
|
8
8
|
|
9
|
-
|
9
|
+
*A piece of software should not limit you to one way of thinking.*
|
10
10
|
|
11
|
-
|
11
|
+
You can write a `Sansomable` for each logical unit of your API, but you also don't have to.
|
12
12
|
|
13
|
-
|
13
|
+
You can also mount existing Rails/Sinatra apps in your `Sansomable`. But you also don't have to.
|
14
14
|
|
15
|
-
|
15
|
+
You can write one `Sansomable` for your entire API.
|
16
16
|
|
17
|
-
|
17
|
+
Installation
|
18
|
+
-
|
18
19
|
|
19
|
-
|
20
|
+
`gem install sansom`
|
20
21
|
|
21
22
|
Usage
|
22
23
|
-
|
@@ -148,18 +149,20 @@ Matching
|
|
148
149
|
|
149
150
|
`Sansom` uses trees to match routes. It follows a certain set of rules:
|
150
151
|
|
152
|
+
- Wildcard routes can't have any siblings
|
151
153
|
- A matching order is enforced:
|
152
154
|
1. The route matching the path and verb
|
153
155
|
2. The first Subsansom that matches the route & verb
|
154
156
|
3. The first mounted non-`Sansom` rack app matching the route
|
157
|
+
|
155
158
|
|
156
159
|
Notes
|
157
160
|
-
|
158
161
|
|
159
162
|
- `Sansom` does not pollute _any_ `Object` methods, including `initialize`
|
160
|
-
- `Sansom` is under **
|
161
|
-
*
|
162
|
-
* Custom routing
|
163
|
+
- `Sansom` is under **190** lines of code at the time of writing. This includes
|
164
|
+
* Rack conformity & the DSL (`sansom.rb`)
|
165
|
+
* Custom tree-based routing (`pine.rb`)
|
163
166
|
|
164
167
|
Contributing
|
165
168
|
-
|
data/changelog.md
CHANGED
@@ -31,4 +31,10 @@ Here's an example
|
|
31
31
|
|
32
32
|
0.0.4
|
33
33
|
|
34
|
-
- Fixed bug with with requiring pine
|
34
|
+
- Fixed bug with with requiring pine
|
35
|
+
|
36
|
+
0.0.5
|
37
|
+
|
38
|
+
- Parameterized URLs!!! (Stuff like `/user/:id/profile`)
|
39
|
+
* Parameterized URLs work with mounted Rack/Sansom apps
|
40
|
+
- Improved matching efficiency
|
data/lib/sansom/pine.rb
CHANGED
@@ -2,115 +2,116 @@
|
|
2
2
|
|
3
3
|
# Path routing tree
|
4
4
|
|
5
|
-
# Custom tree implementation for path routing
|
6
|
-
|
7
|
-
# Custom features:
|
8
|
-
# 1. Trimming: Limit a node to a single element
|
9
|
-
|
10
5
|
module Pine
|
11
6
|
class Node
|
12
|
-
attr_reader :name
|
7
|
+
attr_reader :name, :parent
|
13
8
|
attr_accessor :content
|
14
|
-
attr_accessor :parent
|
15
9
|
|
16
10
|
def initialize name, content=Content.new
|
17
11
|
@name = name
|
18
12
|
@content = content
|
19
13
|
@children = {}
|
20
14
|
@parent = nil
|
21
|
-
@trimmed = false
|
22
15
|
end
|
23
16
|
|
24
|
-
|
25
|
-
|
26
|
-
if trimmed?
|
27
|
-
# Add to first child
|
28
|
-
children.first << node
|
29
|
-
else
|
30
|
-
node.parent = self
|
31
|
-
@children[node.name] = node
|
32
|
-
node
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def create_if_necessary name
|
37
|
-
unless @children.keys.include? name
|
38
|
-
child = self.class.new name
|
39
|
-
child.parent = self
|
40
|
-
@children[name] = child
|
41
|
-
end
|
42
|
-
@children[name]
|
17
|
+
def root?
|
18
|
+
@parent.nil?
|
43
19
|
end
|
44
20
|
|
45
|
-
def
|
46
|
-
|
47
|
-
n = n.parent while !n.root?
|
48
|
-
n
|
21
|
+
def leaf?
|
22
|
+
@children.count == 0
|
49
23
|
end
|
50
24
|
|
51
|
-
def
|
52
|
-
@
|
25
|
+
def wildcard?
|
26
|
+
@wildcard
|
53
27
|
end
|
54
28
|
|
55
|
-
def
|
56
|
-
@
|
57
|
-
|
58
|
-
|
59
|
-
|
29
|
+
def [] k
|
30
|
+
return @children[k] || @children.values.first
|
31
|
+
# child = @children[k] || @child.values.first
|
32
|
+
# return child unless child.nil?
|
33
|
+
|
34
|
+
# @children[k] || @children.values.first.wildcard? ? @children.values.first : nil
|
60
35
|
end
|
61
36
|
|
62
|
-
def
|
63
|
-
|
37
|
+
def create_and_save comp
|
38
|
+
child = self.class.new comp
|
39
|
+
child.instance_variable_set "@parent", self
|
40
|
+
@children[comp] = child
|
41
|
+
child
|
64
42
|
end
|
65
|
-
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
43
|
+
|
44
|
+
def << comp
|
45
|
+
if comp.start_with? ":"
|
46
|
+
@wildcard = true
|
47
|
+
@children.clear
|
48
|
+
create_and_save comp
|
49
|
+
else
|
50
|
+
child = @children[comp]
|
51
|
+
child = create_and_save comp if !child || (!child && child.leaf? && !child.wildcard?)
|
52
|
+
child
|
53
|
+
end
|
72
54
|
end
|
73
|
-
|
74
|
-
def
|
75
|
-
|
55
|
+
|
56
|
+
def parse_path path
|
57
|
+
c = path.split "/"
|
58
|
+
c[0] = '/'
|
59
|
+
c.delete_at(-1) if c[-1].empty?
|
60
|
+
c
|
76
61
|
end
|
77
62
|
|
78
|
-
def
|
79
|
-
|
63
|
+
def map_path path, item, key
|
64
|
+
parse_path(path).inject(self) { |node, comp| node << comp }.content[key] = item
|
65
|
+
path
|
80
66
|
end
|
81
67
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
68
|
+
def match path, verb
|
69
|
+
matched_comps = []
|
70
|
+
matched_params = {}
|
71
|
+
|
72
|
+
walk = parse_path(path).inject self do |node, comp|
|
73
|
+
break node if node.leaf?
|
74
|
+
matched_comps << comp unless comp == "/"
|
75
|
+
child = node[comp]
|
76
|
+
matched_params[child.name[1..-1]] = comp if node.wildcard?
|
77
|
+
child
|
91
78
|
end
|
92
79
|
|
93
|
-
|
80
|
+
return nil if walk.root? rescue true
|
81
|
+
|
82
|
+
c = walk.content
|
83
|
+
subpath = path.sub "/#{matched_comps.join("/")}", ""
|
84
|
+
|
85
|
+
match = c.map[verb.downcase.to_sym]
|
86
|
+
match ||= c.items.detect { |i| sansom?(i) && i.tree.match(subpath, verb) }
|
87
|
+
match ||= c.items.detect { |i| !sansom?(i) }
|
94
88
|
|
95
|
-
|
89
|
+
return nil if match.nil?
|
90
|
+
|
91
|
+
Result.new match, subpath, matched_params
|
92
|
+
end
|
93
|
+
|
94
|
+
def sansom? obj
|
95
|
+
obj.singleton_class.include? Sansomable
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
+
Result = Struct.new :item, :remaining_path, :url_params
|
100
|
+
|
99
101
|
class Content
|
100
|
-
attr_accessor :items
|
101
|
-
attr_accessor :map
|
102
|
+
attr_accessor :items, :map
|
102
103
|
|
103
104
|
def initialize
|
104
105
|
@items = []
|
105
106
|
@map = {}
|
106
107
|
end
|
107
108
|
|
108
|
-
def []=
|
109
|
+
def []= k,v
|
109
110
|
@items << v if k == :map
|
110
111
|
@map[k] = v unless k == :map
|
111
112
|
end
|
112
113
|
|
113
|
-
def []
|
114
|
+
def [] k
|
114
115
|
@items[k] if Numeric === k
|
115
116
|
@map[k] unless Numeric === k
|
116
117
|
end
|
data/lib/sansom.rb
CHANGED
@@ -1,109 +1,70 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require "rack"
|
4
|
-
|
4
|
+
require_relative "./sansom/pine"
|
5
5
|
|
6
6
|
module Sansomable
|
7
7
|
InvalidRouteError = Class.new StandardError
|
8
|
-
|
9
8
|
HTTP_VERBS = [:get,:head, :post, :put, :delete, :patch, :options].freeze
|
10
9
|
HANDLERS = ["puma", "unicorn", "thin", "webrick"].freeze
|
11
|
-
NOT_FOUND = [404, {
|
12
|
-
|
10
|
+
NOT_FOUND = [404, {}, ["Not found."]].freeze
|
11
|
+
|
13
12
|
def tree
|
14
|
-
@tree ||= nil
|
15
13
|
if @tree.nil?
|
16
|
-
@tree = Pine::Node.new
|
14
|
+
@tree = Pine::Node.new "ROOT"
|
17
15
|
template if respond_to? :template
|
18
16
|
end
|
19
17
|
@tree
|
20
18
|
end
|
21
19
|
|
22
|
-
def before_block
|
23
|
-
@before_block ||= nil
|
24
|
-
end
|
25
|
-
|
26
|
-
def match verb, path
|
27
|
-
components = s_parse_path(path)
|
28
|
-
matched_components = []
|
29
|
-
matched_parameters = {}
|
30
|
-
|
31
|
-
walk = components.inject(tree) do |node, component|
|
32
|
-
if node.leaf?
|
33
|
-
node
|
34
|
-
else
|
35
|
-
matched_components << component unless component == "/"
|
36
|
-
node[component]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
return nil if walk.root?
|
41
|
-
|
42
|
-
c = walk.content
|
43
|
-
matched_path = "/" + matched_components.join("/")
|
44
|
-
|
45
|
-
match = c[verb.downcase.to_sym] # Check for route
|
46
|
-
match ||= c.items.select(&method(:sansom?)).reject { |item| item.match(verb, path.sub(matched_path, "")).nil? }.first rescue nil # Check subsansoms
|
47
|
-
match ||= c.items.reject(&method(:sansom?)).first rescue nil # Check for mounted rack apps
|
48
|
-
[match, matched_path]
|
49
|
-
end
|
50
|
-
|
51
20
|
def call env
|
52
21
|
return NOT_FOUND if tree.leaf?
|
53
22
|
|
54
|
-
r = Rack::Request.new env
|
23
|
+
r = Rack::Request.new env.dup
|
24
|
+
|
25
|
+
if @before_block
|
26
|
+
res = @before_block.call r
|
27
|
+
return res if [Fixnum, Hash, Array]-res.map(&:class) == 0
|
28
|
+
end
|
55
29
|
|
56
|
-
m = match r.
|
57
|
-
item = m.first
|
30
|
+
m = tree.match r.path_info, r.request_method
|
58
31
|
|
59
|
-
if
|
32
|
+
if m.url_params.count > 0
|
33
|
+
q = r.params.merge(m.url_params)
|
34
|
+
s = q.map { |p| p.join '=' }.join("&")
|
35
|
+
r.env["rack.request.query_hash"] = q
|
36
|
+
r.env["rack.request.query_string"] = s
|
37
|
+
r.env["QUERY_STRING"] = s
|
38
|
+
r.instance_variable_set "@params", r.POST.merge(q)
|
39
|
+
end
|
40
|
+
|
41
|
+
if !m
|
60
42
|
NOT_FOUND
|
43
|
+
elsif m.item.is_a? Proc
|
44
|
+
m.item.call r
|
45
|
+
elsif m.item.respond_to? :call
|
46
|
+
r.env["PATH_INFO"] = m.remaining_path
|
47
|
+
m.item.call r.env
|
61
48
|
else
|
62
|
-
|
63
|
-
res = before_block.call r
|
64
|
-
return res if res[0].is_a?(Numeric) && res[1].is_a?(Hash) && res[2].respond_to?(:each) rescue false
|
65
|
-
end
|
66
|
-
|
67
|
-
if item.is_a? Proc
|
68
|
-
item.call r
|
69
|
-
elsif sansom? item
|
70
|
-
r.path_info.sub! m[1], ""
|
71
|
-
item.call(r.env)
|
72
|
-
else
|
73
|
-
raise InvalidRouteError, "Invalid route handler, it must be a block (proc/lambda) or a subclass of Sansom."
|
74
|
-
end
|
49
|
+
raise InvalidRouteError, "Route handlers must be blocks or valid rack apps."
|
75
50
|
end
|
76
51
|
end
|
77
52
|
|
78
53
|
def start port=3001
|
79
|
-
raise NoRoutesError if tree.
|
54
|
+
raise NoRoutesError if tree.leaf?
|
80
55
|
Rack::Handler.pick(HANDLERS).run self, :Port => port
|
81
56
|
end
|
82
57
|
|
83
|
-
def before
|
58
|
+
def before &block
|
84
59
|
@before_block = block
|
85
60
|
end
|
86
61
|
|
87
|
-
def method_missing
|
62
|
+
def method_missing meth, *args, &block
|
88
63
|
path, item = *args.dup.push(block)
|
89
64
|
return super unless path && item
|
90
|
-
return super
|
91
|
-
return super unless HTTP_VERBS.include?(meth)
|
92
|
-
|
93
|
-
n = s_parse_path(path).inject(tree) { |node, comp| node.create_if_necessary comp }
|
94
|
-
n.content[meth] = item
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
def sansom? obj
|
100
|
-
return true if obj.is_a? Sansom
|
101
|
-
return true if obj.class.included_modules.include? Sansomable
|
102
|
-
false
|
103
|
-
end
|
104
|
-
|
105
|
-
def s_parse_path path
|
106
|
-
path.split("/").reject(&:empty?).unshift("/")
|
65
|
+
return super unless item != self
|
66
|
+
return super unless (HTTP_VERBS+[:map]).include?(meth)
|
67
|
+
tree.map_path path, item, meth
|
107
68
|
end
|
108
69
|
end
|
109
70
|
|
data/sansom.gemspec
CHANGED
@@ -4,17 +4,18 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "sansom"
|
7
|
-
s.version = "0.0.
|
7
|
+
s.version = "0.0.5"
|
8
8
|
s.authors = ["Nathaniel Symer"]
|
9
9
|
s.email = ["nate@natesymer.com"]
|
10
|
-
s.summary = "
|
11
|
-
s.description = s.summary + "It's under
|
10
|
+
s.summary = "Scientific, philosophical, abstract web 'picowork' named after Sansom street in Philly, near where it was made."
|
11
|
+
s.description = s.summary + " " + "It's under 200 lines of code & it's lightning fast. It uses tree-based route resolution."
|
12
12
|
s.homepage = "http://github.com/fhsjaagshs/sansom"
|
13
13
|
s.license = "MIT"
|
14
14
|
|
15
|
-
|
16
|
-
s.
|
17
|
-
s.
|
15
|
+
allfiles = `git ls-files -z`.split("\x0")
|
16
|
+
s.files = allfiles.grep(%r{(^[^\/]*$|^lib\/)}) # Match all lib files AND files in the root
|
17
|
+
s.executables = allfiles.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
s.test_files = allfiles.grep(%r{^(test|spec|features)/})
|
18
19
|
s.require_paths = ["lib"]
|
19
20
|
|
20
21
|
s.add_development_dependency "bundler", "~> 1.6"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sansom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathaniel Symer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,9 +38,9 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1'
|
41
|
-
description:
|
42
|
-
Philly.It's under
|
43
|
-
route resolution.
|
41
|
+
description: Scientific, philosophical, abstract web 'picowork' named after Sansom
|
42
|
+
street in Philly, near where it was made. It's under 200 lines of code & it's lightning
|
43
|
+
fast. It uses tree-based route resolution.
|
44
44
|
email:
|
45
45
|
- nate@natesymer.com
|
46
46
|
executables: []
|
@@ -52,9 +52,6 @@ files:
|
|
52
52
|
- LICENSE.txt
|
53
53
|
- README.md
|
54
54
|
- changelog.md
|
55
|
-
- examples/before.rb
|
56
|
-
- examples/generic.rb
|
57
|
-
- examples/mixin.rb
|
58
55
|
- lib/sansom.rb
|
59
56
|
- lib/sansom/pine.rb
|
60
57
|
- sansom.gemspec
|
@@ -81,5 +78,6 @@ rubyforge_project:
|
|
81
78
|
rubygems_version: 2.2.2
|
82
79
|
signing_key:
|
83
80
|
specification_version: 4
|
84
|
-
summary:
|
81
|
+
summary: Scientific, philosophical, abstract web 'picowork' named after Sansom street
|
82
|
+
in Philly, near where it was made.
|
85
83
|
test_files: []
|
data/examples/before.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require_relative "../lib/sansom" rescue require "sansom"
|
4
|
-
|
5
|
-
s = Sansom.new
|
6
|
-
|
7
|
-
s.before do |r|
|
8
|
-
puts "(#{s.class.to_s}) #{r.request_method.upcase} #{r.path_info}"
|
9
|
-
[200, {}, ["Hijacked by before!"]] if Random.new.rand(2) == 1
|
10
|
-
end
|
11
|
-
|
12
|
-
s.get "/" do |r|
|
13
|
-
[200, { "Content-Type" => "text/plain"}, ["root"]]
|
14
|
-
end
|
15
|
-
|
16
|
-
s.get "/something" do |r|
|
17
|
-
[200, { "Content-Type" => "text/plain" }, ["something"]]
|
18
|
-
end
|
19
|
-
|
20
|
-
s.start 2000
|
data/examples/generic.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require_relative "../lib/sansom" rescue 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
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require_relative "../lib/sansom" rescue 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
|