http_router 0.8.11 → 0.9.3
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 +32 -3
 - data/js/lib/http_router.coffee +368 -0
 - data/js/lib/http_router.js +668 -0
 - data/js/package.json +10 -0
 - data/js/test/test.coffee +136 -0
 - data/js/test/test.js +229 -0
 - data/lib/http_router/node/arbitrary.rb +1 -1
 - data/lib/http_router/node/free_regex.rb +3 -2
 - data/lib/http_router/node/glob.rb +4 -3
 - data/lib/http_router/node/glob_regex.rb +3 -2
 - data/lib/http_router/node/lookup.rb +2 -3
 - data/lib/http_router/node/path.rb +62 -0
 - data/lib/http_router/node/request.rb +13 -2
 - data/lib/http_router/node/root.rb +29 -2
 - data/lib/http_router/node/spanning_regex.rb +6 -5
 - data/lib/http_router/node.rb +7 -12
 - data/lib/http_router/rack/builder.rb +0 -8
 - data/lib/http_router/regex_route.rb +13 -0
 - data/lib/http_router/route.rb +57 -42
 - data/lib/http_router/util.rb +41 -0
 - data/lib/http_router/version.rb +1 -1
 - data/lib/http_router.rb +17 -22
 - data/test/common/generate.txt +8 -2
 - data/test/common/http_recognize.txt +58 -0
 - data/test/common/recognize.txt +12 -65
 - data/test/generation.rb +5 -102
 - data/test/generic.rb +111 -0
 - data/test/recognition.rb +6 -100
 - data/test/test_misc.rb +26 -2
 - data/test/test_mounting.rb +4 -4
 - data/test/test_recognition.rb +18 -0
 - metadata +94 -34
 - data/lib/http_router/node/destination.rb +0 -45
 - data/lib/http_router/path.rb +0 -58
 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -1,11 +1,33 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # encoding: utf-8
         
     | 
| 
       2 
2 
     | 
    
         
             
            require 'bundler'
         
     | 
| 
       3 
3 
     | 
    
         
             
            Bundler::GemHelper.install_tasks
         
     | 
| 
      
 4 
     | 
    
         
            +
            # Rake::Task['release'].enhance([:test, :release_js]) FIXME, this just doesn't work.
         
     | 
| 
       4 
5 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
      
 6 
     | 
    
         
            +
            task :release_js do
         
     | 
| 
      
 7 
     | 
    
         
            +
              $: << 'lib'
         
     | 
| 
      
 8 
     | 
    
         
            +
              require 'http_router/version'
         
     | 
| 
      
 9 
     | 
    
         
            +
              File.open('js/package.json', 'w') do |f|
         
     | 
| 
      
 10 
     | 
    
         
            +
                f << <<-EOT
         
     | 
| 
      
 11 
     | 
    
         
            +
            {
         
     | 
| 
      
 12 
     | 
    
         
            +
              "name": "http_router",
         
     | 
| 
      
 13 
     | 
    
         
            +
              "description": "URL routing and generation in js",
         
     | 
| 
      
 14 
     | 
    
         
            +
              "author": "Joshua Hull <joshbuddy@gmail.com>",
         
     | 
| 
      
 15 
     | 
    
         
            +
              "version": "#{HttpRouter::VERSION}",
         
     | 
| 
      
 16 
     | 
    
         
            +
              "directories": {
         
     | 
| 
      
 17 
     | 
    
         
            +
                "lib" : "./lib/http_router"
         
     | 
| 
      
 18 
     | 
    
         
            +
              },
         
     | 
| 
      
 19 
     | 
    
         
            +
              "main": "lib/http_router"
         
     | 
| 
      
 20 
     | 
    
         
            +
            }
         
     | 
| 
      
 21 
     | 
    
         
            +
              EOT
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
              sh "cd js && npm publish"
         
     | 
| 
      
 24 
     | 
    
         
            +
              `git commit js/package.json -m'bumped js version'`
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
       7 
26 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
      
 27 
     | 
    
         
            +
            test_tasks = ['test:generation', 'test:recognition', 'test:integration', 'test:examples', 'test:rdoc_examples']
         
     | 
| 
      
 28 
     | 
    
         
            +
            #test_tasks << 'test:js' if `which coffee && which node` && $?.success?
         
     | 
| 
      
 29 
     | 
    
         
            +
            desc "Run all tests"
         
     | 
| 
      
 30 
     | 
    
         
            +
            task :test => test_tasks
         
     | 
| 
       9 
31 
     | 
    
         | 
| 
       10 
32 
     | 
    
         
             
            desc "Clean things"
         
     | 
| 
       11 
33 
     | 
    
         
             
            task :clean do
         
     | 
| 
         @@ -22,6 +44,13 @@ namespace :test do 
     | 
|
| 
       22 
44 
     | 
    
         
             
                Dir['./test/**/test_*.rb'].each { |test| require test }
         
     | 
| 
       23 
45 
     | 
    
         
             
              end
         
     | 
| 
       24 
46 
     | 
    
         | 
| 
      
 47 
     | 
    
         
            +
              desc "Run js tests"
         
     | 
