joshbuddy-usher 0.3.0 → 0.3.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.
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'
12
+ s.add_dependency 'joshbuddy-fuzzy_hash', '>=0.0.2'
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
- :major: 0
3
2
  :minor: 3
4
- :patch: 0
3
+ :patch: 2
4
+ :major: 0
data/lib/usher.rb CHANGED
@@ -30,7 +30,7 @@ class Usher
30
30
  # set.reset!
31
31
  # set.empty? => true
32
32
  def reset!
33
- @tree = Node.root(self)
33
+ @tree = Node.root(self, @request_methods)
34
34
  @named_routes = {}
35
35
  @routes = []
36
36
  @route_count = 0
@@ -38,9 +38,16 @@ class Usher
38
38
  end
39
39
  alias clear! reset!
40
40
 
41
- # Creates a route set
42
- def initialize(delimiter = '/')
43
- @splitter = Splitter.delimiter(delimiter)
41
+ # Creates a route set, with optional Array of +delimiters+ and +request_methods+
42
+ #
43
+ # The +delimiters+ must be one character. By default <tt>['/', '.']</tt> are used.
44
+ # The +request_methods+ are methods that are called against the request object in order to
45
+ # enforce the +conditions+ segment of the routes. For HTTP routes (and in fact the default), those
46
+ # methods are <tt>[:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method]</tt>.
47
+ def initialize(delimiters = ['/', '.'], request_methods = [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method])
48
+ @delimiters = delimiters
49
+ @splitter = Splitter.for_delimiters(delimiters)
50
+ @request_methods = request_methods
44
51
  reset!
45
52
  end
46
53
 
@@ -103,7 +110,7 @@ class Usher
103
110
  # === +options+
104
111
  # * +transformers+ - Transforms a variable before it gets to the requirements. Takes either a +proc+ or a +symbol+. If its a +symbol+, calls the method on the incoming parameter. If its a +proc+, its called with the variable.
105
112
  # * +requirements+ - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
106
- # * +conditions+ - Accepts any of the following <tt>:protocol</tt>, <tt>:domain</tt>, <tt>:port</tt>, <tt>:query_string</tt>, <tt>:remote_ip</tt>, <tt>:user_agent</tt>, <tt>:referer</tt> and <tt>:method</tt>. This can be either a <tt>string</tt> or a regular expression.
113
+ # * +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.
107
114
  # * Any other key is interpreted as a requirement for the variable of its name.
108
115
  def add_route(path, options = {})
109
116
  transformers = options.delete(:transformers) || {}
@@ -116,7 +123,8 @@ class Usher
116
123
  end
117
124
  end
118
125
 
119
- route = Route.new(path, self, {:transformers => transformers, :conditions => conditions, :requirements => requirements}).to(options)
126
+ route = Route.new(path, self, {:transformers => transformers, :conditions => conditions, :requirements => requirements})
127
+ route.to(options) unless options.empty?
120
128
 
121
129
  @tree.add(route)
122
130
  @routes << route
@@ -131,8 +139,8 @@ class Usher
131
139
  # set = Usher.new
132
140
  # route = set.add_route('/test')
133
141
  # set.recognize(Request.new('/test')).path.route == route => true
134
- def recognize(request)
135
- @tree.find(request, @splitter.url_split(request.path))
142
+ def recognize(request, path = request.path)
143
+ @tree.find(request, @splitter.url_split(path))
136
144
  end
137
145
 
138
146
  # Recognizes a set of +parameters+ and gets the closest matching Usher::Route::Path or +nil+ if no route exists.
@@ -153,6 +161,7 @@ class Usher
153
161
  # set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
154
162
  def generate_url(route, params = {}, options = {})
155
163
  check_variables = options.key?(:check_variables) ? options.delete(:check_variables) : false
164
+ delimiter = options.key?(:delimiter) ? options.delete(:delimiter) : @delimiters.first
156
165
 
157
166
  path = case route
158
167
  when Symbol
@@ -184,16 +193,13 @@ class Usher
184
193
  case p.type
185
194
  when :*
186
195
  param_list.first.each {|dp| p.valid!(dp.to_s) } if check_variables
187
- generated_path << '/' << param_list.shift.collect{|dp| dp.to_s} * '/'
188
- when :'.:'
189
- p.valid!(param_list.first.to_s) if check_variables
190
- (dp = param_list.shift) && generated_path << '.' << dp.to_s
196
+ generated_path << param_list.shift.collect{|dp| dp.to_s} * delimiter
191
197
  else
192
198
  p.valid!(param_list.first.to_s) if check_variables
