require 'strscan' class Usher class Splitter def self.for_delimiters(delimiters) delimiters_regex = delimiters.collect{|d| Regexp.quote(d)} * '|' SplitterInstance.new( delimiters, Regexp.new('((:|\*)?[0-9A-Za-z\$\-_\+!\*\',]+|' + delimiters_regex + '|\(|\)|\||\{)'), Regexp.new(delimiters_regex + '|[0-9A-Za-z\$\-_\+!\*\',]+') ) end attr_reader :paths class SplitterInstance def initialize(delimiters, split_regex, url_split_regex) @delimiters = delimiters @delimiter_chars = delimiters.collect{|d| d[0]} @split_regex = split_regex @url_split_regex = url_split_regex end def url_split(path) parts = [] ss = StringScanner.new(path) while !ss.eos? if part = ss.scan(@url_split_regex) parts << case part[0] when *@delimiter_chars part.to_sym else part end end end if path && !path.empty? parts end def split(path, requirements = nil, transformers = nil) parts = Group.new(:all, nil) ss = StringScanner.new(path) current_group = parts while !ss.eos? part = ss.scan(@split_regex) case part[0] when ?*, ?: type = part.slice!(0).chr.to_sym current_group << Usher::Route::Variable.new(type, part, requirements && requirements[part.to_sym], transformers && transformers[part.to_sym]) when ?{ pattern = '' count = 1 variable = ss.scan(/:([^,]+),/) until count.zero? regex_part = ss.scan(/\{|\}|[^\{\}]+/) case regex_part[0] when ?{ count += 1 when ?} count -= 1 end pattern << regex_part end pattern.slice!(pattern.size - 1) regex = Regexp.new(pattern) if variable variable_type = variable.slice!(0).chr.to_sym variable_name = variable[0, variable.size - 1].to_sym current_group << Usher::Route::Variable.new(variable_type, variable_name, requirements && requirements[variable_name], transformers && transformers[variable_name], regex) else current_group << regex end when ?( new_group = Group.new(:any, current_group) current_group << new_group current_group = new_group when ?) current_group = current_group.parent.type == :one ? current_group.parent.parent : current_group.parent when ?| unless current_group.parent.type == :one detached_group = current_group.parent.pop new_group = Group.new(:one, detached_group.parent) detached_group.parent = new_group detached_group.type = :all new_group << detached_group new_group.parent << new_group end current_group.parent << Group.new(:all, current_group.parent) current_group = current_group.parent.last when *@delimiter_chars current_group << part.to_sym else current_group << part end end unless !path || path.empty? paths = calc_paths(parts) paths.each do |path| path.each_with_index do |part, index| if part.is_a?(Usher::Route::Variable) case part.type when :* part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Symbol) && !p.is_a?(Usher::Route::Variable)} || nil when :':' part.look_ahead = path[index + 1, path.size].find{|p| p.is_a?(Symbol)} || @delimiters.first.to_sym end end end end paths end private def cartesian_product!(lval, rval) product = [] (lval.size * rval.size).times do |index| val = [] val.push(*lval[index % lval.size]) val.push(*rval[index % rval.size]) product << val end lval.replace(product) end def calc_paths(parts) if parts.is_a?(Group) paths = [[]] case parts.type when :all parts.each do |p| cartesian_product!(paths, calc_paths(p)) end when :any parts.each do |p| cartesian_product!(paths, calc_paths(p)) end paths.unshift([]) when :one cartesian_product!(paths, parts.collect do |p| calc_paths(p) end) end paths.each{|p| p.compact!; p.flatten! } paths else [[parts]] end end end class Group < Array attr_accessor :type attr_accessor :parent def inspect "#{type}->#{super}" end def initialize(type, parent) @type = type @parent = parent end end end end