joshbuddy-usher 0.3.6 → 0.4.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/README.rdoc CHANGED
@@ -67,7 +67,7 @@ Sections of a route can be marked as "one and only one" by surrounding it with b
67
67
 
68
68
  == Rack
69
69
 
70
- === rackup.ru
70
+ === config.ru
71
71
 
72
72
  require 'usher'
73
73
  app = proc do |env|
@@ -82,8 +82,10 @@ Sections of a route can be marked as "one and only one" by surrounding it with b
82
82
  ]
83
83
  end
84
84
 
85
- routes = Usher::Interface.for(:rack)
86
- routes.add('/hello/:name').to(app)
85
+ routes = Usher::Interface.for(:rack) do
86
+ add('/hello/:name').to(app)
87
+ end
88
+
87
89
  run routes
88
90
 
89
91
  ------------
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  s.homepage = "http://github.com/joshbuddy/usher"
10
10
  s.authors = ["Joshua Hull"]
11
11
  s.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"]
12
- s.add_dependency 'joshbuddy-fuzzy_hash', '>=0.0.2'
12
+ s.add_dependency 'joshbuddy-fuzzy_hash', '>=0.0.3'
13
13
  end
14
14
  rescue LoadError
15
15
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 3
4
- :patch: 6
3
+ :minor: 4
4
+ :patch: 0
data/lib/usher/grapher.rb CHANGED
@@ -11,7 +11,6 @@ class Usher
11
11
  @significant_keys = nil
12
12
  @orders = Hash.new{|h,k| h[k] = Hash.new{|h2, k2| h2[k2] = []}}
13
13
  @key_count = Hash.new(0)
14
- @cache = {}
15
14
  end
16
15
 
17
16
  def add_route(route)
@@ -14,7 +14,7 @@ class Usher
14
14
  instance_eval(&blk) if blk
15
15
  end
16
16
 
17
- def add(path, options = {})
17
+ def add(path, options = nil)
18
18
  @routes.add_route(path, options)
19
19
  end
20
20
 
@@ -7,7 +7,7 @@ class Usher
7
7
  @set = set
8
8
  end
9
9
 
10
- def connect(path, options = {})
10
+ def connect(path, options = nil)
11
11
  @set.add_route(path, options)
12
12
  end
13
13
 
@@ -21,7 +21,7 @@ class Usher
21
21
  named_route(:root, '/', options)
22
22
  end
23
23
 
24
- def named_route(name, path, options = {})
24
+ def named_route(name, path, options = nil)
25
25
  @set.add_named_route(name, path, options)
26
26
  end
27
27
 
data/lib/usher/node.rb CHANGED
@@ -14,10 +14,14 @@ class Usher
14
14
  def initialize(parent, value)
15
15
  @parent = parent
16
16
  @value = value
17
- @lookup = FuzzyHash.new
17
+ @lookup = Hash.new
18
18
  @exclusive_type = nil
19
19
  end
20
20
 
21
+ def upgrade_lookup
22
+ @lookup = FuzzyHash.new(@lookup)
23
+ end
24
+
21
25
  def depth
22
26
  @depth ||= @parent && @parent.is_a?(Node) ? @parent.depth + 1 : 0
23
27
  end
@@ -54,6 +58,7 @@ class Usher
54
58
  key = parts.shift
55
59
  target_node = case key
56
60
  when Route::RequestMethod
61
+ current_node.upgrade_lookup if key.value.is_a?(Regexp)
57
62
  if current_node.exclusive_type == key.type
58
63
  current_node.lookup[key.value] ||= Node.new(current_node, key)
59
64
  elsif current_node.lookup.empty?
@@ -64,7 +69,15 @@ class Usher
64
69
  current_node.lookup[nil] ||= Node.new(current_node, Route::RequestMethod.new(current_node.exclusive_type, nil))
65
70
  end
66
71
  else
67
- current_node.lookup[key.is_a?(Route::Variable) ? nil : key] ||= Node.new(current_node, key)
72
+ if !key.is_a?(Route::Variable)
73
+ current_node.upgrade_lookup if key.is_a?(Regexp)
74
+ current_node.lookup[key] ||= Node.new(current_node, key)
75
+ elsif key.regex_matcher
76
+ current_node.upgrade_lookup
77
+ current_node.lookup[key.regex_matcher] ||= Node.new(current_node, key)
78
+ else
79
+ current_node.lookup[nil] ||= Node.new(current_node, key)
80
+ end
68
81
  end