| 
      
 48 
     | 
    
         
            +
              task :js do
         
     | 
| 
      
 49 
     | 
    
         
            +
                sh "coffee -c js/test/test.coffee"
         
     | 
| 
      
 50 
     | 
    
         
            +
                sh "coffee -c js/lib/http_router.coffee"
         
     | 
| 
      
 51 
     | 
    
         
            +
                sh "node js/test/test.js"
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
       25 
54 
     | 
    
         
             
              desc "Run generic recognition tests"
         
     | 
| 
       26 
55 
     | 
    
         
             
              task :recognition do
         
     | 
| 
       27 
56 
     | 
    
         
             
                $: << 'lib'
         
     | 
| 
         @@ -0,0 +1,368 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            root.Sherpa = class Sherpa
         
     | 
| 
      
 2 
     | 
    
         
            +
              constructor: (@callback) ->
         
     | 
| 
      
 3 
     | 
    
         
            +
                @root = new Node()
         
     | 
| 
      
 4 
     | 
    
         
            +
                @routes = {}
         
     | 
| 
      
 5 
     | 
    
         
            +
              match: (httpRequest, httpResponse) ->
         
     | 
| 
      
 6 
     | 
    
         
            +
                request = if (httpRequest.url?) then new Request(httpRequest) else new PathRequest(httpRequest)
         
     | 
| 
      
 7 
     | 
    
         
            +
                @root.match(request)
         
     | 
| 
      
 8 
     | 
    
         
            +
                if request.destinations.length > 0
         
     | 
| 
      
 9 
     | 
    
         
            +
                  new Response(request, httpResponse).invoke()
         
     | 
| 
      
 10 
     | 
    
         
            +
                else if @callback?
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @callback(request.underlyingRequest)
         
     | 
| 
      
 12 
     | 
    
         
            +
              findSubparts: (part) ->
         
     | 
| 
      
 13 
     | 
    
         
            +
                subparts = []
         
     | 
| 
      
 14 
     | 
    
         
            +
                while match = part.match(/\\.|[:*][a-z0-9_]+|[^:*\\]+/)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  part = part.slice(match.index, part.length)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  subparts.push part.slice(0, match[0].length)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  part = part.slice(match[0].length, part.length)
         
     | 
| 
      
 18 
     | 
    
         
            +
                subparts
         
     | 
| 
      
 19 
     | 
    
         
            +
              generatePaths: (path) ->
         
     | 
| 
      
 20 
     | 
    
         
            +
                [paths, chars, startIndex, endIndex] = [[''], path.split(''), 0, 1]
         
     | 
| 
      
 21 
     | 
    
         
            +
                for charIndex in [0...chars.length]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  c = chars[charIndex]
         
     | 
| 
      
 23 
     | 
    
         
            +
                  switch c
         
     | 
| 
      
 24 
     | 
    
         
            +
                    when '\\'
         
     | 
| 
      
 25 
     | 
    
         
            +
                      # do nothing ... 
         
     | 
| 
      
 26 
     | 
    
         
            +
                      charIndex++
         
     | 
| 
      
 27 
     | 
    
         
            +
                      add = if chars[charIndex] == ')' or chars[charIndex] == '('
         
     | 
| 
      
 28 
     | 
    
         
            +
                        chars[charIndex]
         
     | 
| 
      
 29 
     | 
    
         
            +
                      else
         
     | 
| 
      
 30 
     | 
    
         
            +
                        "\\#{chars[charIndex]}"
         
     | 
| 
      
 31 
     | 
    
         
            +
                      paths[pathIndex] += add for pathIndex in [startIndex...endIndex]
         
     | 
| 
      
 32 
     | 
    
         
            +
                    when '('
         
     | 
| 
      
 33 
     | 
    
         
            +
                      # over current working set, double paths
         
     | 
| 
      
 34 
     | 
    
         
            +
                      paths.push(paths[pathIndex]) for pathIndex in [startIndex...endIndex]
         
     | 
| 
      
 35 
     | 
    
         
            +
                      # move working set to newly copied paths
         
     | 
| 
      
 36 
     | 
    
         
            +
                      startIndex = endIndex
         
     | 
| 
      
 37 
     | 
    
         
            +
                      endIndex = paths.length
         
     | 
| 
      
 38 
     | 
    
         
            +
                    when ')'
         
     | 
| 
      
 39 
     | 
    
         
            +
                      startIndex -= endIndex - startIndex
         
     | 
| 
      
 40 
     | 
    
         
            +
                    else
         
     | 
| 
      
 41 
     | 
    
         
            +
                      paths[pathIndex] += c for pathIndex in [startIndex...endIndex]
         
     | 
| 
      
 42 
     | 
    
         
            +
                paths.reverse()
         
     | 
| 
      
 43 
     | 
    
         
            +
                paths
         
     | 
| 
      
 44 
     | 
    
         
            +
              url: (name, params) ->
         
     | 
| 
      
 45 
     | 
    
         
            +
                @routes[name]?.url(params)
         
     | 
| 
      
 46 
     | 
    
         
            +
              addComplexPart: (subparts, compiledPath, matchesWith, variableNames) ->
         
     | 
