andyjeffries-journey 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +6 -0
- data/Gemfile +11 -0
- data/Manifest.txt +49 -0
- data/README.rdoc +48 -0
- data/Rakefile +31 -0
- data/journey.gemspec +42 -0
- data/lib/journey/backwards.rb +5 -0
- data/lib/journey/core-ext/hash.rb +11 -0
- data/lib/journey/formatter.rb +129 -0
- data/lib/journey/gtg/builder.rb +159 -0
- data/lib/journey/gtg/simulator.rb +44 -0
- data/lib/journey/gtg/transition_table.rb +152 -0
- data/lib/journey/nfa/builder.rb +74 -0
- data/lib/journey/nfa/dot.rb +34 -0
- data/lib/journey/nfa/simulator.rb +45 -0
- data/lib/journey/nfa/transition_table.rb +164 -0
- data/lib/journey/nodes/node.rb +104 -0
- data/lib/journey/parser.rb +204 -0
- data/lib/journey/parser.y +47 -0
- data/lib/journey/parser_extras.rb +21 -0
- data/lib/journey/path/pattern.rb +190 -0
- data/lib/journey/route.rb +92 -0
- data/lib/journey/router/strexp.rb +22 -0
- data/lib/journey/router/utils.rb +57 -0
- data/lib/journey/router.rb +138 -0
- data/lib/journey/routes.rb +74 -0
- data/lib/journey/scanner.rb +58 -0
- data/lib/journey/visitors.rb +186 -0
- data/lib/journey/visualizer/d3.min.js +2 -0
- data/lib/journey/visualizer/fsm.css +34 -0
- data/lib/journey/visualizer/fsm.js +134 -0
- data/lib/journey/visualizer/index.html.erb +50 -0
- data/lib/journey/visualizer/reset.css +48 -0
- data/lib/journey.rb +5 -0
- data/test/gtg/test_builder.rb +77 -0
- data/test/gtg/test_transition_table.rb +113 -0
- data/test/helper.rb +4 -0
- data/test/nfa/test_simulator.rb +96 -0
- data/test/nfa/test_transition_table.rb +70 -0
- data/test/nodes/test_symbol.rb +15 -0
- data/test/path/test_pattern.rb +260 -0
- data/test/route/definition/test_parser.rb +108 -0
- data/test/route/definition/test_scanner.rb +52 -0
- data/test/router/test_strexp.rb +30 -0
- data/test/router/test_utils.rb +19 -0
- data/test/test_route.rb +95 -0
- data/test/test_router.rb +464 -0
- data/test/test_routes.rb +38 -0
- metadata +171 -0
    
        data/.autotest
    ADDED
    
    
    
        data/.gemtest
    ADDED
    
    | 
            File without changes
         | 
    
        data/CHANGELOG.rdoc
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    | @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # -*- ruby -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            source :gemcutter
         | 
| 6 | 
            +
             | 
| 7 | 
            +
             | 
| 8 | 
            +
            gem "minitest", "~>2.5", :group => [:development, :test]
         | 
| 9 | 
            +
            gem "hoe", "~>2.10", :group => [:development, :test]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            # vim: syntax=ruby
         | 
    
        data/Manifest.txt
    ADDED
    
    | @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            .autotest
         | 
| 2 | 
            +
            CHANGELOG.rdoc
         | 
| 3 | 
            +
            Gemfile
         | 
| 4 | 
            +
            Manifest.txt
         | 
| 5 | 
            +
            README.rdoc
         | 
| 6 | 
            +
            Rakefile
         | 
| 7 | 
            +
            journey.gemspec
         | 
| 8 | 
            +
            lib/journey.rb
         | 
| 9 | 
            +
            lib/journey/backwards.rb
         | 
| 10 | 
            +
            lib/journey/core-ext/hash.rb
         | 
| 11 | 
            +
            lib/journey/formatter.rb
         | 
