提交 eb493f5a 编写于 作者: F Francesco Rodriguez

update AD::Journey to follow Rails coding conventions

上级 42b555dc
......@@ -80,4 +80,4 @@ rule '.rb' => '.y' do |t|
sh "racc -l -o #{t.name} #{t.source}"
end
task :compile => 'lib/action_dispatch/journey/parser.rb'
task compile: 'lib/action_dispatch/journey/parser.rb'
......@@ -5,17 +5,17 @@ module Journey
class Formatter # :nodoc:
attr_reader :routes
def initialize routes
def initialize(routes)
@routes = routes
@cache = nil
end
def generate type, name, options, recall = {}, parameterize = nil
constraints = recall.merge options
def generate(type, name, options, recall = {}, parameterize = nil)
constraints = recall.merge(options)
missing_keys = []
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts route, options, recall, parameterize
parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
next if !name && route.requirements.empty? && route.parts.empty?
missing_keys = missing_keys(route, parameterized_parts)
......@@ -35,112 +35,110 @@ def clear
end
private
def extract_parameterized_parts route, options, recall, parameterize = nil
constraints = recall.merge options
data = constraints.dup
keys_to_keep = route.parts.reverse.drop_while { |part|
!options.key?(part) || (options[part] || recall[part]).nil?
} | route.required_parts
def extract_parameterized_parts(route, options, recall, parameterize = nil)
constraints = recall.merge(options)
data = constraints.dup
(data.keys - keys_to_keep).each do |bad_key|
data.delete bad_key
end
keys_to_keep = route.parts.reverse.drop_while { |part|
!options.key?(part) || (options[part] || recall[part]).nil?
} | route.required_parts
(data.keys - keys_to_keep).each do |bad_key|
data.delete(bad_key)
end
parameterized_parts = data.dup
parameterized_parts = data.dup
if parameterize
parameterized_parts.each do |k,v|
parameterized_parts[k] = parameterize.call(k, v)
if parameterize
parameterized_parts.each do |k, v|
parameterized_parts[k] = parameterize.call(k, v)
end
end
end
parameterized_parts.keep_if { |_,v| v }
parameterized_parts
end
parameterized_parts.keep_if { |_, v| v }
parameterized_parts
end
def named_routes
routes.named_routes
end
def named_routes
routes.named_routes
end
def match_route name, options
if named_routes.key? name
yield named_routes[name]
else
#routes = possibles(@cache, options.to_a)
routes = non_recursive(cache, options.to_a)
def match_route(name, options)
if named_routes.key?(name)
yield named_routes[name]
else
routes = non_recursive(cache, options.to_a)
hash = routes.group_by { |_, r|
r.score options
}
hash = routes.group_by { |_, r| r.score(options) }
hash.keys.sort.reverse_each do |score|
next if score < 0
hash.keys.sort.reverse_each do |score|
next if score < 0
hash[score].sort_by { |i,_| i }.each do |_,route|
yield route
hash[score].sort_by { |i, _| i }.each do |_, route|
yield route
end
end
end
end
end
def non_recursive cache, options
routes = []
stack = [cache]
def non_recursive(cache, options)
routes = []
stack = [cache]
while stack.any?
c = stack.shift
routes.concat c[:___routes] if c.key? :___routes
while stack.any?
c = stack.shift
routes.concat(c[:___routes]) if c.key?(:___routes)
options.each do |pair|
stack << c[pair] if c.key? pair
options.each do |pair|
stack << c[pair] if c.key?(pair)
end
end
end
routes
end
routes
end
# Returns an array populated with missing keys if any are present.
def missing_keys route, parts
missing_keys = []
tests = route.path.requirements
route.required_parts.each { |key|
if tests.key? key
missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
else
missing_keys << key unless parts[key]
end
}
missing_keys
end
# Returns an array populated with missing keys if any are present.
def missing_keys(route, parts)
missing_keys = []
tests = route.path.requirements
route.required_parts.each { |key|
if tests.key?(key)
missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
else
missing_keys << key unless parts[key]
end
}
missing_keys
end
def possibles cache, options, depth = 0
cache.fetch(:___routes) { [] } + options.find_all { |pair|
cache.key? pair
}.map { |pair|
possibles(cache[pair], options, depth + 1)
}.flatten(1)
end
def possibles(cache, options, depth = 0)
cache.fetch(:___routes) { [] } + options.find_all { |pair|
cache.key?(pair)
}.map { |pair|
possibles(cache[pair], options, depth + 1)
}.flatten(1)
end
# Returns +true+ if no missing keys are present, otherwise +false+.
def verify_required_parts! route, parts
missing_keys(route, parts).empty?
end
# Returns +true+ if no missing keys are present, otherwise +false+.
def verify_required_parts!(route, parts)
missing_keys(route, parts).empty?
end
def build_cache
root = { :___routes => [] }
routes.each_with_index do |route, i|
leaf = route.required_defaults.inject(root) do |h, tuple|
h[tuple] ||= {}
def build_cache
root = { ___routes: [] }
routes.each_with_index do |route, i|
leaf = route.required_defaults.inject(root) do |h, tuple|
h[tuple] ||= {}
end
(leaf[:___routes] ||= []) << [i, route]
end
(leaf[:___routes] ||= []) << [i, route]
root
end
root
end
def cache
@cache ||= build_cache
end
def cache
@cache ||= build_cache
end
end
end
end
......@@ -8,10 +8,10 @@ class Builder # :nodoc:
attr_reader :root, :ast, :endpoints
def initialize root
@root = root
@ast = Nodes::Cat.new root, DUMMY
@followpos = nil
def initialize(root)
@root = root
@ast = Nodes::Cat.new root, DUMMY
@followpos = nil
end
def transition_table
......@@ -35,21 +35,21 @@ def transition_table
to = state_id[Object.new]
dtrans[from, to] = sym
dtrans.add_accepting to
ps.each { |state| dtrans.add_memo to, state.memo }
dtrans.add_accepting(to)
ps.each { |state| dtrans.add_memo(to, state.memo) }
else
dtrans[state_id[s], state_id[u]] = sym
if u.include? DUMMY
if u.include?(DUMMY)
to = state_id[u]
accepting = ps.find_all { |l| followpos(l).include? DUMMY }
accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
accepting.each { |accepting_state|
dtrans.add_memo to, accepting_state.memo
dtrans.add_memo(to, accepting_state.memo)
}
dtrans.add_accepting state_id[u]
dtrans.add_accepting(state_id[u])
end
end
......@@ -60,7 +60,7 @@ def transition_table
dtrans
end
def nullable? node
def nullable?(node)
case node
when Nodes::Group
true
......@@ -73,18 +73,18 @@ def nullable? node
when Nodes::Terminal
!node.left
when Nodes::Unary
nullable? node.left
nullable?(node.left)
else
raise ArgumentError, 'unknown nullable: %s' % node.class.name
end
end
def firstpos node
def firstpos(node)
case node
when Nodes::Star
firstpos(node.left)
when Nodes::Cat
if nullable? node.left
if nullable?(node.left)
firstpos(node.left) | firstpos(node.right)
else
firstpos(node.left)
......@@ -100,14 +100,14 @@ def firstpos node
end
end
def lastpos node
def lastpos(node)
case node
when Nodes::Star
firstpos(node.left)
when Nodes::Or
node.children.map { |c| lastpos(c) }.flatten.uniq
when Nodes::Cat
if nullable? node.right
if nullable?(node.right)
lastpos(node.left) | lastpos(node.right)
else
lastpos(node.right)
......@@ -121,40 +121,41 @@ def lastpos node
end
end
def followpos node
def followpos(node)
followpos_table[node]
end
private
def followpos_table
@followpos ||= build_followpos
end
def build_followpos
table = Hash.new { |h,k| h[k] = [] }
@ast.each do |n|
case n
when Nodes::Cat
lastpos(n.left).each do |i|
table[i] += firstpos(n.right)
end
when Nodes::Star
lastpos(n).each do |i|
table[i] += firstpos(n)
def followpos_table
@followpos ||= build_followpos
end
def build_followpos
table = Hash.new { |h, k| h[k] = [] }
@ast.each do |n|
case n
when Nodes::Cat
lastpos(n.left).each do |i|
table[i] += firstpos(n.right)
end
when Nodes::Star
lastpos(n).each do |i|
table[i] += firstpos(n)
end
end
end
table
end
table
end
def symbol edge
case edge
when Journey::Nodes::Symbol
edge.regexp
else
edge.left
def symbol(edge)
case edge
when Journey::Nodes::Symbol
edge.regexp
else
edge.left
end
end
end
end
end
end
......
......@@ -6,7 +6,7 @@ module GTG # :nodoc:
class MatchData # :nodoc:
attr_reader :memos
def initialize memos
def initialize(memos)
@memos = memos
end
end
......@@ -14,12 +14,12 @@ def initialize memos
class Simulator # :nodoc:
attr_reader :tt
def initialize transition_table
def initialize(transition_table)
@tt = transition_table
end
def simulate string
input = StringScanner.new string
def simulate(string)
input = StringScanner.new(string)
state = [0]
while sym = input.scan(%r([/.?]|[^/.?]+))
state = tt.move(state, sym)
......@@ -31,9 +31,9 @@ def simulate string
return if acceptance_states.empty?
memos = acceptance_states.map { |x| tt.memo x }.flatten.compact
memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
MatchData.new memos
MatchData.new(memos)
end
alias :=~ :simulate
......
......@@ -15,7 +15,7 @@ def initialize
@memos = Hash.new { |h,k| h[k] = [] }
end
def add_accepting state
def add_accepting(state)
@accepting[state] = true
end
......@@ -23,24 +23,24 @@ def accepting_states
@accepting.keys
end
def accepting? state
def accepting?(state)
@accepting[state]
end
def add_memo idx, memo
def add_memo(idx, memo)
@memos[idx] << memo
end
def memo idx
def memo(idx)
@memos[idx]
end
def eclosure t
def eclosure(t)
Array(t)
end
def move t, a
move_string(t, a).concat move_regexp(t, a)
def move(t, a)
move_string(t, a).concat(move_regexp(t, a))
end
def to_json
......@@ -55,15 +55,15 @@ def to_json
end
JSON.dump({
:regexp_states => simple_regexp,
:string_states => @string_states,
:accepting => @accepting
regexp_states: simple_regexp,
string_states: @string_states,
accepting: @accepting
})
end
def to_svg
svg = IO.popen("dot -Tsvg", 'w+') { |f|
f.write to_dot
svg = IO.popen('dot -Tsvg', 'w+') { |f|
f.write(to_dot)
f.close_write
f.readlines
}
......@@ -71,7 +71,7 @@ def to_svg
svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
end
def visualizer paths, title = 'FSM'
def visualizer(paths, title = 'FSM')
viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer'
fsm_js = File.read File.join(viz_dir, 'fsm.js')
fsm_css = File.read File.join(viz_dir, 'fsm.css')
......@@ -110,7 +110,7 @@ def visualizer paths, title = 'FSM'
template.result(binding)
end
def []= from, to, sym
def []=(from, to, sym)
case sym
when String
@string_states[from][sym] = to
......@@ -136,19 +136,20 @@ def transitions
end
private
def move_regexp t, a
return [] if t.empty?
t.map { |s|
@regexp_states[s].map { |re,v| re === a ? v : nil }
}.flatten.compact.uniq
end
def move_regexp(t, a)
return [] if t.empty?
def move_string t, a
return [] if t.empty?
t.map { |s|
@regexp_states[s].map { |re, v| re === a ? v : nil }
}.flatten.compact.uniq
end
t.map { |s| @string_states[s][a] }.compact
end
def move_string(t, a)
return [] if t.empty?
t.map { |s| @string_states[s][a] }.compact
end
end
end
end
......
......@@ -5,23 +5,23 @@ module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:
class Visitor < Visitors::Visitor # :nodoc:
def initialize tt
def initialize(tt)
@tt = tt
@i = -1
end
def visit_CAT node
left = visit node.left
right = visit node.right
def visit_CAT(node)
left = visit(node.left)
right = visit(node.right)
@tt.merge left.last, right.first
@tt.merge(left.last, right.first)
[left.first, right.last]
end
def visit_GROUP node
def visit_GROUP(node)
from = @i += 1
left = visit node.left
left = visit(node.left)
to = @i += 1
@tt.accepting = to
......@@ -33,14 +33,14 @@ def visit_GROUP node
[from, to]
end
def visit_OR node
from = @i += 1
children = node.children.map { |c| visit c }
to = @i += 1
def visit_OR(node)
from = @i += 1
children = node.children.map { |c| visit(c) }
to = @i += 1
children.each do |child|
@tt[from, child.first] = nil
@tt[child.last, to] = nil
@tt[from, child.first] = nil
@tt[child.last, to] = nil
end
@tt.accepting = to
......@@ -48,26 +48,26 @@ def visit_OR node
[from, to]
end
def terminal node
def terminal(node)
from_i = @i += 1 # new state
to_i = @i += 1 # new state
@tt[from_i, to_i] = node
@tt.accepting = to_i
@tt.add_memo to_i, node.memo
@tt.add_memo(to_i, node.memo)
[from_i, to_i]
end
end
class Builder # :nodoc:
def initialize ast
def initialize(ast)
@ast = ast
end
def transition_table
tt = TransitionTable.new
Visitor.new(tt).accept @ast
Visitor.new(tt).accept(@ast)
tt
end
end
......
......@@ -6,7 +6,7 @@ module NFA # :nodoc:
class MatchData # :nodoc:
attr_reader :memos
def initialize memos
def initialize(memos)
@memos = memos
end
end
......@@ -14,29 +14,29 @@ def initialize memos
class Simulator # :nodoc:
attr_reader :tt
def initialize transition_table
def initialize(transition_table)
@tt = transition_table
end
def simulate string
input = StringScanner.new string
state = tt.eclosure 0
def simulate(string)
input = StringScanner.new(string)
state = tt.eclosure(0)
until input.eos?
sym = input.scan(%r([/.?]|[^/.?]+))
# FIXME: tt.eclosure is not needed for the GTG
state = tt.eclosure tt.move(state, sym)
state = tt.eclosure(tt.move(state, sym))
end
acceptance_states = state.find_all { |s|
tt.accepting? tt.eclosure(s).sort.last
tt.accepting?(tt.eclosure(s).sort.last)
}
return if acceptance_states.empty?
memos = acceptance_states.map { |x| tt.memo x }.flatten.compact
memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
MatchData.new memos
MatchData.new(memos)
end
alias :=~ :simulate
......
......@@ -16,7 +16,7 @@ def initialize
@inverted = nil
end
def accepting? state
def accepting?(state)
accepting == state
end
......@@ -24,20 +24,20 @@ def accepting_states
[accepting]
end
def add_memo idx, memo
def add_memo(idx, memo)
@memos[idx] = memo
end
def memo idx
def memo(idx)
@memos[idx]
end
def []= i, f, s
def []=(i, f, s)
@table[f][i] = s
end
def merge left, right
@memos[right] = @memos.delete left
def merge(left, right)
@memos[right] = @memos.delete(left)
@table[right] = @table.delete(left)
end
......@@ -45,11 +45,10 @@ def states
(@table.keys + @table.values.map(&:keys).flatten).uniq
end
###
# Returns a generalized transition graph with reduced states. The states
# Returns a generalized transition graph with reduced states. The states
# are reduced like a DFA, but the table must be simulated like an NFA.
#
# Edges of the GTG are regular expressions
# Edges of the GTG are regular expressions.
def generalized_table
gt = GTG::TransitionTable.new
marked = {}
......@@ -80,28 +79,26 @@ def generalized_table
final_groups.each do |states|
id = state_id[states]
gt.add_accepting id
gt.add_accepting(id)
save = states.find { |s|
@memos.key?(s) && eclosure(s).sort.last == accepting
}
gt.add_memo id, memo(save)
gt.add_memo(id, memo(save))
end
gt
end
###
# Returns set of NFA states to which there is a transition on ast symbol
# +a+ from some state +s+ in +t+.
def following_states t, a
def following_states(t, a)
Array(t).map { |s| inverted[s][a] }.flatten.uniq
end
###
# Returns set of NFA states to which there is a transition on ast symbol
# +a+ from some state +s+ in +t+.
def move t, a
def move(t, a)
Array(t).map { |s|
inverted[s].keys.compact.find_all { |sym|
sym === a
......@@ -113,10 +110,9 @@ def alphabet
inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s }
end
###
# Returns a set of NFA states reachable from some NFA state +s+ in set
# +t+ on nil-transitions alone.
def eclosure t
def eclosure(t)
stack = Array(t)
seen = {}
children = []
......@@ -128,7 +124,7 @@ def eclosure t
seen[s] = true
children << s
stack.concat inverted[s][nil]
stack.concat(inverted[s][nil])
end
children.uniq
......@@ -141,26 +137,27 @@ def transitions
end
private
def inverted
return @inverted if @inverted
@inverted = Hash.new { |h,from|
h[from] = Hash.new { |j,s| j[s] = [] }
}
def inverted
return @inverted if @inverted
@inverted = Hash.new { |h, from|
h[from] = Hash.new { |j, s| j[s] = [] }
}
@table.each { |to, hash|
hash.each { |from, sym|
if sym
sym = Nodes::Symbol === sym ? sym.regexp : sym.left
end
@table.each { |to, hash|
hash.each { |from, sym|
if sym
sym = Nodes::Symbol === sym ? sym.regexp : sym.left
end
@inverted[from][sym] << to
@inverted[from][sym] << to
}
}
}
@inverted
@inverted
end
end
end
end
end
end
......@@ -8,7 +8,7 @@ class Node # :nodoc:
attr_accessor :left, :memo
def initialize left
def initialize(left)
@left = left
@memo = nil
end
......@@ -51,7 +51,7 @@ def type; :LITERAL; end
end
class Dummy < Literal # :nodoc:
def initialize x = Object.new
def initialize(x = Object.new)
super
end
......@@ -71,7 +71,7 @@ class Symbol < Terminal # :nodoc:
alias :symbol :regexp
DEFAULT_EXP = /[^\.\/\?]+/
def initialize left
def initialize(left)
super
@regexp = DEFAULT_EXP
end
......@@ -98,7 +98,7 @@ def type; :STAR; end
class Binary < Node # :nodoc:
attr_accessor :right
def initialize left, right
def initialize(left, right)
super(left)
@right = right
end
......@@ -113,7 +113,7 @@ def type; :CAT; end
class Or < Node # :nodoc:
attr_reader :children
def initialize children
def initialize(children)
@children = children
end
......
......@@ -10,8 +10,8 @@ def initialize
@scanner = Scanner.new
end
def parse string
@scanner.scan_setup string
def parse(string)
@scanner.scan_setup(string)
do_parse
end
......
......@@ -4,18 +4,18 @@ module Path # :nodoc:
class Pattern # :nodoc:
attr_reader :spec, :requirements, :anchored
def initialize strexp
def initialize(strexp)
parser = Journey::Parser.new
@anchored = true
case strexp
when String
@spec = parser.parse strexp
@spec = parser.parse(strexp)
@requirements = {}
@separators = "/.?"
when Router::Strexp
@spec = parser.parse strexp.path
@spec = parser.parse(strexp.path)
@requirements = strexp.requirements
@separators = strexp.separators.join
@anchored = strexp.anchor
......@@ -61,20 +61,20 @@ def optional_names
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
attr_reader :offsets
def initialize matchers
def initialize(matchers)
@matchers = matchers
@capture_count = [0]
end
def visit node
def visit(node)
super
@capture_count
end
def visit_SYMBOL node
def visit_SYMBOL(node)
node = node.to_sym
if @matchers.key? node
if @matchers.key?(node)
re = /#{@matchers[node]}|/
@capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
else
......@@ -84,51 +84,51 @@ def visit_SYMBOL node
end
class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
def initialize separator, matchers
def initialize(separator, matchers)
@separator = separator
@matchers = matchers
@separator_re = "([^#{separator}]+)"
super()
end
def accept node
def accept(node)
%r{\A#{visit node}\Z}
end
def visit_CAT node
def visit_CAT(node)
[visit(node.left), visit(node.right)].join
end
def visit_SYMBOL node
def visit_SYMBOL(node)
node = node.to_sym
return @separator_re unless @matchers.key? node
return @separator_re unless @matchers.key?(node)
re = @matchers[node]
"(#{re})"
end
def visit_GROUP node
def visit_GROUP(node)
"(?:#{visit node.left})?"
end
def visit_LITERAL node
Regexp.escape node.left
def visit_LITERAL(node)
Regexp.escape(node.left)
end
alias :visit_DOT :visit_LITERAL
def visit_SLASH node
def visit_SLASH(node)
node.left
end
def visit_STAR node
def visit_STAR(node)
re = @matchers[node.left.to_sym] || '.+'
"(#{re})"
end
end
class UnanchoredRegexp < AnchoredRegexp # :nodoc:
def accept node
def accept(node)
%r{\A#{visit node}}
end
end
......@@ -136,7 +136,7 @@ def accept node
class MatchData # :nodoc:
attr_reader :names
def initialize names, offsets, match
def initialize(names, offsets, match)
@names = names
@offsets = offsets
@match = match
......@@ -146,7 +146,7 @@ def captures
(length - 1).times.map { |i| self[i + 1] }
end
def [] x
def [](x)
idx = @offsets[x - 1] + x
@match[idx]
end
......@@ -164,9 +164,9 @@ def to_s
end
end
def match other
def match(other)
return unless match = to_regexp.match(other)
MatchData.new names, offsets, match
MatchData.new(names, offsets, match)
end
alias :=~ :match
......@@ -179,16 +179,17 @@ def to_regexp
end
private
def regexp_visitor
@anchored ? AnchoredRegexp : UnanchoredRegexp
end
def offsets
return @offsets if @offsets
def regexp_visitor
@anchored ? AnchoredRegexp : UnanchoredRegexp
end
viz = RegexpOffsets.new @requirements
@offsets = viz.accept spec
end
def offsets
return @offsets if @offsets
viz = RegexpOffsets.new(@requirements)
@offsets = viz.accept(spec)
end
end
end
end
......
......@@ -11,7 +11,7 @@ class Route # :nodoc:
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
def initialize name, app, path, constraints, defaults = {}
def initialize(name, app, path, constraints, defaults = {})
constraints = constraints.dup
@name = name
@app = app
......@@ -52,7 +52,7 @@ def required_keys
path.required_names.map { |x| x.to_sym } + required_defaults.keys
end
def score constraints
def score(constraints)
required_keys = path.required_names
supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
......@@ -67,7 +67,7 @@ def parts
end
alias :segment_keys :parts
def format path_options
def format(path_options)
path_options.delete_if do |key, value|
value.to_s == defaults[key].to_s && !required_parts.include?(key)
end
......@@ -86,7 +86,7 @@ def required_parts
def required_defaults
@required_defaults ||= begin
matches = parts
@defaults.dup.delete_if { |k,_| matches.include? k }
@defaults.dup.delete_if { |k,_| matches.include?(k) }
end
end
end
......
......@@ -22,7 +22,7 @@ class RoutingError < ::StandardError # :nodoc:
class NullReq # :nodoc:
attr_reader :env
def initialize env
def initialize(env)
@env = env
end
......@@ -44,15 +44,15 @@ def [](k); env[k]; end
attr_reader :request_class, :formatter
attr_accessor :routes
def initialize routes, options
def initialize(routes, options)
@options = options
@params_key = options[:parameters_key]
@request_class = options[:request_class] || NullReq
@routes = routes
end
def call env
env['PATH_INFO'] = Utils.normalize_path env['PATH_INFO']
def call(env)
env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
find_routes(env).each do |match, parameters, route|
script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
......@@ -81,7 +81,7 @@ def call env
return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
end
def recognize req
def recognize(req)
find_routes(req.env).each do |match, parameters, route|
unless route.path.anchored
req.env['SCRIPT_NAME'] = match.to_s
......@@ -96,73 +96,73 @@ def visualizer
tt = GTG::Builder.new(ast).transition_table
groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
asts = groups.values.map { |v| v.first }
tt.visualizer asts
tt.visualizer(asts)
end
private
def partitioned_routes
routes.partitioned_routes
end
def partitioned_routes
routes.partitioned_routes
end
def ast
routes.ast
end
def ast
routes.ast
end
def simulator
routes.simulator
end
def simulator
routes.simulator
end
def custom_routes
partitioned_routes.last
end
def custom_routes
partitioned_routes.last
end
def filter_routes path
return [] unless ast
data = simulator.match(path)
data ? data.memos : []
end
def filter_routes(path)
return [] unless ast
data = simulator.match(path)
data ? data.memos : []
end
def find_routes env
req = request_class.new env
def find_routes env
req = request_class.new(env)
routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
r.path.match(req.path_info)
}
routes.concat get_routes_as_head(routes)
routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
r.path.match(req.path_info)
}
routes.concat get_routes_as_head(routes)
routes.sort_by!(&:precedence).select! { |r|
r.constraints.all? { |k,v| v === req.send(k) } &&
r.verb === req.request_method
}
routes.reject! { |r| req.ip && !(r.ip === req.ip) }
routes.sort_by!(&:precedence).select! { |r|
r.constraints.all? { |k, v| v === req.send(k) } &&
r.verb === req.request_method
}
routes.reject! { |r| req.ip && !(r.ip === req.ip) }
routes.map! { |r|
match_data = r.path.match(req.path_info)
match_names = match_data.names.map { |n| n.to_sym }
match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
info = Hash[match_names.zip(match_values).find_all { |_,y| y }]
routes.map! { |r|
match_data = r.path.match(req.path_info)
match_names = match_data.names.map { |n| n.to_sym }
match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
[match_data, r.defaults.merge(info), r]
}
end
[match_data, r.defaults.merge(info), r]
}
end
def get_routes_as_head(routes)
precedence = (routes.map(&:precedence).max || 0) + 1
routes = routes.select { |r|
r.verb === "GET" && !(r.verb === "HEAD")
}.map! { |r|
Route.new(r.name,
r.app,
r.path,
r.conditions.merge(:request_method => "HEAD"),
r.defaults).tap do |route|
route.precedence = r.precedence + precedence
end
}
routes.flatten!
routes
end
def get_routes_as_head(routes)
precedence = (routes.map(&:precedence).max || 0) + 1
routes = routes.select { |r|
r.verb === "GET" && !(r.verb === "HEAD")
}.map! { |r|
Route.new(r.name,
r.app,
r.path,
r.conditions.merge(request_method: "HEAD"),
r.defaults).tap do |route|
route.precedence = r.precedence + precedence
end
}
routes.flatten!
routes
end
end
end
end
......@@ -8,7 +8,7 @@ class << self
attr_reader :path, :requirements, :separators, :anchor
def initialize path, requirements, separators, anchor = true
def initialize(path, requirements, separators, anchor = true)
@path = path
@requirements = requirements
@separators = separators
......
......@@ -50,12 +50,11 @@ def simulator
return @simulator if @simulator
gtg = GTG::Builder.new(ast).transition_table
@simulator = GTG::Simulator.new gtg
@simulator = GTG::Simulator.new(gtg)
end
###
# Add a route to the routing table.
def add_route app, path, conditions, defaults, name = nil
def add_route(app, path, conditions, defaults, name = nil)
route = Route.new(name, app, path, conditions, defaults)
route.precedence = routes.length
......@@ -66,11 +65,12 @@ def add_route app, path, conditions, defaults, name = nil
end
private
def clear_cache!
@ast = nil
@partitioned_routes = nil
@simulator = nil
end
def clear_cache!
@ast = nil
@partitioned_routes = nil
@simulator = nil
end
end
end
end
......@@ -7,8 +7,8 @@ def initialize
@ss = nil
end
def scan_setup str
@ss = StringScanner.new str
def scan_setup(str)
@ss = StringScanner.new(str)
end
def eos?
......@@ -31,30 +31,31 @@ def next_token
end
private
def scan
case
# /
when text = @ss.scan(/\//)
[:SLASH, text]
when text = @ss.scan(/\*\w+/)
[:STAR, text]
when text = @ss.scan(/\(/)
[:LPAREN, text]
when text = @ss.scan(/\)/)
[:RPAREN, text]
when text = @ss.scan(/\|/)
[:OR, text]
when text = @ss.scan(/\./)
[:DOT, text]
when text = @ss.scan(/:\w+/)
[:SYMBOL, text]
when text = @ss.scan(/[\w%\-~]+/)
[:LITERAL, text]
# any char
when text = @ss.scan(/./)
[:LITERAL, text]
def scan
case
# /
when text = @ss.scan(/\//)
[:SLASH, text]
when text = @ss.scan(/\*\w+/)
[:STAR, text]
when text = @ss.scan(/\(/)
[:LPAREN, text]
when text = @ss.scan(/\)/)
[:RPAREN, text]
when text = @ss.scan(/\|/)
[:OR, text]
when text = @ss.scan(/\./)
[:DOT, text]
when text = @ss.scan(/:\w+/)
[:SYMBOL, text]
when text = @ss.scan(/[\w%\-~]+/)
[:LITERAL, text]
# any char
when text = @ss.scan(/./)
[:LITERAL, text]
end
end
end
end
end
end
......@@ -7,69 +7,70 @@ class Visitor # :nodoc:
h[k] = "visit_#{k}"
}
def accept node
visit node
def accept(node)
visit(node)
end
private
def visit node
send DISPATCH_CACHE[node.type], node
end
def binary node
visit node.left
visit node.right
end
def visit_CAT(n); binary(n); end
def visit node
send(DISPATCH_CACHE[node.type], node)
end
def nary node
node.children.each { |c| visit c }
end
def visit_OR(n); nary(n); end
def binary(node)
visit(node.left)
visit(node.right)
end
def visit_CAT(n); binary(n); end
def unary node
visit node.left
end
def visit_GROUP(n); unary(n); end
def visit_STAR(n); unary(n); end
def nary(node)
node.children.each { |c| visit(c) }
end
def visit_OR(n); nary(n); end
def terminal node; end
%w{ LITERAL SYMBOL SLASH DOT }.each do |t|
class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
end
def unary(node)
visit(node.left)
end
def visit_GROUP(n); unary(n); end
def visit_STAR(n); unary(n); end
def terminal(node); end
%w{ LITERAL SYMBOL SLASH DOT }.each do |t|
class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
end
end
# Loop through the requirements AST
class Each < Visitor # :nodoc:
attr_reader :block
def initialize block
def initialize(block)
@block = block
end
def visit node
def visit(node)
super
block.call node
block.call(node)
end
end
class String < Visitor # :nodoc:
private
def binary node
def binary(node)
[visit(node.left), visit(node.right)].join
end
def nary node
node.children.map { |c| visit c }.join '|'
def nary(node)
node.children.map { |c| visit(c) }.join '|'
end
def terminal node
def terminal(node)
node.left
end
def visit_GROUP node
"(#{visit node.left})"
def visit_GROUP(node)
"(#{visit(node.left)})"
end
end
......@@ -77,43 +78,44 @@ def visit_GROUP node
class Formatter < Visitor # :nodoc:
attr_reader :options, :consumed
def initialize options
def initialize(options)
@options = options
@consumed = {}
end
private
def visit_GROUP node
if consumed == options
nil
else
route = visit node.left
route.include?("\0") ? nil : route
def visit_GROUP(node)
if consumed == options
nil
else
route = visit(node.left)
route.include?("\0") ? nil : route
end
end
end
def terminal node
node.left
end
def terminal(node)
node.left
end
def binary node
[visit(node.left), visit(node.right)].join
end
def binary(node)
[visit(node.left), visit(node.right)].join
end
def nary node
node.children.map { |c| visit c }.join
end
def nary(node)
node.children.map { |c| visit(c) }.join
end
def visit_SYMBOL node
key = node.to_sym
def visit_SYMBOL(node)
key = node.to_sym
if value = options[key]
consumed[key] = value
Router::Utils.escape_path(value)
else
"\0"
if value = options[key]
consumed[key] = value
Router::Utils.escape_path(value)
else
"\0"
end
end
end
end
class Dot < Visitor # :nodoc:
......@@ -122,7 +124,7 @@ def initialize
@edges = []
end
def accept node
def accept(node)
super
<<-eodot
digraph parse_tree {
......@@ -136,50 +138,51 @@ def accept node
end
private
def binary node
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
def binary(node)
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
end
super
end
super
end
def nary node
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
def nary(node)
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
end
super
end
super
end
def unary node
@edges << "#{node.object_id} -> #{node.left.object_id};"
super
end
def unary(node)
@edges << "#{node.object_id} -> #{node.left.object_id};"
super
end
def visit_GROUP node
@nodes << "#{node.object_id} [label=\"()\"];"
super
end
def visit_GROUP(node)
@nodes << "#{node.object_id} [label=\"()\"];"
super
end
def visit_CAT node
@nodes << "#{node.object_id} [label=\"\"];"
super
end
def visit_CAT(node)
@nodes << "#{node.object_id} [label=\"\"];"
super
end
def visit_STAR node
@nodes << "#{node.object_id} [label=\"*\"];"
super
end
def visit_STAR(node)
@nodes << "#{node.object_id} [label=\"*\"];"
super
end
def visit_OR node
@nodes << "#{node.object_id} [label=\"|\"];"
super
end
def visit_OR(node)
@nodes << "#{node.object_id} [label=\"|\"];"
super
end
def terminal node
value = node.left
def terminal(node)
value = node.left
@nodes << "#{node.object_id} [label=\"#{value}\"];"
end
@nodes << "#{node.object_id} [label=\"#{value}\"];"
end
end
end
end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册