69
82
  current_node = target_node
70
83
  end
@@ -86,20 +99,45 @@ class Usher
86
99
  if terminates?
87
100
  Response.new(terminates, params)
88
101
  elsif params.last.is_a?(Array) && @lookup[nil]
89
- Response.new(@lookup[nil].terminates, params)
102
+ if @lookup[nil].exclusive_type
103
+ @lookup[nil].find(request, path, params)
104
+ else
105
+ Response.new(@lookup[nil].terminates, params)
106
+ end
90
107
  end
91
108
  elsif next_part = @lookup[part]
92
- next_part.find(request, path, params)
93
- elsif next_part = @lookup[nil]
94
109
  if next_part.value.is_a?(Route::Variable)
95
110
  part = next_part.value.transform!(part)
96
111
  next_part.value.valid!(part)
112
+ var = next_part.value
113
+ params << [next_part.value.name, part]
114
+ until (path.first == var.look_ahead) || path.empty?
115
+ params.last.last << path.shift.to_s
116
+ end
117
+ next_part.find(request, path, params)
118
+ else
119
+ next_part.find(request, path, params)
120
+ end
121
+ elsif next_part = @lookup[part] || next_part = @lookup[nil]
122
+ if next_part.value.is_a?(Route::Variable)
97
123
  case next_part.value.type
98
124
  when :*
99
125
  params << [next_part.value.name, []] unless params.last && params.last.first == next_part.value.name
100
- params.last.last << part unless part.is_a?(Symbol)
101
- find(request, path, params)
126
+ if next_part.value.look_ahead === part
127
+ path.unshift(part)
128
+ path.unshift(next_part.parent.value) if next_part.parent.value.is_a?(Symbol)
129
+ next_part.find(request, path, params)
130
+ else
131
+ unless part.is_a?(Symbol)
132
+ part = next_part.value.transform!(part)
133
+ next_part.value.valid!(part)
134
+ params.last.last << part
135
+ end
136
+ find(request, path, params)
137
+ end
102
138
  when :':'
139
+ part = next_part.value.transform!(part)
140
+ next_part.value.valid!(part)
103
141
  var = next_part.value
104
142
  params << [next_part.value.name, part]
105
143
  until (path.first == var.look_ahead) || path.empty?
@@ -1,14 +1,15 @@
1
1
  class Usher
2
2
  class Route
3
3
  class Variable
4
- attr_reader :type, :name, :validator, :transformer
4
+ attr_reader :type, :name, :validator, :transformer, :regex_matcher
5
5
  attr_accessor :look_ahead
6
6
 
7
- def initialize(type, name, opts = {})
7
+ def initialize(type, name, validator = nil, transformer = nil, regex_matcher = nil)
8
8
  @type = type
9
9
  @name = :"#{name}"
10
- @validator = opts[:validator]
11
- @transformer = opts[:transformer]
10
+ @validator = validator
11
+ @transformer = transformer
12
+ @regex_matcher = regex_matcher
12
13
  end
13
14
 
14
15
  def to_s
data/lib/usher/route.rb CHANGED
@@ -8,14 +8,15 @@ class Usher
8
8
  class Route
9
9
  attr_reader :paths, :original_path, :requirements, :conditions, :params, :primary_path
10
10
 
11
- def initialize(original_path, router, options = {}) # :nodoc:
11
+ def initialize(original_path, router, options = nil) # :nodoc:
12
12
  @original_path = original_path
13
13
  @router = router
14
- @requirements = options.delete(:requirements)
15
- @conditions = options.delete(:conditions)
16
- @transformers = options.delete(:transformers)
14
+ @requirements = options && options.delete(:requirements)
15
+ @conditions = options && options.delete(:conditions)
16
+ @transformers = options && options.delete(:transformers)
17
17
  @paths = @router.splitter.split(@original_path, @requirements, @transformers).collect {|path| Path.new(self, path)}
18
18
  @primary_path = @paths.first
19
+ #FIXME params is poorly named. this shouldn't be an array
19
20
  @params = []
20
21
  end