| 
      
 47 
     | 
    
         
            +
                escapeRegexp = (str) -> str.replace(/([\.*+?^=!:${}()|[\]\/\\])/g, '\\$1')
         
     | 
| 
      
 48 
     | 
    
         
            +
                [capturingIndicies, splittingIndicies, captures, spans] = [[], [], 0, false]
         
     | 
| 
      
 49 
     | 
    
         
            +
                regexSubparts = for part in subparts
         
     | 
| 
      
 50 
     | 
    
         
            +
                  switch part[0]
         
     | 
| 
      
 51 
     | 
    
         
            +
                    when '\\'
         
     | 
| 
      
 52 
     | 
    
         
            +
                      compiledPath.push "'#{part[1]}'"
         
     | 
| 
      
 53 
     | 
    
         
            +
                      escapeRegexp(part[1])
         
     | 
| 
      
 54 
     | 
    
         
            +
                    when ':', '*'
         
     | 
| 
      
 55 
     | 
    
         
            +
                      spans = true if part[0] == '*'
         
     | 
| 
      
 56 
     | 
    
         
            +
                      captures += 1
         
     | 
| 
      
 57 
     | 
    
         
            +
                      name = part.slice(1, part.length)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      variableNames.push(name)
         
     | 
| 
      
 59 
     | 
    
         
            +
                      if part[0] == '*'
         
     | 
| 
      
 60 
     | 
    
         
            +
                        splittingIndicies.push(captures)
         
     | 
| 
      
 61 
     | 
    
         
            +
                        compiledPath.push "params['#{name}'].join('/')"
         
     | 
| 
      
 62 
     | 
    
         
            +
                      else
         
     | 
| 
      
 63 
     | 
    
         
            +
                        capturingIndicies.push(captures)
         
     | 
| 
      
 64 
     | 
    
         
            +
                        compiledPath.push "params['#{name}']"
         
     | 
| 
      
 65 
     | 
    
         
            +
                      if spans
         
     | 
| 
      
 66 
     | 
    
         
            +
                        if matchesWith[name]? then "((?:#{matchesWith[name].source}\\/?)+)" else '(.*?)'
         
     | 
| 
      
 67 
     | 
    
         
            +
                      else
         
     | 
| 
      
 68 
     | 
    
         
            +
                        "(#{(matchesWith[name]?.source || '[^/]*?')})"
         
     | 
| 
      
 69 
     | 
    
         
            +
                    else
         
     | 
| 
      
 70 
     | 
    
         
            +
                      compiledPath.push "'#{part}'"
         
     | 
| 
      
 71 
     | 
    
         
            +
                      escapeRegexp(part)
         
     | 
| 
      
 72 
     | 
    
         
            +
                regexp = new RegExp("#{regexSubparts.join('')}$")
         
     | 
| 
      
 73 
     | 
    
         
            +
                if spans
         
     | 
| 
      
 74 
     | 
    
         
            +
                  new SpanningRegexMatcher(regexp, capturingIndicies, splittingIndicies)
         
     | 
| 
      
 75 
     | 
    
         
            +
                else
         
     | 
| 
      
 76 
     | 
    
         
            +
                  new RegexMatcher(regexp, capturingIndicies, splittingIndicies)
         
     | 
| 
      
 77 
     | 
    
         
            +
              addSimplePart: (subparts, compiledPath, matchesWith, variableNames) ->
         
     | 
| 
      
 78 
     | 
    
         
            +
                part = subparts[0]
         
     | 
| 
      
 79 
     | 
    
         
            +
                switch part[0]
         
     | 
| 
      
 80 
     | 
    
         
            +
                  when ':'
         
     | 
| 
      
 81 
     | 
    
         
            +
                    variableName = part.slice(1, part.length)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    compiledPath.push "params['#{variableName}']"
         
     | 
| 
      
 83 
     | 
    
         
            +
                    variableNames.push(variableName)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    if matchesWith[variableName]? then new SpanningRegexMatcher(matchesWith[variableName], [0], []) else new Variable() 
         
     | 
| 
      
 85 
     | 
    
         
            +
                  when '*'
         
     | 
| 
      
 86 
     | 
    
         
            +
                    compiledPath.push "params['#{variableName}'].join('/')"
         
     | 
| 
      
 87 
     | 
    
         
            +
                    variableName = part.slice(1, part.length)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    variableNames.push(variableName)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    new Glob(matchesWith[variableName])
         
     | 
| 
      
 90 
     | 
    
         
            +
                  else
         
     | 
| 
      
 91 
     | 
    
         
            +
                    compiledPath.push "'#{part}'"
         
     | 
| 
      
 92 
     | 
    
         
            +
                    new Lookup(part)
         
     | 
| 
      
 93 
     | 
    
         
            +
              add: (rawPath, opts) ->
         
     | 
| 
      
 94 
     | 
    
         
            +
                matchesWith = opts?.matchesWith || {}
         
     | 
| 
      
 95 
     | 
    
         
            +
                defaults = opts?.default || {}
         
     | 
| 
      
 96 
     | 
    
         
            +
                routeName = opts?.name
         
     | 