| 12 | 
            +
            lib/journey/gtg/builder.rb
         | 
| 13 | 
            +
            lib/journey/gtg/simulator.rb
         | 
| 14 | 
            +
            lib/journey/gtg/transition_table.rb
         | 
| 15 | 
            +
            lib/journey/nfa/builder.rb
         | 
| 16 | 
            +
            lib/journey/nfa/dot.rb
         | 
| 17 | 
            +
            lib/journey/nfa/simulator.rb
         | 
| 18 | 
            +
            lib/journey/nfa/transition_table.rb
         | 
| 19 | 
            +
            lib/journey/nodes/node.rb
         | 
| 20 | 
            +
            lib/journey/parser.rb
         | 
| 21 | 
            +
            lib/journey/parser.y
         | 
| 22 | 
            +
            lib/journey/parser_extras.rb
         | 
| 23 | 
            +
            lib/journey/path/pattern.rb
         | 
| 24 | 
            +
            lib/journey/route.rb
         | 
| 25 | 
            +
            lib/journey/router.rb
         | 
| 26 | 
            +
            lib/journey/router/strexp.rb
         | 
| 27 | 
            +
            lib/journey/router/utils.rb
         | 
| 28 | 
            +
            lib/journey/routes.rb
         | 
| 29 | 
            +
            lib/journey/scanner.rb
         | 
| 30 | 
            +
            lib/journey/visitors.rb
         | 
| 31 | 
            +
            lib/journey/visualizer/d3.min.js
         | 
| 32 | 
            +
            lib/journey/visualizer/fsm.css
         | 
| 33 | 
            +
            lib/journey/visualizer/fsm.js
         | 
| 34 | 
            +
            lib/journey/visualizer/index.html.erb
         | 
| 35 | 
            +
            lib/journey/visualizer/reset.css
         | 
| 36 | 
            +
            test/gtg/test_builder.rb
         | 
| 37 | 
            +
            test/gtg/test_transition_table.rb
         | 
| 38 | 
            +
            test/helper.rb
         | 
| 39 | 
            +
            test/nfa/test_simulator.rb
         | 
| 40 | 
            +
            test/nfa/test_transition_table.rb
         | 
| 41 | 
            +
            test/nodes/test_symbol.rb
         | 
| 42 | 
            +
            test/path/test_pattern.rb
         | 
| 43 | 
            +
            test/route/definition/test_parser.rb
         | 
| 44 | 
            +
            test/route/definition/test_scanner.rb
         | 
| 45 | 
            +
            test/router/test_strexp.rb
         | 
| 46 | 
            +
            test/router/test_utils.rb
         | 
| 47 | 
            +
            test/test_route.rb
         | 
| 48 | 
            +
            test/test_router.rb
         | 
| 49 | 
            +
            test/test_routes.rb
         | 
    
        data/README.rdoc
    ADDED
    
    | @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            = Journey
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * http://github.com/tenderlove/journey
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            == DESCRIPTION:
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Journey is a router.  It routes requests.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            == FEATURES/PROBLEMS:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Designed for rails
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            == SYNOPSIS:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              Too complex right now. :(
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            == REQUIREMENTS:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            * None
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            == INSTALL:
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            * gem install journey
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            == LICENSE:
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            (The MIT License)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            Copyright (c) 2011 Aaron Patterson
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 32 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 33 | 
            +
            'Software'), to deal in the Software without restriction, including
         | 
| 34 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 35 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 36 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 37 | 
            +
            the following conditions:
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 40 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
         | 
| 43 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 44 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
         | 
| 45 | 
            +
            IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
         | 
| 46 | 
            +
            CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
         | 
| 47 | 
            +
            TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
         | 
| 48 | 
            +
            SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            # -*- ruby -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rubygems'
         | 
| 4 | 
            +
            require 'hoe'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Hoe.plugins.delete :rubyforge
         | 
| 7 | 
            +
            Hoe.plugin :minitest
         | 
