-
10
Dir[File.dirname(__FILE__) + '/regexp-examples/*.rb'].each {|file| require file }
-
-
1
module RegexpExamples
-
1
class BackReferenceReplacer
-
1
def substitute_backreferences(full_examples)
-
full_examples.map do |full_example|
-
215
begin
-
215
while full_example.match(/__(\w+?)__/)
-
32
full_example.sub!(/__(\w+?)__/, find_backref_for(full_example, $1))
-
end
-
213
full_example
-
rescue RegexpExamples::BackrefNotFound
-
# For instance, one "full example" from /(a|(b)) \2/: "a __2__"
-
# should be rejected because the backref (\2) does not exist
-
2
nil
-
end
-
95
end.compact
-
end
-
-
1
private
-
1
def find_backref_for(full_example, group_id)
-
full_example.all_subgroups.detect do |subgroup|
-
80
subgroup.group_id == group_id
-
32
end || raise(RegexpExamples::BackrefNotFound)
-
end
-
-
end
-
-
end
-
1
module RegexpExamples
-
1
class ResultCountLimiters
-
# The maximum variance for any given repeater, to prevent a huge/infinite number of
-
# examples from being listed. For example, if @@max_repeater_variance = 2 then:
-
# .* is equivalent to .{0,2}
-
# .+ is equivalent to .{1,3}
-
# .{2,} is equivalent to .{2,4}
-
# .{,3} is equivalent to .{0,2}
-
# .{3,8} is equivalent to .{3,5}
-
1
MaxRepeaterVarianceDefault = 2
-
-
# Maximum number of characters returned from a char set, to reduce output spam
-
# For example, if @@max_group_results = 5 then:
-
# \d = ["0", "1", "2", "3", "4"]
-
# \w = ["a", "b", "c", "d", "e"]
-
1
MaxGroupResultsDefault = 5
-
-
1
class << self
-
1
attr_reader :max_repeater_variance, :max_group_results
-
1
def configure!(max_repeater_variance, max_group_results)
-
112
@max_repeater_variance = (max_repeater_variance || MaxRepeaterVarianceDefault)
-
112
@max_group_results = (max_group_results || MaxGroupResultsDefault)
-
end
-
end
-
end
-
-
1
def self.MaxRepeaterVariance
-
16
ResultCountLimiters.max_repeater_variance
-
end
-
1
def self.MaxGroupResults
-
369
ResultCountLimiters.max_group_results
-
end
-
-
1
module CharSets
-
1
Lower = Array('a'..'z')
-
1
Upper = Array('A'..'Z')
-
1
Digit = Array('0'..'9')
-
37
Punct = [33..47, 58..64, 91..96, 123..126].map { |r| r.map { |val| val.chr } }.flatten
-
1
Hex = Array('a'..'f') | Array('A'..'F') | Digit
-
1
Whitespace = [' ', "\t", "\n", "\r", "\v", "\f"]
-
1
Any = Lower | Upper | Digit | Punct
-
end
-
-
# Map of special regex characters, to their associated character sets
-
1
BackslashCharMap = {
-
'd' => CharSets::Digit,
-
'D' => CharSets::Lower | CharSets::Upper | CharSets::Punct,
-
'w' => CharSets::Lower | CharSets::Upper | CharSets::Digit | ['_'],
-
32
'W' => CharSets::Punct.reject { |val| val == '_' },
-
's' => CharSets::Whitespace,
-
'S' => CharSets::Any - CharSets::Whitespace,
-
'h' => CharSets::Hex,
-
'H' => CharSets::Any - CharSets::Hex,
-
-
't' => ["\t"], # tab
-
'n' => ["\n"], # new line
-
'r' => ["\r"], # carriage return
-
'f' => ["\f"], # form feed
-
'a' => ["\a"], # alarm
-
'v' => ["\v"], # vertical tab
-
'e' => ["\e"], # escape
-
}
-
end
-
-
1
module RegexpExamples
-
1
class Error < StandardError; end
-
1
class UnsupportedSyntaxError < Error; end
-
1
class IllegalSyntaxError < Error; end
-
1
class BackrefNotFound < Error; end
-
end
-
1
module RegexpExamples
-
# All Group#result methods return an array of GroupResult objects
-
# The key objective here is to keep track of all capture groups, in order
-
# to fill in backreferences
-
1
class GroupResult < String
-
1
attr_reader :group_id, :subgroups
-
1
def initialize(result, group_id = nil, subgroups = [])
-
1491
@group_id = group_id
-
1491
@subgroups = subgroups
-
1491
if result.respond_to?(:group_id)
-
155
@subgroups = result.all_subgroups
-
end
-
1491
super(result)
-
end
-
-
1
def all_subgroups
-
2213
[self, subgroups].flatten.reject { |subgroup| subgroup.group_id.nil? }
-
end
-
end
-
-
1
class SingleCharGroup
-
1
def initialize(char)
-
287
@char = char
-
end
-
1
def result
-
249
[GroupResult.new(@char)]
-
end
-
end
-
-
1
class CharGroup
-
1
def initialize(chars)
-
41
@chars = chars
-
41
if chars[0] == "^"
-
10
@negative = true
-
10
@chars = @chars[1..-1]
-
else
-
31
@negative = false
-
end
-
-
41
init_backslash_chars
-
41
init_ranges
-
end
-
-
1
def init_ranges
-
# save first and last "-" if present
-
41
first = nil
-
41
last = nil
-
41
first = @chars.shift if @chars.first == "-"
-
41
last = @chars.pop if @chars.last == "-"
-
# Replace all instances of e.g. ["a", "-", "z"] with ["a", "b", ..., "z"]
-
41
while i = @chars.index("-")
-
# Prevent infinite loops from expanding [",", "-", "."] to itself
-
# (Since ",".ord = 44, "-".ord = 45, ".".ord = 46)
-
25
if (@chars[i-1] == ',' && @chars[i+1] == '.')
-
14
first = '-'
-
14
@chars.delete_at(i)
-
else
-
11
@chars[i-1..i+1] = (@chars[i-1]..@chars[i+1]).to_a
-
end
-
end
-
# restore them back
-
41
@chars.unshift(first) if first
-
41
@chars.push(last) if last
-
end
-
-
1
def init_backslash_chars
-
41
@chars.each_with_index do |char, i|
-
1348
if char == "\\"
-
30
if BackslashCharMap.keys.include?(@chars[i+1])
-
20
@chars[i..i+1] = move_backslash_to_front( BackslashCharMap[@chars[i+1]] )
-
elsif @chars[i+1] == 'b'
-
1
@chars[i..i+1] = "\b"
-
elsif @chars[i+1] == "\\"
-
2
@chars.delete_at(i+1)
-
else
-
7
@chars.delete_at(i)
-
end
-
end
-
end
-
end
-
-
1
def result
-
41
(@negative ? (CharSets::Any - @chars) : @chars).map do |result|
-
602
GroupResult.new(result)
-
end
-
end
-
-
1
private
-
1
def move_backslash_to_front(chars)
-
534
if index = chars.index { |char| char == '\\' }
-
9
chars.unshift chars.delete_at(index)
-
end
-
20
chars
-
end
-
end
-
-
1
class DotGroup
-
1
def result
-
1
CharSets::Any.map do |result|
-
94
GroupResult.new(result)
-
end
-
end
-
end
-
-
1
class MultiGroup
-
1
attr_reader :group_id
-
1
def initialize(groups, group_id)
-
45
@groups = groups
-
45
@group_id = group_id
-
end
-
-
# Generates the result of each contained group
-
# and adds the filled group of each result to
-
# itself
-
1
def result
-
194
strings = @groups.map {|repeater| repeater.result}
-
45
RegexpExamples.permutations_of_strings(strings).map do |result|
-
119
GroupResult.new(result, group_id)
-
end
-
end
-
end
-
-
1
class MultiGroupEnd
-
end
-
-
1
class OrGroup
-
1
def initialize(left_repeaters, right_repeaters)
-
11
@left_repeaters = left_repeaters
-
11
@right_repeaters = right_repeaters
-
end
-
-
-
1
def result
-
11
left_result = RegexpExamples.map_results(@left_repeaters)
-
11
right_result = RegexpExamples.map_results(@right_repeaters)
-
11
left_result.concat(right_result).flatten.uniq.map do |result|
-
36
GroupResult.new(result)
-
end
-
end
-
end
-
-
1
class BackReferenceGroup
-
1
attr_reader :id
-
1
def initialize(id)
-
22
@id = id
-
end
-
-
1
def result
-
22
[ GroupResult.new("__#{@id}__") ]
-
end
-
end
-
-
end
-
1
module RegexpExamples
-
# Given an array of arrays of strings,
-
# returns all possible perutations,
-
# for strings created by joining one
-
# element from each array
-
#
-
# For example:
-
# permutations_of_strings [ ['a'], ['b'], ['c', 'd', 'e'] ] #=> ['abc', 'abd', 'abe']
-
# permutations_of_strings [ ['a', 'b'], ['c', 'd'] ] #=> [ 'ac', 'ad', 'bc', 'bd' ]
-
1
def self.permutations_of_strings(arrays_of_strings)
-
813
first = arrays_of_strings.shift
-
813
return first if arrays_of_strings.empty?
-
256
first.product( permutations_of_strings(arrays_of_strings) ).map do |result|
-
350
join_preserving_capture_groups(result)
-
end
-
end
-
-
1
def self.join_preserving_capture_groups(result)
-
350
result.flatten!
-
350
subgroups = result
-
.map(&:all_subgroups)
-
.flatten
-
350
GroupResult.new(result.join, nil, subgroups)
-
end
-
-
1
def self.map_results(repeaters)
-
repeaters
-
220
.map {|repeater| repeater.result}
-
117
.instance_eval do |partial_results|
-
117
RegexpExamples.permutations_of_strings(partial_results)
-
end
-
end
-
end
-
-
1
module RegexpExamples
-
1
class Parser
-
1
attr_reader :regexp_string
-
1
def initialize(regexp_string, options={})
-
112
@regexp_string = regexp_string
-
112
@num_groups = 0
-
112
@current_position = 0
-
112
RegexpExamples::ResultCountLimiters.configure!(
-
options[:max_repeater_variance],
-
options[:max_group_results]
-
)
-
end
-
-
1
def parse
-
169
repeaters = []
-
169
while @current_position < regexp_string.length
-
473
group = parse_group(repeaters)
-
455
break if group.is_a? MultiGroupEnd
-
410
if group.is_a? OrGroup
-
11
return [OneTimeRepeater.new(group)]
-
end
-
399
@current_position += 1
-
399
repeaters << parse_repeater(group)
-
end
-
140
repeaters
-
end
-
-
1
private
-
-
1
def parse_group(repeaters)
-
473
char = regexp_string[@current_position]
-
473
case char
-
when '('
-
50
group = parse_multi_group
-
when ')'
-
45
group = parse_multi_end_group
-
when '['
-
27
group = parse_char_group
-
when '.'
-
4
group = parse_dot_group
-
when '|'
-
11
group = parse_or_group(repeaters)
-
when '\\'
-
66
group = parse_after_backslash_group
-
when '^', 'A'
-
2
if @current_position == 0
-
1
group = parse_single_char_group('') # Ignore the "illegal" character
-
else
-
1
raise IllegalSyntaxError, "Anchors cannot be supported, as they are not regular"
-
end
-
when '$', 'z', 'Z'
-
2
if @current_position == (regexp_string.length - 1)
-
1
group = parse_single_char_group('') # Ignore the "illegal" character
-
else
-
1
raise IllegalSyntaxError, "Anchors cannot be supported, as they are not regular"
-
end
-
else
-
266
group = parse_single_char_group(char)
-
end
-
455
group
-
end
-
-
1
def parse_after_backslash_group
-
66
@current_position += 1
-
case
-
when rest_of_string =~ /\A(\d+)/
-
21
@current_position += ($1.length - 1) # In case of 10+ backrefs!
-
21
group = parse_backreference_group($1)
-
when rest_of_string =~ /\Ak<([^>]+)>/ # Named capture group
-
1
@current_position += ($1.length + 2)
-
1
group = parse_backreference_group($1)
-
when BackslashCharMap.keys.include?(regexp_string[@current_position])
-
15
group = CharGroup.new(
-
# Note: The `.dup` is important, as it prevents modifying the constant, in
-
# CharGroup#init_ranges (where the '-' is moved to the front)
-
BackslashCharMap[regexp_string[@current_position]].dup
-
)
-
when rest_of_string =~ /\A(c|C-)(.)/ # Control character
-
8
@current_position += $1.length
-
8
group = parse_single_char_group( parse_control_character($2) )
-
when rest_of_string =~ /\Ax(\h{1,2})/ # Escape sequence
-
3
@current_position += $1.length
-
3
group = parse_single_char_group( parse_escape_sequence($1) )
-
when rest_of_string =~ /\Au(\h{4}|\{\h{1,4}\})/ # Unicode sequence
-
3
@current_position += $1.length
-
3
sequence = $1.match(/\h{1,4}/)[0] # Strip off "{" and "}"
-
3
group = parse_single_char_group( parse_unicode_sequence(sequence) )
-
when rest_of_string =~ /\Ap\{([^}]+)\}/ # Named properties
-
3
@current_position += ($1.length + 2)
-
3
raise UnsupportedSyntaxError, "Named properties ({\\p#{$1}}) are not yet supported"
-
when rest_of_string =~ /\Ag/ # Subexpression call
-
# TODO: Should this be IllegalSyntaxError ?
-
1
raise UnsupportedSyntaxError, "Subexpression calls (\g) are not yet supported"
-
when rest_of_string =~ /\A[GbB]/ # Anchors
-
3
raise IllegalSyntaxError, "Anchors cannot be supported, as they are not regular"
-
when rest_of_string =~ /\AA/ # Start of string
-
2
if @current_position == 1
-
1
group = parse_single_char_group('') # Ignore the "illegal" character
-
else
-
1
raise IllegalSyntaxError, "Anchors cannot be supported, as they are not regular"
-
end
-
when rest_of_string =~ /\A[zZ]/ # End of string
-
4
if @current_position == (regexp_string.length - 1)
-
2
group = parse_single_char_group('') # Ignore the "illegal" character
-
else
-
2
raise IllegalSyntaxError, "Anchors cannot be supported, as they are not regular"
-
end
-
else
-
2
group = parse_single_char_group( regexp_string[@current_position] )
-
66
end
-
56
group
-
end
-
-
1
def parse_repeater(group)
-
399
char = regexp_string[@current_position]
-
399
case char
-
when '*'
-
5
repeater = parse_star_repeater(group)
-
when '+'
-
7
repeater = parse_plus_repeater(group)
-
when '?'
-
13
repeater = parse_question_mark_repeater(group)
-
when '{'
-
9
repeater = parse_range_repeater(group)
-
else
-
365
repeater = parse_one_time_repeater(group)
-
end
-
399
repeater
-
end
-
-
1
def parse_multi_group
-
50
@current_position += 1
-
50
@num_groups += 1
-
50
group_id = nil # init
-
50
rest_of_string.match(/\A(\?)?(:|!|=|<(!|=|[^!=][^>]*))?/) do |match|
-
case
-
when match[1].nil? # e.g. /(normal)/
-
42
group_id = @num_groups.to_s
-
when match[2] == ':' # e.g. /(?:nocapture)/
-
1
@current_position += 2
-
1
group_id = nil
-
when %w(! =).include?(match[2]) # e.g. /(?=lookahead)/, /(?!neglookahead)/
-
2
raise IllegalSyntaxError, "Lookaheads are not regular; cannot generate examples"
-
when %w(! =).include?(match[3]) # e.g. /(?<=lookbehind)/, /(?<!neglookbehind)/
-
2
raise IllegalSyntaxError, "Lookbehinds are not regular; cannot generate examples"
-
else # e.g. /(?<name>namedgroup)/
-
3
@current_position += (match[3].length + 3)
-
3
group_id = match[3]
-
50
end
-
end
-
46
groups = parse
-
45
MultiGroup.new(groups, group_id)
-
end
-
-
1
def parse_multi_end_group
-
45
MultiGroupEnd.new
-
end
-
-
1
def parse_char_group
-
27
if rest_of_string =~ /\A\[\[:[^:]+:\]\]/
-
1
raise UnsupportedSyntaxError, "POSIX bracket expressions are not yet implemented"
-
end
-
26
chars = []
-
26
@current_position += 1
-
26
if regexp_string[@current_position] == ']'
-
# Beware of the sneaky edge case:
-
# /[]]/ (match "]")
-
1
chars << ']'
-
1
@current_position += 1
-
end
-
until regexp_string[@current_position] == ']' \
-
26
&& !regexp_string[0..@current_position-1].match(/[^\\](\\{2})*\\\z/)
-
# Beware of having an ODD number of "\" before the "]", e.g.
-
# /[\]]/ (match "]")
-
# /[\\]/ (match "\")
-
# /[\\\]]/ (match "\" or "]")
-
102
chars << regexp_string[@current_position]
-
102
@current_position += 1
-
end
-
26
CharGroup.new(chars)
-
end
-
-
1
def parse_dot_group
-
4
DotGroup.new
-
end
-
-
1
def parse_or_group(left_repeaters)
-
11
@current_position += 1
-
11
right_repeaters = parse
-
11
OrGroup.new(left_repeaters, right_repeaters)
-
end
-
-
-
1
def parse_single_char_group(char)
-
287
SingleCharGroup.new(char)
-
end
-
-
1
def parse_backreference_group(match)
-
22
BackReferenceGroup.new(match)
-
end
-
-
1
def parse_control_character(char)
-
8
(char.ord % 32).chr # Black magic!
-
# eval "?\\C-#{char.chr}" # Doesn't work for e.g. char = "?"
-
end
-
-
1
def parse_escape_sequence(match)
-
3
eval "?\\x#{match}"
-
end
-
-
1
def parse_unicode_sequence(match)
-
3
eval "?\\u{#{match}}"
-
end
-
-
1
def parse_star_repeater(group)
-
5
@current_position += 1
-
5
StarRepeater.new(group)
-
end
-
-
1
def parse_plus_repeater(group)
-
7
@current_position += 1
-
7
PlusRepeater.new(group)
-
end
-
-
1
def parse_question_mark_repeater(group)
-
13
@current_position += 1
-
13
QuestionMarkRepeater.new(group)
-
end
-
-
1
def parse_range_repeater(group)
-
9
match = rest_of_string.match(/\A\{(\d+)?(,)?(\d+)?\}/)
-
9
@current_position += match[0].size
-
9
min = match[1].to_i if match[1]
-
9
has_comma = !match[2].nil?
-
9
max = match[3].to_i if match[3]
-
9
RangeRepeater.new(group, min, has_comma, max)
-
end
-
-
1
def parse_one_time_repeater(group)
-
365
OneTimeRepeater.new(group)
-
end
-
-
1
def rest_of_string
-
317
regexp_string[@current_position..-1]
-
end
-
end
-
end
-
-
1
class Regexp
-
1
module Examples
-
1
def examples(options={})
-
112
full_examples = RegexpExamples.map_results(
-
RegexpExamples::Parser.new(source, options).parse
-
)
-
95
RegexpExamples::BackReferenceReplacer.new.substitute_backreferences(full_examples)
-
end
-
end
-
1
include Examples
-
end
-
-
1
module RegexpExamples
-
1
class BaseRepeater
-
1
attr_reader :group
-
1
def initialize(group)
-
410
@group = group
-
end
-
-
1
def result(min_repeats, max_repeats)
-
369
group_results = @group.result[0 .. RegexpExamples.MaxGroupResults-1]
-
369
results = []
-
369
min_repeats.upto(max_repeats) do |repeats|
-
414
if repeats.zero?
-
19
results << [ GroupResult.new('') ]
-
else
-
results << RegexpExamples.permutations_of_strings(
-
[group_results] * repeats
-
395
)
-
end
-
end
-
369
results.flatten.uniq
-
end
-
end
-
-
1
class OneTimeRepeater < BaseRepeater
-
1
def initialize(group)
-
376
super
-
end
-
-
1
def result
-
335
super(1, 1)
-
end
-
end
-
-
1
class StarRepeater < BaseRepeater
-
1
def initialize(group)
-
5
super
-
end
-
-
1
def result
-
5
super(0, RegexpExamples.MaxRepeaterVariance)
-
end
-
end
-
-
1
class PlusRepeater < BaseRepeater
-
1
def initialize(group)
-
7
super
-
end
-
-
1
def result
-
7
super(1, RegexpExamples.MaxRepeaterVariance + 1)
-
end
-
end
-
-
1
class QuestionMarkRepeater < BaseRepeater
-
1
def initialize(group)
-
13
super
-
end
-
-
1
def result
-
13
super(0, 1)
-
end
-
end
-
-
1
class RangeRepeater < BaseRepeater
-
1
def initialize(group, min, has_comma, max)
-
9
super(group)
-
9
@min = min || 0
-
9
if max
-
# Prevent huge number of results in case of e.g. /.{1,100}/.examples
-
3
@max = smallest(max, @min + RegexpExamples.MaxRepeaterVariance)
-
6
elsif has_comma
-
1
@max = @min + RegexpExamples.MaxRepeaterVariance
-
else
-
5
@max = @min
-
end
-
end
-
-
1
def result
-
9
super(@min, @max)
-
end
-
-
1
private
-
1
def smallest(x, y)
-
3
(x < y) ? x : y
-
end
-
end
-
end
-
-
1
RSpec.describe Regexp, "#examples" do
-
1
def self.examples_exist_and_match(*regexps)
-
11
regexps.each do |regexp|
-
80
it do
-
80
regexp_examples = regexp.examples
-
80
expect(regexp_examples).not_to be_empty
-
263
regexp_examples.each { |example| expect(example).to match(/\A(?:#{regexp.source})\z/) }
-
# Note: /\A...\z/ is used, to prevent misleading examples from passing the test.
-
# For example, we don't want things like:
-
# /a*/.examples to include "xyz"
-
# /a|b/.examples to include "bad"
-
end
-
end
-
end
-
-
1
def self.examples_raise_illegal_syntax_error(*regexps)
-
1
regexps.each do |regexp|
-
12
it do
-
24
expect{regexp.examples}.to raise_error RegexpExamples::IllegalSyntaxError
-
end
-
end
-
end
-
-
1
def self.examples_raise_unsupported_syntax_error(*regexps)
-
1
regexps.each do |regexp|
-
5
it do
-
10
expect{regexp.examples}.to raise_error RegexpExamples::UnsupportedSyntaxError
-
end
-
end
-
end
-
-
1
def self.examples_are_empty(*regexps)
-
1
regexps.each do |regexp|
-
9
it do
-
9
expect(regexp.examples).to be_empty
-
end
-
end
-
end
-
-
1
context 'returns matching strings' do
-
1
context "for basic repeaters" do
-
1
examples_exist_and_match(
-
/a/,
-
/a*/,
-
/a+/,
-
/a?/,
-
/a{1}/,
-
/a{1,}/,
-
/a{,2}/,
-
/a{1,2}/
-
)
-
end
-
-
1
context "for basic groups" do
-
1
examples_exist_and_match(
-
/[a]/,
-
/(a)/,
-
/a|b/,
-
/./
-
)
-
end
-
-
1
context "for complex char groups (square brackets)" do
-
1
examples_exist_and_match(
-
-
/[abc]/,
-
/[a-c]/,
-
/[abc-e]/,
-
/[^a-zA-Z]/,
-
/[\w]/,
-
/[]]/, # TODO: How to suppress annoying warnings on this test?
-
/[\]]/,
-
/[\\]/,
-
/[\\\]]/,
-
/[\n-\r]/,
-
/[\-]/,
-
/[%-+]/, # This regex is "supposed to" match some surprising things!!!
-
/['-.]/ # Test to ensure no "infinite loop" on character set expansion
-
)
-
end
-
-
1
context "for complex multi groups" do
-
1
examples_exist_and_match(
-
/(normal)/,
-
/(?:nocapture)/,
-
/(?<name>namedgroup)/,
-
/(?<name>namedgroup) \k<name>/
-
)
-
end
-
-
1
context "for escaped characters" do
-
1
examples_exist_and_match(
-
/\w/,
-
/\W/,
-
/\s/,
-
/\S/,
-
/\d/,
-
/\D/,
-
/\h/,
-
/\H/,
-
/\t/,
-
/\n/,
-
/\f/,
-
/\a/,
-
/\v/,
-
/\e/,
-
/[\b]/
-
)
-
end
-
-
1
context "for backreferences" do
-
1
examples_exist_and_match(
-
/(repeat) \1/,
-
/(ref1) (ref2) \1 \2/,
-
/((ref2)ref1) \1 \2/,
-
/((ref1and2)) \1 \2/,
-
/(one)(two)(three)(four)(five)(six)(seven)(eight)(nine)(ten) \10\9\8\7\6\5\4\3\2\1/,
-
/(a?(b?(c?(d?(e?)))))/,
-
/(a)? \1/,
-
/(a|(b)) \2/
-
)
-
end
-
-
1
context "for complex patterns" do
-
# Longer combinations of the above
-
1
examples_exist_and_match(
-
/https?:\/\/(www\.)github\.com/,
-
/(I(N(C(E(P(T(I(O(N)))))))))*/,
-
/[\w]{1}/,
-
/((a?b*c+)) \1/,
-
/((a?b*c+)?) \1/,
-
/a|b|c|d/,
-
/a+|b*|c?/,
-
/one|two|three/
-
)
-
end
-
-
1
context "for illegal syntax" do
-
1
examples_raise_illegal_syntax_error(
-
/(?=lookahead)/,
-
/(?!neglookahead)/,
-
/(?<=lookbehind)/,
-
/(?<!neglookbehind)/,
-
/\bword-boundary/,
-
/no\Bn-word-boundary/,
-
/\Glast-match/,
-
/start-of\A-string/,
-
/start-of^-line/,
-
/end-of\Z-string/,
-
/end-of\z-string/,
-
/end-of$-line/
-
)
-
end
-
-
1
context "ignore start/end anchors if at start/end" do
-
1
examples_exist_and_match(
-
/\Astart/,
-
/^start/,
-
/end$/,
-
/end\z/,
-
/end\Z/
-
)
-
end
-
-
1
context "for unsupported syntax" do
-
1
examples_raise_unsupported_syntax_error(
-
/\p{L}/,
-
/\p{Arabic}/,
-
/\p{^Ll}/,
-
/(?<name> ... \g<name>*)/,
-
/[[:space:]]/
-
)
-
end
-
-
1
context "for control characters" do
-
1
examples_exist_and_match(
-
/\ca/,
-
/\cZ/,
-
/\c9/,
-
/\c[/,
-
/\c#/,
-
/\c?/,
-
/\C-a/,
-
/\C-&/
-
)
-
end
-
-
1
context "for escape sequences" do
-
1
examples_exist_and_match(
-
/\x42/,
-
/\x1D/,
-
/\x3word/,
-
/#{"\x80".force_encoding("ASCII-8BIT")}/
-
)
-
end
-
-
1
context "for unicode sequences" do
-
1
examples_exist_and_match(
-
/\u6829/,
-
/\uabcd/,
-
/\u{42}word/
-
)
-
end
-
-
1
context "for empty character sets" do
-
1
examples_are_empty(
-
/[^\d\D]/,
-
/[^\w\W]/,
-
/[^\s\S]/,
-
/[^\h\H]/,
-
/[^\D0-9]/,
-
/[^\Wa-zA-Z0-9_]/,
-
/[^\d\D]+/,
-
/[^\d\D]{2}/,
-
/[^\d\D]word/
-
)
-
end
-
-
1
context "exact examples match" do
-
# More rigorous tests to assert that ALL examples are being listed
-
1
context "default options" do
-
2
it { expect(/[ab]{2}/.examples).to eq ["aa", "ab", "ba", "bb"] }
-
2
it { expect(/(a|b){2}/.examples).to eq ["aa", "ab", "ba", "bb"] }
-
2
it { expect(/a+|b?/.examples).to eq ["a", "aa", "aaa", "", "b"] }
-
end
-
1
context "max_repeater_variance option" do
-
1
it do
-
1
expect(/a+/.examples(max_repeater_variance: 5))
-
.to eq %w(a aa aaa aaaa aaaaa aaaaaa)
-
end
-
1
it do
-
1
expect(/a{4,8}/.examples(max_repeater_variance: 0))
-
.to eq %w(aaaa)
-
end
-
end
-
1
context "max_group_results option" do
-
1
it do
-
1
expect(/\d/.examples(max_group_results: 10))
-
.to eq %w(0 1 2 3 4 5 6 7 8 9)
-
end
-
end
-
end
-
-
end
-
end