21
22
 
@@ -27,8 +28,8 @@ class Usher
27
28
  # route = set.add_route('/test')
28
29
  # route.to(:controller => 'testing', :action => 'index')
29
30
  # set.recognize(Request.new('/test')).first.params => {:controller => 'testing', :action => 'index'}
30
- def to(options)
31
- @params << options
31
+ def to(options = nil, &block)
32
+ @params << (block_given? ? block : options)
32
33
  self
33
34
  end
34
35
 
@@ -8,7 +8,7 @@ class Usher
8
8
 
9
9
  SplitterInstance.new(
10
10
  delimiters,
11
- Regexp.new('((:|\*)?[0-9A-Za-z\$\-_\+!\*\',]+|' + delimiters_regex + '|\(|\)|\|)'),
11
+ Regexp.new('((:|\*)?[0-9A-Za-z\$\-_\+!\*\',]+|' + delimiters_regex + '|\(|\)|\||\{)'),
12
12
  Regexp.new(delimiters_regex + '|[0-9A-Za-z\$\-_\+!\*\',]+')
13
13
  )
14
14
  end
@@ -40,7 +40,7 @@ class Usher
40
40
  parts
41
41
  end
42
42
 
43
- def split(path, requirements = {}, transformers = {})
43
+ def split(path, requirements = nil, transformers = nil)
44
44
  parts = Group.new(:all, nil)
45
45
  ss = StringScanner.new(path)
46
46
  current_group = parts
@@ -48,8 +48,32 @@ class Usher
48
48
  part = ss.scan(@split_regex)
49
49
  case part[0]
50
50
  when ?*, ?:
51
- type = (part[1] == ?: ? part.slice!(0,2) : part.slice!(0).chr).to_sym
52
- current_group << Usher::Route::Variable.new(type, part, :validator => requirements[part.to_sym], :transformer => transformers[part.to_sym])
51
+ type = part.slice!(0).chr.to_sym
52
+ current_group << Usher::Route::Variable.new(type, part, requirements && requirements[part.to_sym], transformers && transformers[part.to_sym])
53
+ when ?{
54
+ pattern = ''
55
+ count = 1
56
+ variable = ss.scan(/:([^,]+),/)
57
+ until count.zero?
58
+ regex_part = ss.scan(/\{|\}|[^\{\}]+/)
59
+ case regex_part[0]
60
+ when ?{
61
+ count += 1
62
+ when ?}
63
+ count -= 1
64
+ end
65
+ pattern << regex_part
66
+ end
67
+ pattern.slice!(pattern.size - 1)
68
+ regex = Regexp.new(pattern)
69
+ if variable
70
+ variable_type = variable.slice!(0).chr.to_sym
71
+ variable_name = variable[0, variable.size - 1].to_sym
72
+ current_group << Usher::Route::Variable.new(variable_type, variable_name, requirements && requirements[variable_name], transformers && transformers[variable_name], regex)
73
+ else
74
+ current_group << regex
75
+ end
76
+
53
77
  when ?(
54
78
  new_group = Group.new(:any, current_group)
55
79
  current_group << new_group
@@ -75,20 +99,16 @@ class Usher
75
99
  end unless !path || path.empty?
76
100
  paths = calc_paths(parts)
77
101
  paths.each do |path|
78
- last_delimiter = nil
79
- last_variable = nil
80
- path.each do |part|
81
- case part
82
- when Symbol
83
- last_delimiter = part
84
- when Usher::Route::Variable
85
- if last_variable
86
- last_variable.look_ahead = last_delimiter || @delimiters.first.to_sym
102
+ path.each_with_index do |part, index|
103
+ if part.is_a?(Usher::Route::Variable)
104
+ case part.type
105
+ when :*
106
+ part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Symbol) && !p.is_a?(Usher::Route::Variable)} || nil
107
+ when :':'
108
+ part.look_ahead = path[index + 1, path.size].find{|p| p.is_a?(Symbol)} || @delimiters.first.to_sym
87
109
  end
88
- last_variable = part
89
110
  end
90
111
  end
91
- last_variable.look_ahead = last_delimiter || @delimiters.first.to_sym if last_variable
92
112
  end
93
113
  paths
94
114
  end