| 
      
 97 
     | 
    
         
            +
                partiallyMatch = false
         
     | 
| 
      
 98 
     | 
    
         
            +
                route = if rawPath.exec?
         
     | 
| 
      
 99 
     | 
    
         
            +
                  new Route([@root.add(new RegexPath(@root, rawPath))])
         
     | 
| 
      
 100 
     | 
    
         
            +
                else
         
     | 
| 
      
 101 
     | 
    
         
            +
                  if rawPath.substring(rawPath.length - 1) == '*'
         
     | 
| 
      
 102 
     | 
    
         
            +
                    rawPath = rawPath.substring(0, rawPath.length - 1)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    partiallyMatch = true
         
     | 
| 
      
 104 
     | 
    
         
            +
                  pathSet = for path in @generatePaths(rawPath)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    node = @root
         
     | 
| 
      
 106 
     | 
    
         
            +
                    variableNames = []
         
     | 
| 
      
 107 
     | 
    
         
            +
                    parts = path.split('/')
         
     | 
| 
      
 108 
     | 
    
         
            +
                    compiledPath = []
         
     | 
| 
      
 109 
     | 
    
         
            +
                    for part in parts
         
     | 
| 
      
 110 
     | 
    
         
            +
                      unless part == ''
         
     | 
| 
      
 111 
     | 
    
         
            +
                        compiledPath.push "'/'"
         
     | 
| 
      
 112 
     | 
    
         
            +
                        subparts = @findSubparts(part)
         
     | 
| 
      
 113 
     | 
    
         
            +
                        nextNodeFn = if subparts.length == 1 then @addSimplePart else @addComplexPart
         
     | 
| 
      
 114 
     | 
    
         
            +
                        node = node.add(nextNodeFn(subparts, compiledPath, matchesWith, variableNames))
         
     | 
| 
      
 115 
     | 
    
         
            +
                    if opts?.conditions?
         
     | 
| 
      
 116 
     | 
    
         
            +
                      node = node.add(new RequestMatcher(opts.conditions))
         
     | 
| 
      
 117 
     | 
    
         
            +
                    path = new Path(node, variableNames)
         
     | 
| 
      
 118 
     | 
    
         
            +
                    path.partial = partiallyMatch
         
     | 
| 
      
 119 
     | 
    
         
            +
                    path.compiled = if compiledPath.length == 0 then "'/'" else compiledPath.join('+')
         
     | 
| 
      
 120 
     | 
    
         
            +
                    path
         
     | 
| 
      
 121 
     | 
    
         
            +
                  new Route(pathSet, matchesWith)
         
     | 
| 
      
 122 
     | 
    
         
            +
                route.default = defaults
         
     | 
| 
      
 123 
     | 
    
         
            +
                route.name = routeName
         
     | 
| 
      
 124 
     | 
    
         
            +
                @routes[routeName] = route if routeName?
         
     | 
| 
      
 125 
     | 
    
         
            +
                route
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
              class Response
         
     | 
| 
      
 128 
     | 
    
         
            +
                constructor: (@request, @httpResponse, @position) ->
         
     | 
| 
      
 129 
     | 
    
         
            +
                  @position ||= 0
         
     | 
| 
      
 130 
     | 
    
         
            +
                next: ->
         
     | 
| 
      
 131 
     | 
    
         
            +
                  if @position == @destinations.length - 1
         
     | 
| 
      
 132 
     | 
    
         
            +
                    false
         
     | 
| 
      
 133 
     | 
    
         
            +
                  else
         
     | 
| 
      
 134 
     | 
    
         
            +
                    new Response(@request, @httpResponse, @position + 1).invoke()
         
     | 
| 
      
 135 
     | 
    
         
            +
                invoke: ->
         
     | 
| 
      
 136 
     | 
    
         
            +
                  req = if typeof(@request.underlyingRequest) == 'string' then {} else @request.underlyingRequest
         
     | 
| 
      
 137 
     | 
    
         
            +
                  req.params = @request.destinations[@position].params
         
     | 
| 
      
 138 
     | 
    
         
            +
                  req.route = @request.destinations[@position].route
         
     | 
| 
      
 139 
     | 
    
         
            +
                  req.pathInfo = @request.destinations[@position].pathInfo
         
     | 
| 
      
 140 
     | 
    
         
            +
                  @request.destinations[@position].route.destination(req, @httpResponse)
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
              class Node
         
     | 
| 
      
 143 
     | 
    
         
            +
                constructor: ->
         
     | 
| 
      
 144 
     | 
    
         
            +
                  @type ||= 'node'
         
     | 
| 
      
 145 
     | 
    
         
            +
                  @matchers = []
         
     | 
| 
      
 146 
     | 
    
         
            +
                add: (n) ->
         
     | 
| 
      
 147 
     | 
    
         
            +
                  @matchers.push(n) if !@matchers[@matchers.length - 1]?.usable(n)
         
     | 
| 
      
 148 
     | 
    
         
            +
                  @matchers[@matchers.length - 1].use(n)
         
     | 
| 
      
 149 
     | 
    
         
            +
                usable: (n) -> n.type == @type
         
     | 
| 
      
 150 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 151 
     | 
    
         
            +
                  m.match(request) for m in @matchers
         
     | 
