usher 0.4.8

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.
Files changed (43) hide show
  1. data/History.txt +3 -0
  2. data/Manifest.txt +35 -0
  3. data/README.rdoc +126 -0
  4. data/Rakefile +72 -0
  5. data/VERSION.yml +4 -0
  6. data/lib/usher/exceptions.rb +5 -0
  7. data/lib/usher/generate.rb +131 -0
  8. data/lib/usher/grapher.rb +65 -0
  9. data/lib/usher/interface/email_interface.rb +27 -0
  10. data/lib/usher/interface/merb_interface.rb +61 -0
  11. data/lib/usher/interface/rack_interface/mapper.rb +0 -0
  12. data/lib/usher/interface/rack_interface/route.rb +9 -0
  13. data/lib/usher/interface/rack_interface.rb +37 -0
  14. data/lib/usher/interface/rails2_2_interface/mapper.rb +44 -0
  15. data/lib/usher/interface/rails2_2_interface.rb +135 -0
  16. data/lib/usher/interface/rails2_3_interface.rb +135 -0
  17. data/lib/usher/interface.rb +27 -0
  18. data/lib/usher/node.rb +138 -0
  19. data/lib/usher/route/path.rb +24 -0
  20. data/lib/usher/route/request_method.rb +22 -0
  21. data/lib/usher/route/variable.rb +37 -0
  22. data/lib/usher/route.rb +58 -0
  23. data/lib/usher/splitter.rb +159 -0
  24. data/lib/usher.rb +184 -0
  25. data/rails/init.rb +8 -0
  26. data/spec/private/email/recognize_spec.rb +38 -0
  27. data/spec/private/generate_spec.rb +141 -0
  28. data/spec/private/grapher_spec.rb +41 -0
  29. data/spec/private/path_spec.rb +68 -0
  30. data/spec/private/rack/dispatch_spec.rb +29 -0
  31. data/spec/private/rails2_2/compat.rb +1 -0
  32. data/spec/private/rails2_2/generate_spec.rb +28 -0
  33. data/spec/private/rails2_2/path_spec.rb +16 -0
  34. data/spec/private/rails2_2/recognize_spec.rb +79 -0
  35. data/spec/private/rails2_3/compat.rb +1 -0
  36. data/spec/private/rails2_3/generate_spec.rb +28 -0
  37. data/spec/private/rails2_3/path_spec.rb +16 -0
  38. data/spec/private/rails2_3/recognize_spec.rb +79 -0
  39. data/spec/private/recognize_spec.rb +178 -0
  40. data/spec/private/request_method_spec.rb +15 -0
  41. data/spec/private/split_spec.rb +76 -0
  42. data/spec/spec.opts +7 -0
  43. metadata +120 -0
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ == 0.0.1
2
+
3
+ Initial release as gem
data/Manifest.txt ADDED
@@ -0,0 +1,35 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/compat.rb
6
+ lib/usher.rb
7
+ lib/usher/exceptions.rb
8
+ lib/usher/grapher.rb
9
+ lib/usher/interface.rb
10
+ lib/usher/interface/merb_interface.rb
11
+ lib/usher/interface/rack_interface.rb
12
+ lib/usher/interface/rack_interface/mapper.rb
13
+ lib/usher/interface/rack_interface/route.rb
14
+ lib/usher/interface/rails2_interface.rb
15
+ lib/usher/interface/rails2_interface/mapper.rb
16
+ lib/usher/node.rb
17
+ lib/usher/node/lookup.rb
18
+ lib/usher/route.rb
19
+ lib/usher/route/http.rb
20
+ lib/usher/route/path.rb
21
+ lib/usher/route/separator.rb
22
+ lib/usher/route/splitter.rb
23
+ lib/usher/route/variable.rb
24
+ rails/init.rb
25
+ spec/generate_spec.rb
26
+ spec/node/lookup_spec.rb
27
+ spec/path_spec.rb
28
+ spec/rack/dispatch_spec.rb
29
+ spec/rails/generate_spec.rb
30
+ spec/rails/path_spec.rb
31
+ spec/rails/recognize_spec.rb
32
+ spec/recognize_spec.rb
33
+ spec/spec.opts
34
+ spec/split_spec.rb
35
+ usher.gemspec
data/README.rdoc ADDED
@@ -0,0 +1,126 @@
1
+ = Usher
2
+
3
+ Tree-based router library. Useful for (specifically) for Rails and Rack, but probably generally useful for anyone interested in doing routing. Based on Ilya Grigorik suggestion, turns out looking up in a hash and following a tree is faster than Krauter's massive regex approach.
4
+
5
+ == Features
6
+
7
+ * Understands single and path-globbing variables
8
+ * Arbitrary HTTP header requirements
9
+ * No optimization phase, so routes are always alterable after the fact
10
+ * Understands Proc and Regex transformations, validations
11
+ * Really, really fast
12
+ * Relatively light and happy code-base, should be easy and fun to alter
13
+ * Interface and implementation are separate, encouraging cross-pollination
14
+
15
+ == Route format
16
+
17
+ From the rdoc:
18
+
19
+ Creates a route from +path+ and +options+
20
+
21
+ === +path+
22
+ A path consists a mix of dynamic and static parts delimited by <tt>/</tt>
23
+
24
+ ==== Dynamic
25
+ Dynamic parts are prefixed with either :, *. :variable matches only one part of the path, whereas *variable can match one or
26
+ more parts.
27
+
28
+ <b>Example:</b>
29
+ <tt>/path/:variable/path</tt> would match
30
+
31
+ * <tt>/path/test/path</tt>
32
+ * <tt>/path/something_else/path</tt>
33
+ * <tt>/path/one_more/path</tt>
34
+
35
+ In the above examples, 'test', 'something_else' and 'one_more' respectively would be bound to the key <tt>:variable</tt>.
36
+ However, <tt>/path/test/one_more/path</tt> would not be matched.
37
+
38
+ <b>Example:</b>
39
+ <tt>/path/*variable/path</tt> would match
40
+
41
+ * <tt>/path/one/two/three/path</tt>
42
+ * <tt>/path/four/five/path</tt>
43
+
44
+ In the above examples, ['one', 'two', 'three'] and ['four', 'five'] respectively would be bound to the key :variable.
45
+
46
+ As well, variables can have a regex matcher.
47
+
48
+ <b>Example:</b>
49
+ <tt>/product/{:id,\d+}</tt> would match
50
+
51
+ * <tt>/product/123</tt>
52
+ * <tt>/product/4521</tt>
53
+
54
+ But not
55
+ * <tt>/product/AE-35</tt>
56
+
57
+ As well, the same logic applies for * variables as well, where only parts matchable by the supplied regex will
58
+ actually be bound to the variable
59
+
60
+ ==== Static
61
+
62
+ Static parts of literal character sequences. For instance, <tt>/path/something.html</tt> would match only the same path.
63
+ As well, static parts can have a regex pattern in them as well, such as <tt>/path/something.{html|xml}</tt> which would match only
64
+ <tt>/path/something.html</tt> and <tt>/path/something.xml</tt>
65
+
66
+ ==== Optional sections
67
+
68
+ Sections of a route can be marked as optional by surrounding it with brackets. For instance, in the above static example, <tt>/path/something(.html)</tt> would match both <tt>/path/something</tt> and <tt>/path/something.html</tt>.
69
+
70
+ ==== One and only one sections
71
+
72
+ Sections of a route can be marked as "one and only one" by surrounding it with brackets and separating parts of the route with pipes.
73
+ For instance, the path, <tt>/path/something(.xml|.html)</tt> would only match <tt>/path/something.xml</tt> and
74
+ <tt>/path/something.html</tt>. Generally its more efficent to use one and only sections over using regex.
75
+
76
+ === +options+
77
+ * +requirements+ - After transformation, tests the condition using ===. If it returns false, it raises an <tt>Usher::ValidationException</tt>
78
+ * +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.
79
+ * Any other key is interpreted as a requirement for the variable of its name.
80
+
81
+ == Rails
82
+
83
+ script/plugin install git://github.com/joshbuddy/usher.git
84
+
85
+ == Rack
86
+
87
+ === config.ru
88
+
89
+ require 'usher'
90
+ app = proc do |env|
91
+ body = "Hi there #{env['usher.params'][:name]}"
92
+ [
93
+ 200, # Status code
94
+ { # Response headers
95
+ 'Content-Type' => 'text/plain',
96
+ 'Content-Length' => body.size.to_s,
97
+ },
98
+ [body] # Response body
99
+ ]
100
+ end
101
+
102
+ routes = Usher::Interface.for(:rack) do
103
+ add('/hello/:name').to(app)
104
+ end
105
+
106
+ run routes
107
+
108
+ ------------
109
+
110
+ >> curl http://127.0.0.1:3000/hello/samueltanders
111
+ << Hi there samueltanders
112
+
113
+ == DONE
114
+
115
+ * add support for () optional parts
116
+ * Add support for arbitrary HTTP header checks
117
+ * Emit exceptions inline with relevant interfaces
118
+ * More RDoc! (optionally cowbell)
119
+
120
+ == TODO
121
+
122
+ * Make it integrate with merb
123
+ * Make it integrate with rails3
124
+ * Create decent DSL for use with rack
125
+
126
+ (Let me show you to your request)
data/Rakefile ADDED
@@ -0,0 +1,72 @@
1
+ require 'lib/usher'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "usher"
7
+ s.description = s.summary = "A general purpose routing library"
8
+ s.email = "joshbuddy@gmail.com"
9
+ s.homepage = "http://github.com/joshbuddy/usher"
10
+ s.authors = ["Joshua Hull"]
11
+ s.files = FileList["[A-Z]*", "{lib,spec,rails}/**/*"]
12
+ s.add_dependency 'fuzzyhash', '>=0.0.3'
13
+ s.rubyforge_project = 'joshbuddy-usher' # This line would be new
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ require 'spec'
20
+ require 'spec/rake/spectask'
21
+ task :spec => 'spec:all'
22
+ namespace(:spec) do
23
+ Spec::Rake::SpecTask.new(:all) do |t|
24
+ t.spec_opts ||= []
25
+ t.spec_opts << "-rubygems"
26
+ t.spec_opts << "--options" << "spec/spec.opts"
27
+ t.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ end
31
+
32
+ desc "Run all examples with RCov"
33
+ Spec::Rake::SpecTask.new('spec_with_rcov') do |t|
34
+ t.spec_files = FileList['spec/**/*.rb']
35
+ t.rcov = true
36
+ t.rcov_opts = ['--exclude', 'spec']
37
+ end
38
+
39
+ require 'rake/rdoctask'
40
+ desc "Generate documentation"
41
+ Rake::RDocTask.new do |rd|
42
+ rd.main = "README.rdoc"
43
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
44
+ rd.rdoc_dir = 'rdoc'
45
+ end
46
+
47
+ # These are new tasks
48
+ begin
49
+ require 'rake/contrib/sshpublisher'
50
+ namespace :rubyforge do
51
+
52
+ desc "Release gem and RDoc documentation to RubyForge"
53
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
54
+
55
+ namespace :release do
56
+ desc "Publish RDoc to RubyForge."
57
+ task :docs => [:rdoc] do
58
+ config = YAML.load(
59
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
60
+ )
61
+
62
+ host = "#{config['username']}@rubyforge.org"
63
+ remote_dir = "/var/www/gforge-projects/joshbuddy-usher/"
64
+ local_dir = 'rdoc'
65
+
66
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
67
+ end
68
+ end
69
+ end
70
+ rescue LoadError
71
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
72
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 4
3
+ :patch: 8
4
+ :major: 0
@@ -0,0 +1,5 @@
1
+ class Usher
2
+ class UnrecognizedException < RuntimeError; end
3
+ class ValidationException < RuntimeError; end
4
+ class MissingParameterException < RuntimeError; end
5
+ end
@@ -0,0 +1,131 @@
1
+ require 'rack'
2
+
3
+ unless Rack::Utils.respond_to?(:uri_escape)
4
+ module Rack
5
+
6
+ module Utils
7
+
8
+ def uri_escape(s)
9
+ s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
10
+ '%'<<$1.unpack('H2'*$1.size).join('%').upcase
11
+ }.tr(' ', '+')
12
+ end
13
+ module_function :uri_escape
14
+
15
+ def uri_unescape(s)
16
+ gsub(/((?:%[0-9a-fA-F]{2})+)/n){
17
+ [$1.delete('%')].pack('H*')
18
+ }
19
+ end
20
+ module_function :uri_unescape
21
+
22
+ end
23
+ end
24
+ end
25
+
26
+ class Usher
27
+ class Generators
28
+
29
+ class URL
30
+
31
+ def initialize(usher)
32
+ @usher = usher
33
+ end
34
+
35
+ def generate_full(routing_lookup, request, params = nil)
36
+ path = path_for_routing_lookup(routing_lookup, params)
37
+ result = generate_start(path, request)
38
+ result << generate_path(path, params)
39
+ end
40
+
41
+ def generate(routing_lookup, params = nil)
42
+ generate_path(path_for_routing_lookup(routing_lookup, params), params)
43
+ end
44
+
45
+ def generate_start(path, request)
46
+ result = (path.route.generate_with && path.route.generate_with.scheme || request.scheme).dup
47
+ result << '://'
48
+ result << (path.route.generate_with && path.route.generate_with.host) ? path.route.generate_with.host : request.host
49
+ port = path.route.generate_with && path.route.generate_with.port || request.port
50
+ if result[4] == ?s
51
+ result << ':' << port.to_s if port != 443
52
+ else
53
+ result << ':' << port.to_s if port != 80
54
+ end
55
+ result
56
+ end
57
+
58
+ def path_for_routing_lookup(routing_lookup, params)
59
+ path = case routing_lookup
60
+ when Symbol
61
+ route = @usher.named_routes[routing_lookup]
62
+ params.is_a?(Hash) ? route.find_matching_path(params) : route.paths.first
63
+ when Route
64
+ params.is_a?(Hash) ? routing_lookup.find_matching_path(params) : routing_lookup.paths.first
65
+ when nil
66
+ params.is_a?(Hash) ? @usher.path_for_options(params) : raise
67
+ when Route::Path
68
+ routing_lookup
69
+ end
70
+ end
71
+
72
+ # Generates a completed URL based on a +route+ or set of optional +params+
73
+ #
74
+ # set = Usher.new
75
+ # route = set.add_named_route(:test_route, '/:controller/:action')
76
+ # set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
77
+ # set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
78
+ # set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
79
+ def generate_path(path, params = nil)
80
+ raise UnrecognizedException.new unless path
81
+
82
+ params = Array(params) if params.is_a?(String)
83
+ if params.is_a?(Array)
84
+ given_size = params.size
85
+ extra_params = params.last.is_a?(Hash) ? params.pop : nil
86
+ params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
87
+ params.merge!(extra_params) if extra_params
88
+ end
89
+
90
+ result = ''
91
+ path.parts.each do |part|
92
+ case part
93
+ when Route::Variable
94
+ value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
95
+ case part.type
96
+ when :*
97
+ value.each_with_index do |current_value, index|
98
+ current_value = current_value.to_s unless current_value.is_a?(String)
99
+ part.valid!(current_value)
100
+ result << current_value
101
+ result << '/' if index != value.size - 1
102
+ end
103
+ when :':'
104
+ value = value.to_s unless value.is_a?(String)
105
+ part.valid!(value)
106
+ result << value
107
+ end
108
+ else
109
+ result << part
110
+ end
111
+ end
112
+ result = Rack::Utils.uri_escape(result)
113
+
114
+ if params && !params.empty?
115
+ has_query = result[??]
116
+ params.each do |k,v|
117
+ case v
118
+ when Array
119
+ v.each do |v_part|
120
+ result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
121
+ end
122
+ else
123
+ result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
124
+ end
125
+ end
126
+ end
127
+ result
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,65 @@
1
+ class Usher
2
+ class Grapher
3
+
4
+ def initialize
5
+ reset!
6
+ end
7
+
8
+ def reset!
9
+ @significant_keys = nil
10
+ @orders = Hash.new{|h,k| h[k] = Hash.new{|h2, k2| h2[k2] = []}}
11
+ @key_count = Hash.new(0)
12
+ @cache = {}
13
+ end
14
+
15
+ def add_route(route)
16
+ route.paths.each do |path|
17
+ unless path.dynamic_keys.size.zero?
18
+ path.dynamic_keys.each do |k|
19
+ @orders[path.dynamic_keys.size][k] << path
20
+ @key_count[k] += 1
21
+ end
22
+
23
+ dynamic_parts_with_defaults = path.dynamic_parts.select{|part| part.default_value }.map{|dp| dp.name}
24
+ dynamic_parts_without_defaults = path.dynamic_parts.select{|part| !part.default_value }.map{|dp| dp.name}
25
+
26
+ (1...(2 ** (dynamic_parts_with_defaults.size))).each do |i|
27
+ current_set = dynamic_parts_without_defaults.dup
28
+ dynamic_parts_with_defaults.each_with_index do |dp, index|
29
+ current_set << dp unless (index & i) == 0
30
+ end
31
+
32
+ current_set.each do |k|
33
+ @orders[current_set.size][k] << path
34
+ @key_count[k] += 1
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def significant_keys
42
+ @significant_keys ||= @key_count.keys.uniq
43
+ end
44
+
45
+ def find_matching_path(params)
46
+ unless params.empty?
47
+ set = params.keys & significant_keys
48
+ if cached = @cache[set]
49
+ return cached
50
+ end
51
+ set.size.downto(1) do |o|
52
+ set.each do |k|
53
+ @orders[o][k].each { |r|
54
+ if r.can_generate_from?(set)
55
+ @cache[set] = r
56
+ return r
57
+ end
58
+ }
59
+ end
60
+ end
61
+ nil
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ class Usher
2
+ module Interface
3
+ class EmailInterface
4
+
5
+ def initialize(&blk)
6
+ @routes = Usher.new(:delimiters => ['@', '-', '.'], :valid_regex => '[\+a-zA-Z0-9]+', :globs_capture_separators => true)
7
+ instance_eval(&blk) if blk
8
+ end
9
+
10
+ def for(path, &block)
11
+ @routes.add_route(path).to(block)
12
+ end
13
+
14
+ def reset!
15
+ @routes.reset!
16
+ end
17
+
18
+ def act(email)
19
+ response = @routes.recognize(email, email)
20
+ if response.path
21
+ response.path.route.destination.call(response.params.inject({}){|h,(k,v)| h[k]=v.to_s; h })
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,61 @@
1
+ require 'merb-core'
2
+ require 'merb-core/dispatch/router/behavior'
3
+
4
+ class Usher
5
+ module Interface
6
+ class MerbInterface
7
+
8
+ # merb does everything with class methods.
9
+
10
+ @root_behavior = ::Merb::Router::Behavior.new.defaults(:action => "index")
11
+
12
+ class << self
13
+ attr_accessor :root_behavior
14
+
15
+ UsherRoutes = Usher.new
16
+
17
+ def prepare(first = [], last = [], &block)
18
+ @routes = []
19
+ root_behavior._with_proxy(&block)
20
+ @routes = first + @routes + last
21
+ compile
22
+ self
23
+ end
24
+
25
+ def compile
26
+ routes.each do |r|
27
+ r.segments
28
+ end
29
+
30
+ #puts r.inspect; UsherRoutes.add_route(r) }
31
+ #routes.each {|r| }
32
+ end
33
+
34
+ def named_routes
35
+ UsherRoutes.named_routes
36
+ end
37
+
38
+ def routes
39
+ UsherRoutes.routes
40
+ end
41
+
42
+ def route_for(request)
43
+ p request
44
+ p UsherRoutes.tree
45
+ UsherRoutes.recognize(request)
46
+ end
47
+
48
+ end
49
+
50
+ #class BootLoader < ::Merb::BootLoader
51
+ #end
52
+
53
+ def load_into_merb!
54
+ ::Merb.send(:remove_const, "Router")
55
+ ::Merb.const_set("Router", Usher::Interface::MerbInterface)
56
+ #::Merb::BootLoader.const_set("Router", Usher::Interface::Merb::BootLoader)
57
+ end
58
+
59
+ end
60
+ end
61
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ class Usher
2
+ module Interface
3
+ class RackInterface
4
+ module Route
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ require 'rack'
2
+
3
+ class Usher
4
+ module Interface
5
+ class RackInterface
6
+
7
+ attr_accessor :routes
8
+
9
+ def initialize(&blk)
10
+ @routes = Usher.new(:request_methods => [:method, :host, :port, :scheme])
11
+ @generator = Usher::Generators::URL.new(@routes)
12
+ instance_eval(&blk) if blk
13
+ end
14
+
15
+ def add(path, options = nil)
16
+ @routes.add_route(path, options)
17
+ end
18
+
19
+ def reset!
20
+ @routes.reset!
21
+ end
22
+
23
+ def call(env)
24
+ response = @routes.recognize(Rack::Request.new(env))
25
+ params = {}
26
+ response.params.each{ |hk| params[hk.first] = hk.last}
27
+ env['usher.params'] = params
28
+ response.path.route.destination.call(env)
29
+ end
30
+
31
+ def generate(route, params = nil, options = nil)
32
+ @generator.generate(route, params, options)
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ class Usher
2
+ module Interface
3
+ class Rails2_2Interface
4
+
5
+ class Mapper #:doc:
6
+ def initialize(set) #:nodoc:
7
+ @set = set
8
+ end
9
+
10
+ def connect(path, options = nil)
11
+ @set.add_route(path, options)
12
+ end
13
+
14
+ def root(options = {})
15
+ if options.is_a?(Symbol)
16
+ if source_route = @set.named_routes[options]
17
+ options = source_route.conditions.blank? ?
18
+ source_route.options.merge({ :conditions => source_route.conditions }) : source_route.options
19
+ end
20
+ end
21
+ named_route(:root, '/', options)
22
+ end
23
+
24
+ def named_route(name, path, options = nil)
25
+ @set.add_named_route(name, path, options)
26
+ end
27
+
28
+ def namespace(name, options = {}, &block)
29
+ if options[:namespace]
30
+ with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
31
+ else
32
+ with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
33
+ end
34
+ end
35
+
36
+ def method_missing(route_name, *args, &proc) #:nodoc:
37
+ super unless args.length >= 1 && proc.nil?
38
+ @set.add_named_route(route_name, *args)
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end