| 8 | 
            +
            Hoe.plugin :gemspec # `gem install hoe-gemspec`
         | 
| 9 | 
            +
            Hoe.plugin :git     # `gem install hoe-git`
         | 
| 10 | 
            +
            Hoe.plugin :bundler # `gem install hoe-bundler`
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Hoe.spec 'andyjeffries-journey' do
         | 
| 13 | 
            +
              developer('Aaron Patterson', 'aaron@tenderlovemaking.com')
         | 
| 14 | 
            +
              self.readme_file      = 'README.rdoc'
         | 
| 15 | 
            +
              self.history_file     = 'CHANGELOG.rdoc'
         | 
| 16 | 
            +
              self.extra_rdoc_files = FileList['*.rdoc']
         | 
| 17 | 
            +
              self.extra_dev_deps += [
         | 
| 18 | 
            +
                ["racc",            ">= 1.4.6"],
         | 
| 19 | 
            +
                ["json"],
         | 
| 20 | 
            +
              ]
         | 
| 21 | 
            +
            end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            rule '.rb' => '.y' do |t|
         | 
| 24 | 
            +
              sh "racc -l -o #{t.name} #{t.source}"
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            task :compile => "lib/journey/parser.rb"
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            Rake::Task[:test].prerequisites.unshift "lib/journey/parser.rb"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            # vim: syntax=ruby
         | 
    
        data/journey.gemspec
    ADDED
    
    | @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Gem::Specification.new do |s|
         | 
| 4 | 
            +
              s.name = "andyjeffries-journey"
         | 
| 5 | 
            +
              s.version = "1.0.0.20111022124133"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 8 | 
            +
              s.authors = ["Aaron Patterson", "Andy Jeffries"]
         | 
| 9 | 
            +
              s.date = "2011-10-22"
         | 
| 10 | 
            +
              s.description = "Journey is a router.  It routes requests."
         | 
| 11 | 
            +
              s.email = ["aaron@tenderlovemaking.com"]
         | 
| 12 | 
            +
              s.extra_rdoc_files = ["Manifest.txt", "CHANGELOG.rdoc", "README.rdoc"]
         | 
| 13 | 
            +
              s.files = [".autotest", "CHANGELOG.rdoc", "Gemfile", "Manifest.txt", "README.rdoc", "Rakefile", "journey.gemspec", "lib/journey.rb", "lib/journey/backwards.rb", "lib/journey/core-ext/hash.rb", "lib/journey/formatter.rb", "lib/journey/gtg/builder.rb", "lib/journey/gtg/simulator.rb", "lib/journey/gtg/transition_table.rb", "lib/journey/nfa/builder.rb", "lib/journey/nfa/dot.rb", "lib/journey/nfa/simulator.rb", "lib/journey/nfa/transition_table.rb", "lib/journey/nodes/node.rb", "lib/journey/parser.rb", "lib/journey/parser.y", "lib/journey/parser_extras.rb", "lib/journey/path/pattern.rb", "lib/journey/route.rb", "lib/journey/router.rb", "lib/journey/router/strexp.rb", "lib/journey/router/utils.rb", "lib/journey/routes.rb", "lib/journey/scanner.rb", "lib/journey/visitors.rb", "lib/journey/visualizer/d3.min.js", "lib/journey/visualizer/fsm.css", "lib/journey/visualizer/fsm.js", "lib/journey/visualizer/index.html.erb", "lib/journey/visualizer/reset.css", "test/gtg/test_builder.rb", "test/gtg/test_transition_table.rb", "test/helper.rb", "test/nfa/test_simulator.rb", "test/nfa/test_transition_table.rb", "test/nodes/test_symbol.rb", "test/path/test_pattern.rb", "test/route/definition/test_parser.rb", "test/route/definition/test_scanner.rb", "test/router/test_strexp.rb", "test/router/test_utils.rb", "test/test_route.rb", "test/test_router.rb", "test/test_routes.rb", ".gemtest"]
         | 