| 
      
 152 
     | 
    
         
            +
                superMatch: Node::match
         
     | 
| 
      
 153 
     | 
    
         
            +
                use: (n) -> this
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
              class Lookup extends Node
         
     | 
| 
      
 156 
     | 
    
         
            +
                constructor: (part) ->
         
     | 
| 
      
 157 
     | 
    
         
            +
                  @part = part  
         
     | 
| 
      
 158 
     | 
    
         
            +
                  @type = 'lookup'
         
     | 
| 
      
 159 
     | 
    
         
            +
                  @map = {}
         
     | 
| 
      
 160 
     | 
    
         
            +
                  super
         
     | 
| 
      
 161 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 162 
     | 
    
         
            +
                  if @map[request.path[0]]?
         
     | 
| 
      
 163 
     | 
    
         
            +
                    request = request.clone()
         
     | 
| 
      
 164 
     | 
    
         
            +
                    part = request.path.shift()
         
     | 
| 
      
 165 
     | 
    
         
            +
                    @map[part].match(request)
         
     | 
| 
      
 166 
     | 
    
         
            +
                use: (n) ->
         
     | 
| 
      
 167 
     | 
    
         
            +
                  @map[n.part] ||= new Node()
         
     | 
| 
      
 168 
     | 
    
         
            +
                  @map[n.part]
         
     | 
| 
      
 169 
     | 
    
         
            +
              
         
     | 
| 
      
 170 
     | 
    
         
            +
              class Variable extends Node
         
     | 
| 
      
 171 
     | 
    
         
            +
                constructor: ->
         
     | 
| 
      
 172 
     | 
    
         
            +
                  @type ||= 'variable'
         
     | 
| 
      
 173 
     | 
    
         
            +
                  super
         
     | 
| 
      
 174 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 175 
     | 
    
         
            +
                  if request.path.length > 0
         
     | 
| 
      
 176 
     | 
    
         
            +
                    request = request.clone()
         
     | 
| 
      
 177 
     | 
    
         
            +
                    request.variables.push(request.path.shift())
         
     | 
| 
      
 178 
     | 
    
         
            +
                    super(request)
         
     | 
| 
      
 179 
     | 
    
         
            +
                  
         
     | 
| 
      
 180 
     | 
    
         
            +
              class Glob extends Variable
         
     | 
| 
      
 181 
     | 
    
         
            +
                constructor: (@regexp) ->
         
     | 
| 
      
 182 
     | 
    
         
            +
                  @type = 'glob'
         
     | 
| 
      
 183 
     | 
    
         
            +
                  super
         
     | 
| 
      
 184 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 185 
     | 
    
         
            +
                  if request.path.length > 0
         
     | 
| 
      
 186 
     | 
    
         
            +
                    original_request = request
         
     | 
| 
      
 187 
     | 
    
         
            +
                    cloned_path = request.path.slice(0, request.path)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    for i in [1..original_request.path.length]
         
     | 
| 
      
 189 
     | 
    
         
            +
                      request = original_request.clone()
         
     | 
| 
      
 190 
     | 
    
         
            +
                      match = request.path[i - 1].match(@regexp) if @regexp?
         
     | 
| 
      
 191 
     | 
    
         
            +
                      return if @regexp? and (!match? or match[0].length != request.path[i - 1].length)
         
     | 
| 
      
 192 
     | 
    
         
            +
                      request.variables.push(request.path.slice(0, i))
         
     | 
| 
      
 193 
     | 
    
         
            +
                      request.path = request.path.slice(i, request.path.length)
         
     | 
| 
      
 194 
     | 
    
         
            +
                      @superMatch(request)
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
              class RegexMatcher extends Node
         
     | 
| 
      
 197 
     | 
    
         
            +
                constructor: (@regexp, @capturingIndicies, @splittingIndicies) ->
         
     | 
| 
      
 198 
     | 
    
         
            +
                  @type ||= 'regex'
         
     | 
| 
      
 199 
     | 
    
         
            +
                  @varIndicies = []
         
     | 
| 
      
 200 
     | 
    
         
            +
                  @varIndicies[i] = [i, 'split'] for i in @splittingIndicies
         
     | 
| 
      
 201 
     | 
    
         
            +
                  @varIndicies[i] = [i, 'capture'] for i in @capturingIndicies
         
     | 
| 
      
 202 
     | 
    
         
            +
                  @varIndicies.sort (a, b) -> a[0] - b[0]
         
     | 
| 
      
 203 
     | 
    
         
            +
                  super
         
     | 
| 
      
 204 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 205 
     | 
    
         
            +
                  if request.path[0]? and match = request.path[0].match(@regexp)
         
     | 
| 
      
 206 
     | 
    
         
            +
                    return unless match[0].length == request.path[0].length
         
     | 
| 
      
 207 
     | 
    
         
            +
                    request = request.clone()
         
     | 
| 
      
 208 
     | 
    
         
            +
                    @addVariables(request, match)
         
     | 
| 
      
 209 
     | 
    
         
            +
                    request.path.shift()
         
     | 