data/lib/usher.rb CHANGED
@@ -45,9 +45,9 @@ class Usher
45
45
  # The +request_methods+ are methods that are called against the request object in order to
46
46
  # enforce the +conditions+ segment of the routes. For HTTP routes (and in fact the default), those
47
47
  # methods are <tt>[:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method]</tt>.
48
- def initialize(options = {})
49
- @delimiters = options.delete(:options) || ['/', '.']
50
- @request_methods = options.delete(:request_methods) || [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]
48
+ def initialize(options = nil)
49
+ @delimiters = options && options.delete(:options) || ['/', '.']
50
+ @request_methods = options && options.delete(:request_methods) || [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]
51
51
  @splitter = Splitter.for_delimiters(@delimiters)
52
52
  reset!
53
53
  end
@@ -56,7 +56,7 @@ class Usher
56
56
  #
57
57
  # set = Usher.new
58
58
  # set.add_named_route(:test_route, '/test')
59
- def add_named_route(name, path, options = {})
59
+ def add_named_route(name, path, options = nil)
60
60
  add_route(path, options).name(name)
61
61
  end
62
62
 
@@ -113,19 +113,20 @@ class Usher
113
113
  # * +requirements+ - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
114
114
  # * +conditions+ - Accepts any of the +request_methods+ specificied in the construction of Usher. This can be either a <tt>string</tt> or a regular expression.
115
115
  # * Any other key is interpreted as a requirement for the variable of its name.
116
- def add_route(path, options = {})
117
- transformers = options.delete(:transformers) || {}
118
- conditions = options.delete(:conditions) || {}
119
- requirements = options.delete(:requirements) || {}
120
- options.delete_if do |k, v|
121
- if v.is_a?(Regexp) || v.is_a?(Proc)
122
- requirements[k] = v
123
- true
116
+ def add_route(path, options = nil)
117
+ transformers = options && options.delete(:transformers) || {}
118
+ conditions = options && options.delete(:conditions) || {}
119
+ requirements = options && options.delete(:requirements) || {}
120
+ if options
121
+ options.delete_if do |k, v|
122
+ if v.is_a?(Regexp) || v.is_a?(Proc)
123
+ requirements[k] = v
124
+ true
125
+ end
124
126
  end
125
127
  end
126
-
127
128
  route = Route.new(path, self, {:transformers => transformers, :conditions => conditions, :requirements => requirements})
128
- route.to(options) unless options.empty?
129
+ route.to(options) if options && !options.empty?
129
130
 
130
131
  @tree.add(route)
131
132
  @routes << route
@@ -160,10 +161,10 @@ class Usher
160
161
  # set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
161
162
  # set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
162
163
  # set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
163
- def generate_url(route, params = {}, options = {})
164
- check_variables = options.key?(:check_variables) ? options.delete(:check_variables) : false
165
- delimiter = options.key?(:delimiter) ? options.delete(:delimiter) : @delimiters.first
166
- extra_params = options.key?(:extra_params) ? options.delete(:extra_params) : {}
164
+ def generate_url(route, params = nil, options = nil)
165
+ check_variables = options && options.key?(:check_variables) ? options.delete(:check_variables) : false
166
+ delimiter = options && options.key?(:delimiter) ? options.delete(:delimiter) : @delimiters.first
167
+ extra_params = options && options.key?(:extra_params) ? options.delete(:extra_params) : {}
167
168
 
168
169
  path = case route
169
170
  when Symbol
@@ -183,6 +184,8 @@ class Usher
183
184
  path.dynamic_parts.collect{|k| params_hash.delete(k.name) {|el| raise MissingParameterException.new(k.name)} }
184
185
  when Array
185
186
  path.dynamic_parts.size == params.size ? params : raise(MissingParameterException.new("got #{params.size} arguments, expected #{path.dynamic_parts.size}"))
187
+ when nil
188
+ nil
186
189
  else
187
190
  Array(params)
188
191
  end
@@ -40,6 +40,50 @@ describe "Usher route recognition" do
40
40
  route_set.recognize(build_request({:method => 'get', :path => '/sample/html/json/apple'})).params.should == [[:format, ['html', 'json', 'apple']]]
41
41
  end
42
42
 