| 14 | 
            +
              s.homepage = "http://github.com/tenderlove/journey"
         | 
| 15 | 
            +
              s.rdoc_options = ["--main", "README.rdoc"]
         | 
| 16 | 
            +
              s.require_paths = ["lib"]
         | 
| 17 | 
            +
              s.rubyforge_project = "journey"
         | 
| 18 | 
            +
              s.rubygems_version = "1.8.11"
         | 
| 19 | 
            +
              s.summary = "Journey is a router"
         | 
| 20 | 
            +
              s.test_files = ["test/gtg/test_builder.rb", "test/gtg/test_transition_table.rb", "test/nfa/test_simulator.rb", "test/nfa/test_transition_table.rb", "test/nodes/test_symbol.rb", "test/path/test_pattern.rb", "test/route/definition/test_parser.rb", "test/route/definition/test_scanner.rb", "test/router/test_strexp.rb", "test/router/test_utils.rb", "test/test_route.rb", "test/test_router.rb", "test/test_routes.rb"]
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              if s.respond_to? :specification_version then
         | 
| 23 | 
            +
                s.specification_version = 3
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
         | 
| 26 | 
            +
                  s.add_development_dependency(%q<minitest>, ["~> 2.6"])
         | 
| 27 | 
            +
                  s.add_development_dependency(%q<racc>, [">= 1.4.6"])
         | 
| 28 | 
            +
                  s.add_development_dependency(%q<json>, [">= 0"])
         | 
| 29 | 
            +
                  s.add_development_dependency(%q<hoe>, ["~> 2.12"])
         | 
| 30 | 
            +
                else
         | 
| 31 | 
            +
                  s.add_dependency(%q<minitest>, ["~> 2.6"])
         | 
| 32 | 
            +
                  s.add_dependency(%q<racc>, [">= 1.4.6"])
         | 
| 33 | 
            +
                  s.add_dependency(%q<json>, [">= 0"])
         | 
| 34 | 
            +
                  s.add_dependency(%q<hoe>, ["~> 2.12"])
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              else
         | 
| 37 | 
            +
                s.add_dependency(%q<minitest>, ["~> 2.6"])
         | 
| 38 | 
            +
                s.add_dependency(%q<racc>, [">= 1.4.6"])
         | 
| 39 | 
            +
                s.add_dependency(%q<json>, [">= 0"])
         | 
| 40 | 
            +
                s.add_dependency(%q<hoe>, ["~> 2.12"])
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,129 @@ | |
| 1 | 
            +
            module Journey
         | 
| 2 | 
            +
              ###
         | 
| 3 | 
            +
              # The Formatter class is used for formatting URLs.  For example, parameters
         | 
| 4 | 
            +
              # passed to +url_for+ in rails will eventually call Formatter#generate
         | 
| 5 | 
            +
              class Formatter
         | 
| 6 | 
            +
                attr_reader :routes
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize routes
         | 
| 9 | 
            +
                  @routes = routes
         | 
| 10 | 
            +
                  @cache  = nil
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def generate key, name, options, recall = {}, parameterize = nil
         | 
| 14 | 
            +
                  constraints = recall.merge options
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  match_route(name, constraints) do |route|
         | 
| 17 | 
            +
                    data = constraints.dup
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    keys_to_keep = route.parts.reverse.drop_while { |part|
         | 
| 20 | 
            +
                      !options.key?(part) || (options[part] || recall[part]).nil?
         | 
| 21 | 
            +
                    } | route.required_parts
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    (data.keys - keys_to_keep).each do |bad_key|
         | 
| 24 | 
            +
                      data.delete bad_key
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    parameterized_parts = data.dup
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    if parameterize
         | 
| 30 | 
            +
                      parameterized_parts.each do |k,v|
         | 