193
- (dp = param_list.shift) && generated_path << '/' << dp.to_s
199
+ (dp = param_list.shift) && generated_path << dp.to_s
194
200
  end
195
201
  else
196
- generated_path << '/' << p.to_s
202
+ generated_path << p.to_s
197
203
  end
198
204
  end
199
205
  unless params_hash.empty?
@@ -23,7 +23,7 @@ class Usher
23
23
  def call(env)
24
24
  response = @routes.recognize(Request.new(env['REQUEST_URI'], env['REQUEST_METHOD']))
25
25
  env['usher.params'] = response.params.inject({}){|h,(k,v)| h[k]=v; h }
26
- response.path.route.params.call(env)
26
+ response.path.route.params.first.call(env)
27
27
  end
28
28
 
29
29
  end
@@ -34,18 +34,19 @@ class Usher
34
34
  add_route('/:controller', options.merge({:action => 'index'}))
35
35
  @controller_route_added = true
36
36
  end
37
-
38
- options[:action] = 'index' unless options[:action]
39
37
 
38
+ options[:action] = 'index' unless options[:action]
39
+
40
+ path[0, 0] = '/' unless path[0] == ?/
40
41
  route = @usher.add_route(path, options)
41
- raise "your route must include a controller" unless route.primary_path.dynamic_set.include?(:controller) || route.params.include?(:controller)
42
+ raise "your route must include a controller" unless route.primary_path.dynamic_set.include?(:controller) || route.params.first.include?(:controller)
42
43
  route
43
44
  end
44
45
 
45
46
  def recognize(request)
46
47
  node = @usher.recognize(request)
47
48
  params = node.params.inject({}){|h,(k,v)| h[k]=v; h }
48
- request.path_parameters = (node.params.empty? ? node.path.route.params : node.path.route.params.merge(params)).with_indifferent_access
49
+ request.path_parameters = (node.params.empty? ? node.path.route.params.first : node.path.route.params.first.merge(params)).with_indifferent_access
49
50
  "#{request.path_parameters[:controller].camelize}Controller".constantize
50
51
  rescue
51
52
  raise ActionController::RoutingError, "No route matches #{request.path.inspect} with #{request.inspect}"
@@ -70,14 +71,16 @@ class Usher
70
71
  merged_options = options
71
72
  merged_options[:controller] = recall[:controller] unless options.key?(:controller)
72
73
  unless options.key?(:action)
73
- options[:action] = nil
74
+ options[:action] = ''
74
75
  end
75
76
  route_for_options(merged_options)
76
77
  end
77
78
  case method
78
79
  when :generate
79
80
  merged_options ||= recall.merge(options)
80
- generate_url(route, merged_options)
81
+ url = generate_url(route, merged_options)
82
+ url.slice!(-1) if url[-1] == ?/
83
+ url
81
84
  else
82
85
  raise "method #{method} not recognized"
83
86
  end
data/lib/usher/node.rb CHANGED
@@ -6,11 +6,10 @@ class Usher
6
6
 
7
7
  class Node
8
8
 
9
- ConditionalTypes = [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method]
10
9
  Response = Struct.new(:path, :params)
11
10
 
12
11
  attr_reader :lookup
13
- attr_accessor :terminates, :exclusive_type, :parent, :value
12
+ attr_accessor :terminates, :exclusive_type, :parent, :value, :request_methods
14
13
 
15
14
  def initialize(parent, value)
16
15
  @parent = parent
@@ -24,8 +23,10 @@ class Usher
24
23
  @depth ||= @parent && @parent.is_a?(Node) ? @parent.depth + 1 : 0
25
24
  end
26
25
 
27
- def self.root(route_set)
28
- self.new(route_set, nil)
26
+ def self.root(route_set, request_methods)
27
+ root = self.new(route_set, nil)
28
+ root.request_methods = request_methods
29
+ root
29
30
  end
30
31
 
31
32
  def has_globber?
@@ -63,8 +64,8 @@ class Usher
63
64
  def add(route)
64
65
  route.paths.each do |path|
65
66
  parts = path.parts.dup
66
- ConditionalTypes.each do |type|
67
- parts.push(Route::RequestMethod.new(type, route.conditions[type])) if route.conditions[type]
67
+ request_methods.each do |type|
68
+ parts.push(Route::RequestMethod.new(type, route.conditions[type])) if route.conditions.key?(type)
68
69
  end
69
70
 
70
71
  current_node = self
@@ -123,9 +124,6 @@ class Usher
123
124
  params.last.last << part
124
125
  when :':'
125
126
  params << [next_part.value.name, part]