| 
      
 210 
     | 
    
         
            +
                    super(request)
         
     | 
| 
      
 211 
     | 
    
         
            +
                addVariables: (request, match) ->
         
     | 
| 
      
 212 
     | 
    
         
            +
                  for v in @varIndicies when v?
         
     | 
| 
      
 213 
     | 
    
         
            +
                    idx = v[0]
         
     | 
| 
      
 214 
     | 
    
         
            +
                    type = v[1]
         
     | 
| 
      
 215 
     | 
    
         
            +
                    switch type
         
     | 
| 
      
 216 
     | 
    
         
            +
                      when 'split' then request.variables.push match[idx].split('/')
         
     | 
| 
      
 217 
     | 
    
         
            +
                      when 'capture' then request.variables.push match[idx]
         
     | 
| 
      
 218 
     | 
    
         
            +
                usable: (n) ->
         
     | 
| 
      
 219 
     | 
    
         
            +
                  n.type == @type && n.regexp == @regexp && n.capturingIndicies == @capturingIndicies && n.splittingIndicies == @splittingIndicies
         
     | 
| 
      
 220 
     | 
    
         
            +
                
         
     | 
| 
      
 221 
     | 
    
         
            +
              class SpanningRegexMatcher extends RegexMatcher
         
     | 
| 
      
 222 
     | 
    
         
            +
                constructor: (@regexp, @capturingIndicies, @splittingIndicies) ->
         
     | 
| 
      
 223 
     | 
    
         
            +
                  @type = 'spanning'
         
     | 
| 
      
 224 
     | 
    
         
            +
                  super
         
     | 
| 
      
 225 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 226 
     | 
    
         
            +
                  if request.path.length > 0
         
     | 
| 
      
 227 
     | 
    
         
            +
                    wholePath = request.wholePath()
         
     | 
| 
      
 228 
     | 
    
         
            +
                    if match = wholePath.match(@regexp)
         
     | 
| 
      
 229 
     | 
    
         
            +
                      return unless match.index == 0
         
     | 
| 
      
 230 
     | 
    
         
            +
                      request = request.clone()
         
     | 
| 
      
 231 
     | 
    
         
            +
                      @addVariables(request, match)
         
     | 
| 
      
 232 
     | 
    
         
            +
                      request.path = request.splitPath(wholePath.slice(match.index + match[0].length, wholePath.length))
         
     | 
| 
      
 233 
     | 
    
         
            +
                      @superMatch(request)
         
     | 
| 
      
 234 
     | 
    
         
            +
                  
         
     | 
| 
      
 235 
     | 
    
         
            +
              class RequestMatcher extends Node
         
     | 
| 
      
 236 
     | 
    
         
            +
                constructor: (@conditions) ->
         
     | 
| 
      
 237 
     | 
    
         
            +
                  @type = 'request'
         
     | 
| 
      
 238 
     | 
    
         
            +
                  super
         
     | 
| 
      
 239 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 240 
     | 
    
         
            +
                  conditionCount = 0
         
     | 
| 
      
 241 
     | 
    
         
            +
                  satisfiedConditionCount = 0
         
     | 
| 
      
 242 
     | 
    
         
            +
                  for type, matcher of @conditions
         
     | 
| 
      
 243 
     | 
    
         
            +
                    val = request.underlyingRequest[type]
         
     | 
| 
      
 244 
     | 
    
         
            +
                    conditionCount++
         
     | 
| 
      
 245 
     | 
    
         
            +
                    v = if matcher instanceof Array
         
     | 
| 
      
 246 
     | 
    
         
            +
                      matching = ->
         
     | 
| 
      
 247 
     | 
    
         
            +
                        for cond in matcher
         
     | 
| 
      
 248 
     | 
    
         
            +
                          if cond.exec?
         
     | 
| 
      
 249 
     | 
    
         
            +
                            return true if matcher.exec(val)
         
     | 
| 
      
 250 
     | 
    
         
            +
                          else
         
     | 
| 
      
 251 
     | 
    
         
            +
                            return true if cond == val
         
     | 
| 
      
 252 
     | 
    
         
            +
                        false
         
     | 
| 
      
 253 
     | 
    
         
            +
                      matching()
         
     | 
| 
      
 254 
     | 
    
         
            +
                    else
         
     | 
| 
      
 255 
     | 
    
         
            +
                      if matcher.exec? then matcher.exec(val) else matcher == val
         
     | 
| 
      
 256 
     | 
    
         
            +
                    satisfiedConditionCount++ if v
         
     | 
| 
      
 257 
     | 
    
         
            +
                  if conditionCount == satisfiedConditionCount
         
     | 
| 
      
 258 
     | 
    
         
            +
                    super(request)
         
     | 
| 
      
 259 
     | 
    
         
            +
                usable: (n) ->
         
     | 
| 
      
 260 
     | 
    
         
            +
                  n.type == @type && n.conditions == @conditions
         
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
              class Path extends Node
         
     | 
| 
      
 263 
     | 
    
         
            +
                constructor: (@parent, @variableNames) ->
         
     | 
| 
      
 264 
     | 
    
         
            +
                  @type = 'path'
         
     | 
