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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c81abee4ed6b93558cbc51b117135cf674fde459
4
- data.tar.gz: 1bbfd96b4165afe38158c02ac0c0f8cbef6fa0da
3
+ metadata.gz: 6ea8ea9f12513294f151757423264d44870d210f
4
+ data.tar.gz: 405f6c890367eaa2c961cc4520f58b1bdc7ddc6d
5
5
  SHA512:
6
- metadata.gz: 52adf8b2ff6fcfa0ebc4e84647a5f3a912c02c47b50363dfd434eecf60f439ce1e32eec5f773b43b4333b7ece92c26824997283d8b62c113af99620cdd735ba0
7
- data.tar.gz: cc53ff5b59f6da3fb832133ec986131621e524ced4fc5e2971f7644c8036ccbac809db0f7557c5cb411cb2238e31309f123ff113804fa0e0a362a1cbecb56ab9
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
- Flexible, versatile, light web framework named after Sansom street in Philly.
4
+ Scientific, philosophical, abstract web 'picowork' named after Sansom street in Philly, where it was made.
5
5
 
6
- Installation
6
+ Philosophy
7
7
  -
8
8
 
9
- Add this line to your application's Gemfile:
9
+ *A piece of software should not limit you to one way of thinking.*
10
10
 
11
- gem '.'
11
+ You can write a `Sansomable` for each logical unit of your API, but you also don't have to.
12
12
 
13
- And then execute:
13
+ You can also mount existing Rails/Sinatra apps in your `Sansomable`. But you also don't have to.
14
14
 
15
- $ bundle
15
+ You can write one `Sansomable` for your entire API.
16
16
 
17
- Or install it yourself as:
17
+ Installation
18
+ -
18
19
 
19
- $ gem install .
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 **140** lines of code at the time of writing. This includes
161
- * Everything above
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
- # returns a node for chaining
25
- def <<(node)
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 root
46
- n = self
47
- n = n.parent while !n.root?
48
- n
21
+ def leaf?
22
+ @children.count == 0
49
23
  end
50
24
 
51
- def children
52
- @children.values
25
+ def wildcard?
26
+ @wildcard
53
27
  end
54
28
 
55
- def trim(node)
56
- @trimmed = true
57
- @children.clear
58
- @children[node.name] = node
59
- self
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 []=(k,v)
63
- self << Node.new(k, v)
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 [](k)
67
- @children[k]
68
- end
69
-
70
- def root?
71
- @parent.nil?
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 leaf?
75
- @children.count == 0
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 trimmed?
79
- @trimmed
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 inspect(level=0)
83
- if root?
84
- print "*"
85
- else
86
- print "|" unless parent.parent.children.last == parent rescue false
87
- print(' ' * level * 4)
88
- print(parent.children.last == self ? "+" : "|")
89
- print "---"
90
- print(leaf? ? ">" : "+")
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
- puts " #{name} #{content.map rescue "fuck"}"
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
- children.each { |child| child.inspect(level + 1) if child } # Child might be 'nil'
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 []=(k,v)
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 [](k)
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
- require "sansom/pine"
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, {"Content-Type" => "text/plain"}, ["Not found."]].freeze
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("ROOT", nil)
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.request_method, r.path_info
57
- item = m.first
30
+ m = tree.match r.path_info, r.request_method
58
31
 
59
- if item.nil?
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
- if before_block
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.children.empty?
54
+ raise NoRoutesError if tree.leaf?
80
55
  Rack::Handler.pick(HANDLERS).run self, :Port => port
81
56
  end
82
57
 
83
- def before(&block)
58
+ def before &block
84
59
  @before_block = block
85
60
  end
86
61
 
87
- def method_missing(meth, *args, &block)
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 if item == self
91
- return super unless HTTP_VERBS.include?(meth) || meth == :map
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.4"
7
+ s.version = "0.0.5"
8
8
  s.authors = ["Nathaniel Symer"]
9
9
  s.email = ["nate@natesymer.com"]
10
- s.summary = "Flexible, versatile, light web framework named after Sansom street in Philly."
11
- s.description = s.summary + "It's under 140 lines of code & and it's lightning fast. It uses tree-based route resolution."
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
- s.files = `git ls-files -z`.split("\x0")
16
- s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- s.test_files = s.files.grep(%r{^(test|spec|features)/})
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
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-30 00:00:00.000000000 Z
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: Flexible, versatile, light web framework named after Sansom street in
42
- Philly.It's under 140 lines of code & and it's lightning fast. It uses tree-based
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: Flexible, versatile, light web framework named after Sansom street in Philly.
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