126
- when:'.:'
127
- part.slice!(0)
128
- params << [next_part.value.name, part]
129
127
  end
130
128
  end
131
129
  next_part.find(request, path, params)
data/lib/usher/route.rb CHANGED
@@ -16,6 +16,7 @@ class Usher
16
16
  @transformers = 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
+ @params = []
19
20
  end
20
21
 
21
22
 
@@ -27,7 +28,7 @@ class Usher
27
28
  # route.to(:controller => 'testing', :action => 'index')
28
29
  # set.recognize(Request.new('/test')).first.params => {:controller => 'testing', :action => 'index'}
29
30
  def to(options)
30
- @params = options
31
+ @params << options
31
32
  self
32
33
  end
33
34
 
@@ -3,11 +3,13 @@ require 'strscan'
3
3
  class Usher
4
4
  class Splitter
5
5
 
6
- def self.delimiter(delimiter = '/')
6
+ def self.for_delimiters(delimiters)
7
+ delimiters_regex = delimiters.collect{|d| Regexp.quote(d)} * '|'
8
+
7
9
  SplitterInstance.new(
8
- delimiter,
9
- Regexp.new('((:|\*||\.:|\.)[0-9A-Za-z\$\-_\+!\*\',]+|' + Regexp.quote(delimiter) + '|\(|\)|\|)'),
10
- Regexp.new(Regexp.quote(delimiter) + '|\.?[0-9A-Za-z\$\-_\+!\*\',]+')
10
+ delimiters,
11
+ Regexp.new('((:|\*)?[0-9A-Za-z\$\-_\+!\*\',]+|' + delimiters_regex + '|\(|\)|\|)'),
12
+ Regexp.new(delimiters_regex + '|[0-9A-Za-z\$\-_\+!\*\',]+')
11
13
  )
12
14
  end
13
15
 
@@ -15,8 +17,9 @@ class Usher
15
17
 
16
18
  class SplitterInstance
17
19
 
18
- def initialize(delimiter, split_regex, url_split_regex)
19
- @delimiter = delimiter
20
+ def initialize(delimiters, split_regex, url_split_regex)
21
+ @delimiters = delimiters
22
+ @delimiter_chars = delimiters.collect{|d| d[0]}
20
23
  @split_regex = split_regex
21
24
  @url_split_regex = url_split_regex
22
25
  end
@@ -26,7 +29,12 @@ class Usher
26
29
  ss = StringScanner.new(path)
27
30
  while !ss.eos?
28
31
  if part = ss.scan(@url_split_regex)
29
- parts << part unless part == @delimiter
32
+ parts << case part[0]
33
+ when *@delimiter_chars
34
+ part.to_sym
35
+ else
36
+ part
37
+ end
30
38
  end
31
39
  end if path && !path.empty?
32
40
  parts
@@ -39,7 +47,7 @@ class Usher
39
47
  while !ss.eos?
40
48
  part = ss.scan(@split_regex)
41
49
  case part[0]
42
- when ?*, ?:, ?.
50
+ when ?*, ?:
43
51
  type = (part[1] == ?: ? part.slice!(0,2) : part.slice!(0).chr).to_sym
44
52
  current_group << Usher::Route::Variable.new(type, part, :validator => requirements[part.to_sym], :transformer => transformers[part.to_sym])