| 31 | 
            +
                        parameterized_parts[k] = parameterize.call(k, v)
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    parameterized_parts.keep_if { |_,v| v  }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    next unless verify_required_parts!(route, parameterized_parts)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    z = Hash[options.to_a - data.to_a - route.defaults.to_a]
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    return [route.format(parameterized_parts), z]
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  raise Router::RoutingError
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def clear
         | 
| 48 | 
            +
                  @cache = nil
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                private
         | 
| 52 | 
            +
                def named_routes
         | 
| 53 | 
            +
                  routes.named_routes
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def match_route name, options
         | 
| 57 | 
            +
                  if named_routes.key? name
         | 
| 58 | 
            +
                    yield named_routes[name]
         | 
| 59 | 
            +
                  else
         | 
| 60 | 
            +
                    #routes = possibles(@cache, options.to_a)
         | 
| 61 | 
            +
                    routes = non_recursive(cache, options.to_a)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    hash = routes.group_by { |_, r|
         | 
| 64 | 
            +
                      r.score options
         | 
| 65 | 
            +
                    }
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    hash.keys.sort.reverse_each do |score|
         | 
| 68 | 
            +
                      next if score < 0
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                      hash[score].sort_by { |i,_| i }.each do |_,route|
         | 
| 71 | 
            +
                        yield route
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def non_recursive cache, options
         | 
| 78 | 
            +
                  routes = []
         | 
| 79 | 
            +
                  stack  = [cache]
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  while stack.any?
         | 
| 82 | 
            +
                    c = stack.shift
         | 
| 83 | 
            +
                    routes.concat c[:___routes] if c.key? :___routes
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    options.each do |pair|
         | 
| 86 | 
            +
                      stack << c[pair] if c.key? pair
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  routes
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def possibles cache, options, depth = 0
         | 
| 94 | 
            +
                  cache.fetch(:___routes) { [] } + options.find_all { |pair|
         | 
| 95 | 
            +
                    cache.key? pair
         | 
| 96 | 
            +
                  }.map { |pair|
         | 
| 97 | 
            +
                    possibles(cache[pair], options, depth + 1)
         | 
| 98 | 
            +
                  }.flatten(1)
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                def verify_required_parts! route, parts
         | 
| 102 | 
            +
                  tests = route.path.requirements
         | 
| 103 | 
            +
                  route.required_parts.all? { |key|
         | 
| 104 | 
            +
                    if tests.key? key
         | 
| 105 | 
            +
                      /\A#{tests[key]}\Z/ === parts[key]
         | 
| 106 | 
            +
                    else
         | 
| 107 | 
            +
                      true
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                  }
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                def build_cache
         | 
| 113 | 
            +
                  kash = {}
         | 
| 114 | 
            +
                  routes.each_with_index do |route, i|
         | 
| 115 | 
            +
                    money = kash
         | 
| 116 | 
            +
                    route.required_defaults.each do |tuple|
         | 
| 117 | 
            +
                      hash = (money[tuple] ||= {})
         | 
| 118 | 
            +
                      money = hash
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
                    (money[:___routes] ||= []) << [i, route]
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                  kash
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                def cache
         | 
| 126 | 
            +
                  @cache ||= build_cache
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
            end
         | 
| @@ -0,0 +1,159 @@ | |
| 1 | 
            +
            require 'journey/gtg/transition_table'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Journey
         | 
| 4 | 
            +
              module GTG
         | 
| 5 | 
            +
                class Builder
         | 
| 6 | 
            +
                  DUMMY = Nodes::Literal.new Object.new # :nodoc:
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  attr_reader :root, :ast, :endpoints
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize root
         | 
| 11 | 
            +
                    @root         = root
         | 
| 12 | 
            +
                    @ast          = Nodes::Cat.new root, DUMMY
         | 
| 13 | 
            +
                    @followpos    = nil
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def transition_table
         | 
