class Capybara::Selector::RegexpDisassembler

@api private

Public Class Methods

new(regexp) click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 9
def initialize(regexp)
  @regexp = regexp
end

Public Instance Methods

alternated_substrings() click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 13
def alternated_substrings
  @alternated_substrings ||= begin
    or_strings = process(alternation: true)
    remove_or_covered(or_strings)
    or_strings.any?(&:empty?) ? [] : or_strings
  end
end
substrings() click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 21
def substrings
  @substrings ||= begin
    strs = process(alternation: false).first
    remove_and_covered(strs)
  end
end

Private Instance Methods

collapse(strs) click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 84
def collapse(strs)
  strs.map do |substrings|
    substrings.slice_before(&:nil?).map(&:join).reject(&:empty?).uniq
  end
end
combine(strs) click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 68
def combine(strs)
  suffixes = [[]]
  strs.reverse_each do |str|
    if str.is_a? Set
      prefixes = str.each_with_object([]) { |s, memo| memo.concat combine(s) }

      result = []
      prefixes.product(suffixes) { |pair| result << pair.flatten(1) }
      suffixes = result
    else
      suffixes.each { |arr| arr.unshift str }
    end
  end
  suffixes
end
extract_strings(expression, alternation: false) click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 90
def extract_strings(expression, alternation: false)
  Expression.new(expression).extract_strings(alternation)
end
process(alternation:) click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 61
def process(alternation:)
  strs = extract_strings(Regexp::Parser.parse(@regexp), alternation: alternation)
  strs = collapse(combine(strs).map(&:flatten))
  strs.each { |str| str.map!(&:upcase) } if @regexp.casefold?
  strs
end
remove_and_covered(strings) click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 30
def remove_and_covered(strings)
  # delete_if is documented to modify the array after every block iteration - this doesn't appear to be true
  # uniq the strings to prevent identical strings from removing each other
  strings.uniq!

  # If we have "ab" and "abcd" required - only need to check for "abcd"
  strings.delete_if do |sub_string|
    strings.any? do |cover_string|
      next if sub_string.equal? cover_string

      cover_string.include?(sub_string)
    end
  end
end
remove_or_covered(or_series) click to toggle source
# File lib/capybara/selector/regexp_disassembler.rb, line 45
def remove_or_covered(or_series)
  # If we are going to match `("a" and "b") or ("ade" and "bce")` it only makes sense to match ("a" and "b")

  # Ensure minimum sets of strings are being or'd
  or_series.each { |strs| remove_and_covered(strs) }

  # Remove any of the alternated string series that fully contain any other string series
  or_series.delete_if do |and_strs|
    or_series.any? do |and_strs2|
      next if and_strs.equal? and_strs2

      remove_and_covered(and_strs + and_strs2) == and_strs
    end
  end
end