45
53
  when ?(
@@ -59,7 +67,8 @@ class Usher
59
67
  end
60
68
  current_group.parent << Group.new(:all, current_group.parent)
61
69
  current_group = current_group.parent.last
62
- when @delimiter[0]
70
+ when *@delimiter_chars
71
+ current_group << part.to_sym
63
72
  else
64
73
  current_group << part
65
74
  end
data/spec/path_spec.rb CHANGED
@@ -22,12 +22,12 @@ describe "Usher route adding" do
22
22
  it "should add every kind of optional route possible" do
23
23
  route_set.add_route('/a/b(/c)(/d(/e))')
24
24
  route_set.routes.first.paths.collect{|a| a.parts }.should == [
25
- ["a", "b"],
26
- ["a", "b", "c", "d"],
27
- ["a", "b", "d", "e"],
28
- ["a", "b", "c"],
29
- ["a", "b", "d"],
30
- ["a", "b", "c", "d", "e"]
25
+ [:/, "a", :/, "b"],
26
+ [:/, "a", :/, "b", :/, "c", :/, "d"],
27
+ [:/, "a", :/, "b", :/, "d", :/, "e"],
28
+ [:/, "a", :/, "b", :/, "c"],
29
+ [:/, "a", :/, "b", :/, "d"],
30
+ [:/, "a", :/, "b", :/, "c", :/, "d", :/, "e"]
31
31
  ]
32
32
 
33
33
  end
@@ -28,7 +28,7 @@ describe "Usher (for rack) route dispatching" do
28
28
 
29
29
  it "should dispatch a simple request" do
30
30
  env = {'REQUEST_URI' => '/sample', 'REQUEST_METHOD' => 'get', 'usher.params' => {}}
31
- route_set.add('/sample', :controller => 'sample', :action => 'action').to(build_app_mock(env.dup))
31
+ route_set.add('/sample').to(build_app_mock(env.dup))
32
32
  route_set.call(env)
33
33
  end
34
34
 
@@ -10,17 +10,17 @@ describe "Usher (for rails) URL generation" do
10
10
  end
11
11
 
12
12
  it "should fill in the controller from recall" do
13
- route_set.add_route(':controller/:action/:id')
13
+ route_set.add_route('/:controller/:action/:id')
14
14
  route_set.generate({:action => 'thingy'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/sample/thingy'
15
15
  end
16
16
 
17
17
  it "should skip the action if not provided" do
18
- route_set.add_route(':controller/:action/:id')
18
+ route_set.add_route('/:controller/:action/:id')
19
19
  route_set.generate({:controller => 'thingy'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/thingy'
20
20
  end
21
21
 
22
22
  it "should pick the correct param from optional parts" do
23
- route_set.add_route(':controller/:action(.:format)')
23
+ route_set.add_route('/:controller/:action(.:format)')
24
24
  route_set.generate({:action => 'thingy', :format => 'html'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/sample/thingy.html'
25
25
  route_set.generate({:action => 'thingy'}, {:controller => 'sample', :action => 'index', :id => 123}, :generate).should == '/sample/thingy'
26
26
  end
@@ -36,12 +36,12 @@ describe "Usher route recognition" do
36
36
  end
37
37
 
38
38
  it "should recognize a format-style literal" do
39
- target_route = route_set.add_route(':action.html', :controller => 'sample', :action => 'action')
39
+ target_route = route_set.add_route('/:action.html', :controller => 'sample', :action => 'action')
40
40
  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']])
41
41
  end
42
42
 
43
43
  it "should recognize a format-style variable along side another variable" do
44
- target_route = route_set.add_route(':action.:format', :controller => 'sample', :action => 'action')
44
+ target_route = route_set.add_route('/:action.:format', :controller => 'sample', :action => 'action')
45
45
  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'], [:format, 'html']])
46
46
  end
47
47
 
@@ -57,12 +57,21 @@ describe "Usher route recognition" do
57
57
  end
58
58
 
59
59
  it "should correctly fix that tree if conditionals are used later" do
60
- noop_route = route_set.add_route('noop', :controller => 'products', :action => 'noop')
60
+ noop_route = route_set.add_route('/noop', :controller => 'products', :action => 'noop')
61
61
  product_show_route = route_set.add_route('/products/show/:id', :id => /\d+/, :conditions => {:method => 'get'})
62
62
  noop_route.paths.include?(route_set.recognize(build_request({:method => 'get', :path => '/noop', :domain => 'admin.host.com'})).first).should == true
63
63
  product_show_route.paths.include?(route_set.recognize(build_request({:method => 'get', :path => '/products/show/123', :domain => 'admin.host.com'})).first).should == true
64
64
  end
65
65
 
66
+ it "should use conditionals that are boolean" do
67
+ # hijacking user_agent
68
+ insecure_product_show_route = route_set.add_route('/products/show/:id', :id => /\d+/, :conditions => {:user_agent => false, :method => 'get'})
69
+ secure_product_show_route = route_set.add_route('/products/show/:id', :id => /\d+/, :conditions => {:user_agent => true, :method => 'get'})
70
+
71
+ secure_product_show_route.should == route_set.recognize(build_request({:method => 'get', :path => '/products/show/123', :domain => 'admin.host.com', :user_agent => true})).path.route
72
+ insecure_product_show_route.should == route_set.recognize(build_request({:method => 'get', :path => '/products/show/123', :domain => 'admin.host.com', :user_agent => false})).path.route
73
+ end
74
+
66
75
  it "should use a transformer (proc) on incoming variables" do
67
76
  route_set.add_route('/:controller/:action/:id', :transformers => {:id => proc{|v| v.to_i}})
68
77
  route_set.recognize(build_request({:method => 'get', :path => '/products/show/123asd', :domain => 'admin.host.com'})).params.rassoc(123).first.should == :id
data/spec/split_spec.rb CHANGED
@@ -4,53 +4,53 @@ describe "Usher route tokenizing" do
4
4
 
5
5
 
6
6
  it "should split / delimited routes" do
7
- Usher::Splitter.delimiter('/').split('/test/this/split').should == [['test', 'this', 'split']]
7
+ Usher::Splitter.for_delimiters(['/', '.']).split('/test/this/split').should == [[:/, 'test', :/,'this', :/, 'split']]
8
8
  end
9
9
 
10
10
  it "should split on ' ' delimited routes as well" do
11
- Usher::Splitter.delimiter(' ').split('test this split').should == [['test', 'this', 'split']]
11
+ Usher::Splitter.for_delimiters([' ']).split('test this split').should == [['test', :' ', 'this', :' ', 'split']]
12
12
  end
13
13
 
14
14
  it "should split on ' ' delimited routes for more complex routes as well" do
15
- Usher::Splitter.delimiter(' ').split('(test|this) split').should == [['test', 'split'], ['this', 'split']]
15
+ Usher::Splitter.for_delimiters([' ']).split('(test|this) split').should == [['test', :' ', 'split'], ['this', :' ', 'split']]
16
16
  end
17
17
 
18
18
  it "should group optional parts with brackets" do
19
- Usher::Splitter.delimiter('/').split('/test/this(/split)').should == [
20
- ['test', 'this'],
21
- ['test', 'this', 'split']
19
+ Usher::Splitter.for_delimiters(['/', '.']).split('/test/this(/split)').should == [
20
+ [:/, 'test', :/, 'this'],
21
+ [:/, 'test', :/, 'this', :/, 'split']
22
22
  ]
23
23
  end
24
24
 
25
25
  it "should group exclusive optional parts with brackets and pipes" do
26
- Usher::Splitter.delimiter('/').split('/test/this(/split|/split2)').should == [
27
- ['test', 'this', 'split'],
28
- ['test', 'this', 'split2']
26
+ Usher::Splitter.for_delimiters(['/', '.']).split('/test/this(/split|/split2)').should == [
27
+ [:/, 'test', :/, 'this',:/, 'split'],
28
+ [:/, 'test', :/, 'this',:/, 'split2']
29
29
  ]
30
30
  end
31
31
 
32
32
  it "should group exclusive optional-optional parts with brackets and pipes" do
33
- Usher::Splitter.delimiter('/').split('/test/this((/split|/split2))').should == [
34
- ['test', 'this'],
35
- ['test', 'this', 'split'],
36
- ['test', 'this', 'split2']
33
+ Usher::Splitter.for_delimiters(['/', '.']).split('/test/this((/split|/split2))').should == [
34
+ [:/, 'test',:/, 'this'],
35
+ [:/, 'test',:/, 'this', :/, 'split'],
36
+ [:/, 'test',:/, 'this', :/, 'split2']
37
37
  ]
38
38
  end
39
39
 
40
40
  it "should group optional parts with brackets (for non overlapping groups)" do
41
- Usher::Splitter.delimiter('/').split('/test/this(/split)(/split2)') == [
42
- ["test", "this"],
43
- ["test", "this", "split"],
44
- ["test", "this", "split2"],
45
- ["test", "this", "split", "split2"]
41
+ Usher::Splitter.for_delimiters(['/', '.']).split('/test/this(/split)(/split2)') == [
42
+ [:/, "test", :/, "this"],
43
+ [:/, "test", :/, "this", :/, "split"],
44
+ [:/, "test", :/, "this", :/, "split2"],
45
+ [:/, "test", :/, "this", :/, "split", :/, "split2"]
46
46
  ]
47
47
  end
48
48
 
49
49
  it "should group nested-optional parts with brackets" do
50
- Usher::Splitter.delimiter('/').split('/test/this(/split(.:format))') == [
51
- ["test", "this"],
52
- ["test", "this", "split"],
53
- ["test", "this", "split", Usher::Route::Variable.new(:'.:', :format)]
50
+ Usher::Splitter.for_delimiters(['/', '.']).split('/test/this(/split(.:format))') == [
51
+ [:/, "test", :/, "this"],
52
+ [:/, "test", :/, "this", :/, "split"],
53
+ [:/, "test", :/, "this", :/, "split", '.', Usher::Route::Variable.new(:':', :format)]
54
54
  ]
55
55
  end
56
56
 
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.0
4
+ version: 0.3.2
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-03 00:00:00 -07:00
12
+ date: 2009-04-06 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"
23
+ version: 0.0.2
24
24
  version:
25
25
  description: A general purpose routing library
26
26
  email: joshbuddy@gmail.com