43
+ it "should recgonize only a glob-style variable" do
44
+ target_route = route_set.add_route('/*format')
45
+ response = route_set.recognize(build_request({:method => 'get', :path => '/sample/html/json/apple'}))
46
+ response.params.should == [[:format, ['sample', 'html', 'json', 'apple']]]
47
+ response.path.route.should == target_route
48
+ end
49
+
50
+ it "should recgonize a regex static part" do
51
+ target_route = route_set.add_route('/test/part/{one|two}')
52
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/one'})).path.route.should == target_route
53
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/two'})).path.route.should == target_route
54
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/three'})).should == nil
55
+ end
56
+
57
+ it "should recgonize a regex static part containing {}'s" do
58
+ target_route = route_set.add_route('/test/part/{^o{2,3}$}')
59
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/oo'})).path.route.should == target_route
60
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/ooo'})).path.route.should == target_route
61
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/oooo'})).should == nil
62
+ end
63
+
64
+ it "should recgonize a regex static part containing {}'s" do
65
+ target_route = route_set.add_route('/test/part/{:test,hello|again}')
66
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/hello'})).path.route.should == target_route
67
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/hello'})).params.should == [[:test, 'hello']]
68
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/again'})).path.route.should == target_route
69
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/again'})).params.should == [[:test, 'again']]
70
+ route_set.recognize(build_request({:method => 'get', :path => '/test/part/world'})).should == nil
71
+ end
72
+
73
+ it "should recgonize two glob-style variables separated by a static part" do
74
+ target_route = route_set.add_route('/*format/innovate/*onemore')
75
+ response = route_set.recognize(build_request({:method => 'get', :path => '/sample/html/innovate/apple'}))
76
+ response.params.should == [[:format, ['sample', 'html']], [:onemore, ['apple']]]
77
+ response.path.route.should == target_route
78
+ end
79
+
80
+ it "should recgonize only a glob-style variable with a condition" do
81
+ target_route = route_set.add_route('/*format', :conditions => {:domain => 'test-domain'})
82
+ response = route_set.recognize(build_request({:method => 'get', :path => '/sample/html/json/apple', :domain => 'test-domain'}))
83
+ response.params.should == [[:format, ['sample', 'html', 'json', 'apple']]]
84
+ response.path.route.should == target_route
85
+ end
86
+
43
87
  it "should recognize a format-style literal" do
44
88
  target_route = route_set.add_route('/:action.html', :controller => 'sample', :action => 'action')
45
89
  route_set.recognize(build_request({:method => 'get', :path => '/sample.html', :domain => 'admin.host.com'})).should == Usher::Node::Response.new(target_route.paths.first, [[:action , 'sample']])
data/spec/split_spec.rb CHANGED
@@ -6,6 +6,11 @@ describe "Usher route tokenizing" do
6
6
  it "should split / delimited routes" do
7
7
  Usher::Splitter.for_delimiters(['/', '.']).split('/test/this/split').should == [[:/, 'test', :/,'this', :/, 'split']]
8
8
  end
9
+
10
+ it "should split / delimited routes with a regex in it" do
11
+ Usher::Splitter.for_delimiters(['/', '.']).
12
+ split('/test/{this}/split').should == [[:/, 'test', :/, /this/, :/, 'split']]
13
+ end
9
14
 
10
15
  it "should split on ' ' delimited routes as well" do
11
16
  Usher::Splitter.for_delimiters([' ']).split('test this split').should == [['test', :' ', 'this', :' ', 'split']]
@@ -50,7 +55,7 @@ describe "Usher route tokenizing" do
50
55
  Usher::Splitter.for_delimiters(['/', '.']).split('/test/this(/split(.:format))') == [
51
56
  [:/, "test", :/, "this"],
52
57
  [:/, "test", :/, "this", :/, "split"],
53
- [:/, "test", :/, "this", :/, "split", '.', Usher::Route::Variable.new(:':', :format)]
58
+ [:/, "test", :/, "this", :/, "split", :'.', Usher::Route::Variable.new(:':', :format)]
54
59
  ]
55
60
  end
56
61
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: joshbuddy-usher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Hull
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-27 00:00:00 -07:00
12
+ date: 2009-05-09 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.0.2
23
+ version: 0.0.3
24
24
  version:
25
25
  description: A general purpose routing library
26
26
  email: joshbuddy@gmail.com