| 17 | 
            +
                    dtrans   = TransitionTable.new
         | 
| 18 | 
            +
                    marked   = {}
         | 
| 19 | 
            +
                    state_id = Hash.new { |h,k| h[k] = h.length }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    start   = firstpos(root)
         | 
| 22 | 
            +
                    dstates = [start]
         | 
| 23 | 
            +
                    until dstates.empty?
         | 
| 24 | 
            +
                      s = dstates.shift
         | 
| 25 | 
            +
                      next if marked[s]
         | 
| 26 | 
            +
                      marked[s] = true # mark s
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      s.group_by { |state| symbol(state) }.each do |sym, ps|
         | 
| 29 | 
            +
                        u = ps.map { |l| followpos(l) }.flatten
         | 
| 30 | 
            +
                        next if u.empty?
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                        if u.uniq == [DUMMY]
         | 
| 33 | 
            +
                          from = state_id[s]
         | 
| 34 | 
            +
                          to   = state_id[Object.new]
         | 
| 35 | 
            +
                          dtrans[from, to] = sym
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                          dtrans.add_accepting to
         | 
| 38 | 
            +
                          ps.each { |state| dtrans.add_memo to, state.memo }
         | 
| 39 | 
            +
                        else
         | 
| 40 | 
            +
                          dtrans[state_id[s], state_id[u]] = sym
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                          if u.include? DUMMY
         | 
| 43 | 
            +
                            to = state_id[u]
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                            accepting = ps.find_all { |l| followpos(l).include? DUMMY }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                            accepting.each { |accepting_state|
         | 
| 48 | 
            +
                              dtrans.add_memo to, accepting_state.memo
         | 
| 49 | 
            +
                            }
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                            dtrans.add_accepting state_id[u]
         | 
| 52 | 
            +
                          end
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                        dstates << u
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    dtrans
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  def nullable? node
         | 
| 63 | 
            +
                    case node
         | 
| 64 | 
            +
                    when Nodes::Group
         | 
| 65 | 
            +
                      true
         | 
| 66 | 
            +
                    when Nodes::Star
         | 
| 67 | 
            +
                      true
         | 
| 68 | 
            +
                    when Nodes::Or
         | 
| 69 | 
            +
                      node.children.any? { |c| nullable?(c) }
         | 
| 70 | 
            +
                    when Nodes::Cat
         | 
| 71 | 
            +
                      nullable?(node.left) && nullable?(node.right)
         | 
| 72 | 
            +
                    when Nodes::Terminal
         | 
| 73 | 
            +
                      !node.left
         | 
| 74 | 
            +
                    when Nodes::Unary
         | 
| 75 | 
            +
                      nullable? node.left
         | 
| 76 | 
            +
                    else
         | 
| 77 | 
            +
                      raise ArgumentError, 'unknown nullable: %s' % node.class.name
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def firstpos node
         | 
| 82 | 
            +
                    case node
         | 
| 83 | 
            +
                    when Nodes::Star
         | 
| 84 | 
            +
                      firstpos(node.left)
         | 
| 85 | 
            +
                    when Nodes::Cat
         | 
| 86 | 
            +
                      if nullable? node.left
         | 
| 87 | 
            +
                        firstpos(node.left) | firstpos(node.right)
         | 
| 88 | 
            +
                      else
         | 
| 89 | 
            +
                        firstpos(node.left)
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                    when Nodes::Or
         | 
| 92 | 
            +
                      node.children.map { |c| firstpos(c) }.flatten.uniq
         | 
| 93 | 
            +
                    when Nodes::Unary
         | 
| 94 | 
            +
                      firstpos(node.left)
         | 
| 95 | 
            +
                    when Nodes::Terminal
         | 
| 96 | 
            +
                      nullable?(node) ? [] : [node]
         | 
| 97 | 
            +
                    else
         | 
| 98 | 
            +
                      raise ArgumentError, 'unknown firstpos: %s' % node.class.name
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  def lastpos node
         | 
