aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/journey/path
diff options
context:
space:
mode:
authorAndrew White <andyw@pixeltrix.co.uk>2012-12-19 20:54:47 +0000
committerAndrew White <andyw@pixeltrix.co.uk>2012-12-19 22:13:08 +0000
commit56fee39c392788314c44a575b3fd66e16a50c8b5 (patch)
treee12603fff0d1e7c69d021f822b4077a74b91ddc4 /actionpack/lib/action_dispatch/journey/path
parentb225693a0d86f2e33c66049a69e5148e5c93b7cd (diff)
downloadrails-56fee39c392788314c44a575b3fd66e16a50c8b5.tar.gz
rails-56fee39c392788314c44a575b3fd66e16a50c8b5.tar.bz2
rails-56fee39c392788314c44a575b3fd66e16a50c8b5.zip
Integrate Journey into Action Dispatch
Move the Journey code underneath the ActionDispatch namespace so that we don't pollute the global namespace with names that may be used for models. Fixes rails/journey#49.
Diffstat (limited to 'actionpack/lib/action_dispatch/journey/path')
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb195
1 files changed, 195 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..e14168aeb2
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -0,0 +1,195 @@
+module ActionDispatch
+ module Journey
+ module Path
+ class Pattern
+ attr_reader :spec, :requirements, :anchored
+
+ def initialize strexp
+ parser = Journey::Parser.new
+
+ @anchored = true
+
+ case strexp
+ when String
+ @spec = parser.parse strexp
+ @requirements = {}
+ @separators = "/.?"
+ when Router::Strexp
+ @spec = parser.parse strexp.path
+ @requirements = strexp.requirements
+ @separators = strexp.separators.join
+ @anchored = strexp.anchor
+ else
+ raise "wtf bro: #{strexp}"
+ end
+
+ @names = nil
+ @optional_names = nil
+ @required_names = nil
+ @re = nil
+ @offsets = nil
+ 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 { |n| n.name }
+ end
+
+ def required_names
+ @required_names ||= names - optional_names
+ end
+
+ def optional_names
+ @optional_names ||= spec.grep(Nodes::Group).map { |group|
+ group.grep(Nodes::Symbol)
+ }.flatten.map { |n| n.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
+ end
+
+ class UnanchoredRegexp < AnchoredRegexp # :nodoc:
+ def accept node
+ %r{\A#{visit node}}
+ end
+ end
+
+ class MatchData
+ 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