aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/journey/path/pattern.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/journey/path/pattern.rb')
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb198
1 files changed, 198 insertions, 0 deletions
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
new file mode 100644
index 0000000000..64b48ca45f
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -0,0 +1,198 @@
+require 'action_dispatch/journey/router/strexp'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module Path # :nodoc:
+ class Pattern # :nodoc:
+ attr_reader :spec, :requirements, :anchored
+
+ def self.from_string string
+ new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
+ end
+
+ def initialize(strexp)
+ @spec = strexp.ast
+ @requirements = strexp.requirements
+ @separators = strexp.separators.join
+ @anchored = strexp.anchor
+
+ @names = nil
+ @optional_names = nil
+ @required_names = nil
+ @re = nil
+ @offsets = nil
+ end
+
+ def build_formatter
+ Visitors::FormatBuilder.new.accept(spec)
+ end
+
+ def ast
+ @spec.grep(Nodes::Symbol).each do |node|
+ re = @requirements[node.to_sym]
+ node.regexp = re if re
+ end
+
+ @spec.grep(Nodes::Star).each do |node|
+ node = node.left
+ node.regexp = @requirements[node.to_sym] || /(.+)/
+ end
+
+ @spec
+ end
+
+ def names
+ @names ||= spec.grep(Nodes::Symbol).map(&:name)
+ end
+
+ def required_names
+ @required_names ||= names - optional_names
+ end
+
+ def optional_names
+ @optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
+ group.grep(Nodes::Symbol)
+ }.map(&:name).uniq
+ end
+
+ class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
+ attr_reader :offsets
+
+ def initialize(matchers)
+ @matchers = matchers
+ @capture_count = [0]
+ end
+
+ def visit(node)
+ super
+ @capture_count
+ end
+
+ def visit_SYMBOL(node)
+ node = node.to_sym
+
+ if @matchers.key?(node)
+ re = /#{@matchers[node]}|/
+ @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
+ else
+ @capture_count << (@capture_count.last || 0)
+ end
+ end
+ end
+
+ class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
+ def initialize(separator, matchers)
+ @separator = separator
+ @matchers = matchers
+ @separator_re = "([^#{separator}]+)"
+ super()
+ end
+
+ def accept(node)
+ %r{\A#{visit node}\Z}
+ end
+
+ def visit_CAT(node)
+ [visit(node.left), visit(node.right)].join
+ end
+
+ def visit_SYMBOL(node)
+ node = node.to_sym
+
+ return @separator_re unless @matchers.key?(node)
+
+ re = @matchers[node]
+ "(#{re})"
+ end
+
+ def visit_GROUP(node)
+ "(?:#{visit node.left})?"
+ end
+
+ def visit_LITERAL(node)
+ Regexp.escape(node.left)
+ end
+ alias :visit_DOT :visit_LITERAL
+
+ def visit_SLASH(node)
+ node.left
+ end
+
+ def visit_STAR(node)
+ re = @matchers[node.left.to_sym] || '.+'
+ "(#{re})"
+ end
+
+ def visit_OR(node)
+ children = node.children.map { |n| visit n }
+ "(?:#{children.join(?|)})"
+ end
+ end
+
+ class UnanchoredRegexp < AnchoredRegexp # :nodoc:
+ def accept(node)
+ %r{\A#{visit node}}
+ end
+ end
+
+ class MatchData # :nodoc:
+ attr_reader :names
+
+ def initialize(names, offsets, match)
+ @names = names
+ @offsets = offsets
+ @match = match
+ end
+
+ def captures
+ (length - 1).times.map { |i| self[i + 1] }
+ end
+
+ def [](x)
+ idx = @offsets[x - 1] + x
+ @match[idx]
+ end
+
+ def length
+ @offsets.length
+ end
+
+ def post_match
+ @match.post_match
+ end
+
+ def to_s
+ @match.to_s
+ end
+ end
+
+ def match(other)
+ return unless match = to_regexp.match(other)
+ MatchData.new(names, offsets, match)
+ end
+ alias :=~ :match
+
+ def source
+ to_regexp.source
+ end
+
+ def to_regexp
+ @re ||= regexp_visitor.new(@separators, @requirements).accept spec
+ end
+
+ private
+
+ def regexp_visitor
+ @anchored ? AnchoredRegexp : UnanchoredRegexp
+ end
+
+ def offsets
+ return @offsets if @offsets
+
+ viz = RegexpOffsets.new(@requirements)
+ @offsets = viz.accept(spec)
+ end
+ end
+ end
+ end
+end