joshbuddy-usher 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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