| 103 | 
            +
                    case node
         | 
| 104 | 
            +
                    when Nodes::Star
         | 
| 105 | 
            +
                      firstpos(node.left)
         | 
| 106 | 
            +
                    when Nodes::Or
         | 
| 107 | 
            +
                      node.children.map { |c| lastpos(c) }.flatten.uniq
         | 
| 108 | 
            +
                    when Nodes::Cat
         | 
| 109 | 
            +
                      if nullable? node.right
         | 
| 110 | 
            +
                        lastpos(node.left) | lastpos(node.right)
         | 
| 111 | 
            +
                      else
         | 
| 112 | 
            +
                        lastpos(node.right)
         | 
| 113 | 
            +
                      end
         | 
| 114 | 
            +
                    when Nodes::Terminal
         | 
| 115 | 
            +
                      nullable?(node) ? [] : [node]
         | 
| 116 | 
            +
                    when Nodes::Unary
         | 
| 117 | 
            +
                      lastpos(node.left)
         | 
| 118 | 
            +
                    else
         | 
| 119 | 
            +
                      raise ArgumentError, 'unknown lastpos: %s' % node.class.name
         | 
| 120 | 
            +
                    end
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  def followpos node
         | 
| 124 | 
            +
                    followpos_table[node]
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  private
         | 
| 128 | 
            +
                  def followpos_table
         | 
| 129 | 
            +
                    @followpos ||= build_followpos
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  def build_followpos
         | 
| 133 | 
            +
                    table = Hash.new { |h,k| h[k] = [] }
         | 
| 134 | 
            +
                    @ast.each do |n|
         | 
| 135 | 
            +
                      case n
         | 
| 136 | 
            +
                      when Nodes::Cat
         | 
| 137 | 
            +
                        lastpos(n.left).each do |i|
         | 
| 138 | 
            +
                          table[i] += firstpos(n.right)
         | 
| 139 | 
            +
                        end
         | 
| 140 | 
            +
                      when Nodes::Star
         | 
| 141 | 
            +
                        lastpos(n).each do |i|
         | 
| 142 | 
            +
                          table[i] += firstpos(n)
         | 
| 143 | 
            +
                        end
         | 
| 144 | 
            +
                      end
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
                    table
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  def symbol edge
         | 
| 150 | 
            +
                    case edge
         | 
| 151 | 
            +
                    when Journey::Nodes::Symbol
         | 
| 152 | 
            +
                      edge.regexp
         | 
| 153 | 
            +
                    else
         | 
| 154 | 
            +
                      edge.left
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
              end
         | 
| 159 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            require 'strscan'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Journey
         | 
| 4 | 
            +
              module GTG
         | 
| 5 | 
            +
                class MatchData
         | 
| 6 | 
            +
                  attr_reader :memos
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize memos
         | 
| 9 | 
            +
                    @memos = memos
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                class Simulator
         | 
| 14 | 
            +
                  attr_reader :tt
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def initialize transition_table
         | 
| 17 | 
            +
                    @tt = transition_table
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def simulate string
         | 
| 21 | 
            +
                    input = StringScanner.new string
         | 
| 22 | 
            +
                    state = [0]
         | 
| 23 | 
            +
                    until input.eos?
         | 
| 24 | 
            +
                      sym   = input.scan(/[\/\.\?]|[^\/\.\?]+/)
         | 
| 25 | 
            +
                      state = tt.move(state, sym)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    acceptance_states = state.find_all { |s|
         | 
| 29 | 
            +
                      tt.accepting? s
         | 
| 30 | 
            +
                    }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    return if acceptance_states.empty?
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    memos = acceptance_states.map { |x| tt.memo x }.flatten.compact
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    MatchData.new memos
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  alias :=~    :simulate
         | 
| 40 | 
            +
                  alias :match :simulate
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| 44 | 
            +
             |