| 
      
 265 
     | 
    
         
            +
                  @partial = false
         
     | 
| 
      
 266 
     | 
    
         
            +
                addDestination: (request) -> request.destinations.push({route: @route, request: request, params: @constructParams(request)})
         
     | 
| 
      
 267 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 268 
     | 
    
         
            +
                  if @partial or request.path.length == 0
         
     | 
| 
      
 269 
     | 
    
         
            +
                    @addDestination(request)
         
     | 
| 
      
 270 
     | 
    
         
            +
                    if @partial
         
     | 
| 
      
 271 
     | 
    
         
            +
                      request.destinations[request.destinations.length - 1].pathInfo = "/#{request.wholePath()}"
         
     | 
| 
      
 272 
     | 
    
         
            +
                constructParams: (request) ->
         
     | 
| 
      
 273 
     | 
    
         
            +
                  params = {}
         
     | 
| 
      
 274 
     | 
    
         
            +
                  for i in [0...@variableNames.length]
         
     | 
| 
      
 275 
     | 
    
         
            +
                    params[@variableNames[i]] = request.variables[i]
         
     | 
| 
      
 276 
     | 
    
         
            +
                  params
         
     | 
| 
      
 277 
     | 
    
         
            +
                url: (rawParams) ->
         
     | 
| 
      
 278 
     | 
    
         
            +
                  rawParams = {} unless rawParams?
         
     | 
| 
      
 279 
     | 
    
         
            +
                  params = {}
         
     | 
| 
      
 280 
     | 
    
         
            +
                  for key in @variableNames
         
     | 
| 
      
 281 
     | 
    
         
            +
                    params[key] = if @route.default? then rawParams[key] || @route.default[key] else rawParams[key]
         
     | 
| 
      
 282 
     | 
    
         
            +
                    return undefined if !params[key]?
         
     | 
| 
      
 283 
     | 
    
         
            +
                  for name in @variableNames
         
     | 
| 
      
 284 
     | 
    
         
            +
                    if @route.matchesWith[name]?
         
     | 
| 
      
 285 
     | 
    
         
            +
                      match = params[name].match(@route.matchesWith[name])
         
     | 
| 
      
 286 
     | 
    
         
            +
                      return undefined unless match? && match[0].length == params[name].length
         
     | 
| 
      
 287 
     | 
    
         
            +
                  path = if @compiled == '' then '' else eval(@compiled)
         
     | 
| 
      
 288 
     | 
    
         
            +
                  if path?
         
     | 
| 
      
 289 
     | 
    
         
            +
                    delete rawParams[name] for name in @variableNames
         
     | 
| 
      
 290 
     | 
    
         
            +
                    path
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
              class RegexPath extends Path
         
     | 
| 
      
 293 
     | 
    
         
            +
                constructor: (@parent, @regexp) ->
         
     | 
| 
      
 294 
     | 
    
         
            +
                  @type = 'regexp_route'
         
     | 
| 
      
 295 
     | 
    
         
            +
                  super
         
     | 
| 
      
 296 
     | 
    
         
            +
                match: (request) ->
         
     | 
| 
      
 297 
     | 
    
         
            +
                  request.regexpRouteMatch = @regexp.exec(request.decodedPath())
         
     | 
| 
      
 298 
     | 
    
         
            +
                  if request.regexpRouteMatch? && request.regexpRouteMatch[0].length == request.decodedPath().length
         
     | 
| 
      
 299 
     | 
    
         
            +
                    request = request.clone()
         
     | 
| 
      
 300 
     | 
    
         
            +
                    request.path = []
         
     | 
| 
      
 301 
     | 
    
         
            +
                    super(request)
         
     | 
| 
      
 302 
     | 
    
         
            +
                constructParams: (request) -> request.regexpRouteMatch
         
     | 
| 
      
 303 
     | 
    
         
            +
                url: (rawParams) -> throw("This route cannot be generated")
         
     | 
| 
      
 304 
     | 
    
         
            +
             
     | 
| 
      
 305 
     | 
    
         
            +
              class Route
         
     | 
| 
      
 306 
     | 
    
         
            +
                constructor: (@pathSet, @matchesWith) ->
         
     | 
| 
      
 307 
     | 
    
         
            +
                  path.route = this for path in @pathSet
         
     | 
| 
      
 308 
     | 
    
         
            +
                to: (@destination) ->
         
     | 
| 
      
 309 
     | 
    
         
            +
                  path.parent.add(path) for path in @pathSet
         
     | 
| 
      
 310 
     | 
    
         
            +
                generateQuery: (params, base, query) ->
         
     | 
| 
      
 311 
     | 
    
         
            +
                  query = ""
         
     | 
| 
      
 312 
     | 
    
         
            +
                  base ||= ""
         
     | 
| 
      
 313 
     | 
    
         
            +
                  if params?
         
     | 
| 
      
 314 
     | 
    
         
            +
                    if params instanceof Array
         
     | 
| 
      
 315 
     | 
    
         
            +
                      for idx in [0...(params.length)]
         
     | 
| 
      
 316 
     | 
    
         
            +
                        query += @generateQuery(params[idx], "#{base}[]")
         
     | 
| 
      
 317 
     | 
    
         
            +
                    else if params instanceof Object
         
     | 
| 
      
 318 
     | 
    
         
            +
                      for k,v of params
         
     | 
| 
      
 319 
     | 
    
         
            +
                        query += @generateQuery(v, if base == '' then k else "#{base}[#{k}]")
         
     | 
| 
      
 320 
     | 
    
         
            +
                    else
         
     | 
| 
      
 321 
     | 
    
         
            +
                      query += encodeURIComponent(base).replace(/%20/g, '+')
         
     | 
| 
      
 322 
     | 
    
         
            +
                      query += '='
         
     | 
| 
      
 323 
     | 
    
         
            +
                      query += encodeURIComponent(params).replace(/%20/g, '+')
         
     | 
| 
      
 324 
     | 
    
         
            +
                      query += '&'
         
     | 
| 
      
 325 
     | 
    
         
            +
                  query
         
     | 
| 
      
 326 
     | 
    
         
            +
                url: (params) ->
         
     | 
| 
      
 327 
     | 
    
         
            +
                  path = undefined
         
     | 
| 
      
 328 
     | 
    
         
            +
                  for pathObj in @pathSet
         
     | 
| 
      
 329 
     | 
    
         
            +
                    path = pathObj.url(params)
         
     | 
| 
      
 330 
     | 
    
         
            +
                    break if path?
         
     | 
| 
      
 331 
     | 
    
         
            +
                  if path?
         
     | 
| 
      
 332 
     | 
    
         
            +
                    query = @generateQuery(params)
         
     | 
| 
      
 333 
     | 
    
         
            +
                    joiner = if query != '' then '?' else ''
         
     | 
| 
      
 334 
     | 
    
         
            +
                    "#{encodeURI(path)}#{joiner}#{query.substr(0, query.length - 1)}"
         
     | 
| 
      
 335 
     | 
    
         
            +
                  else
         
     | 
| 
      
 336 
     | 
    
         
            +
                    undefined
         
     | 
| 
      
 337 
     | 
    
         
            +
             
     | 
| 
      
 338 
     | 
    
         
            +
              class Request
         
     | 
| 
      
 339 
     | 
    
         
            +
                constructor: (@underlyingRequest, @callback) ->
         
     | 
| 
      
 340 
     | 
    
         
            +
                  @variables = []
         
     | 
| 
      
 341 
     | 
    
         
            +
                  @destinations = []
         
     | 
| 
      
 342 
     | 
    
         
            +
                  if @underlyingRequest?
         
     | 
| 
      
 343 
     | 
    
         
            +
                    @path = @splitPath()
         
     | 
| 
      
 344 
     | 
    
         
            +
                toString: -> "<Request path: /#{@path.join('/') } #{@path.length}>"
         
     | 
| 
      
 345 
     | 
    
         
            +
                wholePath: -> @path.join('/')
         
     | 
| 
      
 346 
     | 
    
         
            +
                decodedPath: (path) ->
         
     | 
| 
      
 347 
     | 
    
         
            +
                  unless path?
         
     | 
| 
      
 348 
     | 
    
         
            +
                    path = require('url').parse(@underlyingRequest.url).pathname
         
     | 
| 
      
 349 
     | 
    
         
            +
                  decodeURI(path)
         
     | 
| 
      
 350 
     | 
    
         
            +
                splitPath: (path) ->
         
     | 
| 
      
 351 
     | 
    
         
            +
                  decodedPath = @decodedPath(path)
         
     | 
| 
      
 352 
     | 
    
         
            +
                  splitPath = if decodedPath == '/' then [] else decodedPath.split('/')
         
     | 
| 
      
 353 
     | 
    
         
            +
                  splitPath.shift() if splitPath[0] == ''
         
     | 
| 
      
 354 
     | 
    
         
            +
                  splitPath
         
     | 
| 
      
 355 
     | 
    
         
            +
                clone: ->
         
     | 
| 
      
 356 
     | 
    
         
            +
                  c = new Request()
         
     | 
| 
      
 357 
     | 
    
         
            +
                  c.path = @path.slice(0, @path.length)
         
     | 
| 
      
 358 
     | 
    
         
            +
                  c.variables = @variables.slice(0, @variables.length)
         
     | 
| 
      
 359 
     | 
    
         
            +
                  c.underlyingRequest = @underlyingRequest
         
     | 
| 
      
 360 
     | 
    
         
            +
                  c.callback = @callback
         
     | 
| 
      
 361 
     | 
    
         
            +
                  c.destinations = @destinations
         
     | 
| 
      
 362 
     | 
    
         
            +
                  c
         
     | 
| 
      
 363 
     | 
    
         
            +
             
     | 
| 
      
 364 
     | 
    
         
            +
              class PathRequest extends Request
         
     | 
| 
      
 365 
     | 
    
         
            +
                decodedPath: (path) ->
         
     | 
| 
      
 366 
     | 
    
         
            +
                  unless path?
         
     | 
| 
      
 367 
     | 
    
         
            +
                    path = @underlyingRequest
         
     | 
| 
      
 368 
     | 
    
         
            +
                  decodeURI(path)
         
     |