commit
8b6a2f1ffc
|
@ -2,6 +2,7 @@ package DDG::Goodie::Regexp;
|
||||||
# ABSTRACT: Parse a regexp.
|
# ABSTRACT: Parse a regexp.
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
|
use warnings;
|
||||||
use DDG::Goodie;
|
use DDG::Goodie;
|
||||||
|
|
||||||
use Safe;
|
use Safe;
|
||||||
|
@ -9,26 +10,88 @@ use Safe;
|
||||||
zci answer_type => "regexp";
|
zci answer_type => "regexp";
|
||||||
zci is_cached => 1;
|
zci is_cached => 1;
|
||||||
|
|
||||||
triggers query_lc => qr/^regex[p]? [\/\\](.+?)[\/\\] (.+)$/i;
|
triggers start => 'regex', 'match', 'regexp';
|
||||||
|
triggers any => '=~';
|
||||||
|
|
||||||
handle query => sub {
|
sub compile_re {
|
||||||
my $regexp = $1;
|
my ($re, $modifiers, $compiler) = @_;
|
||||||
my $str = $2;
|
$compiler->($re, $modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
my $compiler = Safe->new->reval(q{ sub { qr/$_[0]/ } });
|
# Using $& causes a performance penalty, apparently.
|
||||||
|
sub get_full_match {
|
||||||
|
return substr(shift, $-[0], $+[0] - $-[0]);
|
||||||
|
}
|
||||||
|
|
||||||
sub compile_re {
|
# Ensures that the correct numbered matches are being produced.
|
||||||
my ( $re, $compiler ) = @_;
|
sub real_number_matches {
|
||||||
$compiler->($re);
|
my ($one, @numbered) = @_;
|
||||||
|
# If the first match isn't defined then neither are the others!
|
||||||
|
return defined $one ? @numbered : ();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_match_record {
|
||||||
|
my ($regexp, $str, $modifiers) = @_;
|
||||||
|
my $compiler = Safe->new->reval(q { sub { qr/(?$_[1])$_[0]/ } }) or return;
|
||||||
|
BEGIN {
|
||||||
|
$SIG{'__WARN__'} = sub {
|
||||||
|
warn $_[0] if $_[0] !~ /Use of uninitialized value in regexp compilation/i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my @results = ();
|
my @numbered = $str =~ compile_re($regexp, $modifiers, $compiler) or return;
|
||||||
eval {
|
@numbered = real_number_matches($1, @numbered);
|
||||||
@results = $str =~ compile_re($regexp, $compiler);
|
my $matches = {};
|
||||||
|
$matches->{'Full Match'} = get_full_match($str);
|
||||||
|
foreach my $match (keys %+) {
|
||||||
|
$matches->{"Named Capture <$match>"} = $+{$match};
|
||||||
};
|
};
|
||||||
|
my $i = 1;
|
||||||
|
foreach my $match (@numbered) {
|
||||||
|
$matches->{"Subpattern Match $i"} = $match;
|
||||||
|
$i++;
|
||||||
|
};
|
||||||
|
return $matches;
|
||||||
|
}
|
||||||
|
|
||||||
return join( ' | ', @results ), heading => 'Regexp Result' if @results;
|
my $regex_re = qr/\/(?<regex>.+)\/(?<modifiers>i)?/;
|
||||||
return;
|
|
||||||
|
sub extract_regex_text {
|
||||||
|
my $query = shift;
|
||||||
|
$query =~ /^(?<text>.+) =~ $regex_re$/;
|
||||||
|
($+{regex} && $+{text}) || ($query =~ /^(?:match\s*regexp?|regexp?)\s*$regex_re\s+(?<text>.+)$/);
|
||||||
|
return unless defined $+{regex} && defined $+{text};
|
||||||
|
my $modifiers = $+{modifiers} // '';
|
||||||
|
return ($+{regex}, $+{text}, $modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_match_keys { return sort (keys %{$_[0]}) }
|
||||||
|
|
||||||
|
handle query => sub {
|
||||||
|
my $query = $_;
|
||||||
|
my ($regexp, $str, $modifiers) = extract_regex_text($query) or return;
|
||||||
|
my $matches = get_match_record($regexp, $str, $modifiers) or return;
|
||||||
|
my @key_order = get_match_keys($matches);
|
||||||
|
return unless $matches->{'Full Match'} ne '';
|
||||||
|
|
||||||
|
return $matches,
|
||||||
|
structured_answer => {
|
||||||
|
id => 'regexp',
|
||||||
|
name => 'Answer',
|
||||||
|
data => {
|
||||||
|
title => "Regular Expression Match",
|
||||||
|
subtitle => "Match regular expression /$regexp/$modifiers on $str",
|
||||||
|
record_data => $matches,
|
||||||
|
record_keys => \@key_order,
|
||||||
|
},
|
||||||
|
templates => {
|
||||||
|
group => 'list',
|
||||||
|
options => {
|
||||||
|
content => 'record',
|
||||||
|
},
|
||||||
|
moreAt => 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.zci--regexp .record .record__cell--key {
|
||||||
|
width: 20em;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
91
t/Regexp.t
91
t/Regexp.t
|
@ -8,24 +8,79 @@ use DDG::Test::Goodie;
|
||||||
zci answer_type => 'regexp';
|
zci answer_type => 'regexp';
|
||||||
zci is_cached => 1;
|
zci is_cached => 1;
|
||||||
|
|
||||||
ddg_goodie_test(
|
sub build_structured_answer {
|
||||||
[qw( DDG::Goodie::Regexp )],
|
my ($result, $expression, $text) = @_;
|
||||||
'regexp /(hello\s)/ hello probably' => test_zci(
|
return $result,
|
||||||
"hello ",
|
structured_answer => {
|
||||||
heading => 'Regexp Result',
|
id => 'regexp',
|
||||||
),
|
name => 'Answer',
|
||||||
'regexp /(dd)/ ddg' => test_zci(
|
data => {
|
||||||
"dd",
|
title => 'Regular Expression Match',
|
||||||
heading => 'Regexp Result',
|
subtitle => "Match regular expression $expression on $text",
|
||||||
),
|
record_data => $result,
|
||||||
'regex /(poss)/ many possibilities' => test_zci(
|
record_keys => \@{[sort (keys %$result)]},
|
||||||
"poss",
|
},
|
||||||
heading => 'Regexp Result',
|
templates => {
|
||||||
),
|
group => 'list',
|
||||||
'regexp /(.*)/ ddg' => test_zci(
|
options => {
|
||||||
'ddg',
|
content => 'record',
|
||||||
heading => 'Regexp Result'
|
},
|
||||||
),
|
moreAt => 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub build_test { test_zci(build_structured_answer(@_)) }
|
||||||
|
|
||||||
|
ddg_goodie_test([qw( DDG::Goodie::Regexp )],
|
||||||
|
'regexp /(?<name>Harry|Larry) is awesome/ Harry is awesome' => build_test({
|
||||||
|
'Full Match' => 'Harry is awesome',
|
||||||
|
'Named Capture <name>' => 'Harry',
|
||||||
|
'Subpattern Match 1' => 'Harry',
|
||||||
|
}, '/(?<name>Harry|Larry) is awesome/', 'Harry is awesome'),
|
||||||
|
'regex /(he|she) walked away/ he walked away' => build_test({
|
||||||
|
'Full Match' => 'he walked away',
|
||||||
|
'Subpattern Match 1' => 'he',
|
||||||
|
}, '/(he|she) walked away/', 'he walked away'),
|
||||||
|
'match regex /How are (?:we|you) (doing|today)\?/ How are you today?' => build_test({
|
||||||
|
'Full Match' => 'How are you today?',
|
||||||
|
'Subpattern Match 1' => 'today',
|
||||||
|
}, '/How are (?:we|you) (doing|today)\?/', 'How are you today?'),
|
||||||
|
'abc =~ /[abc]+/' => build_test({
|
||||||
|
'Full Match' => 'abc',
|
||||||
|
}, '/[abc]+/', 'abc'),
|
||||||
|
'DDG::Goodie::Regexp =~ /^DDG::Goodie::(?<goodie>\w+)$/' => build_test({
|
||||||
|
'Full Match' => 'DDG::Goodie::Regexp',
|
||||||
|
'Named Capture <goodie>' => 'Regexp',
|
||||||
|
'Subpattern Match 1' => 'Regexp',
|
||||||
|
}, '/^DDG::Goodie::(?<goodie>\w+)$/', 'DDG::Goodie::Regexp'),
|
||||||
|
'regexp /foo/ foo' => build_test({
|
||||||
|
'Full Match' => 'foo',
|
||||||
|
}, '/foo/', 'foo'),
|
||||||
|
# Modifiers
|
||||||
|
'Foo =~ /(foo)/i' => build_test({
|
||||||
|
'Full Match' => 'Foo',
|
||||||
|
'Subpattern Match 1' => 'Foo',
|
||||||
|
}, '/(foo)/i', 'Foo'),
|
||||||
|
'regexp /hello/i HELLO' => build_test({
|
||||||
|
'Full Match' => 'HELLO',
|
||||||
|
}, '/hello/i', 'HELLO'),
|
||||||
|
# Primary example query
|
||||||
|
'regexp /(.*)/ ddg' => build_test({
|
||||||
|
'Full Match' => 'ddg',
|
||||||
|
'Subpattern Match 1' => 'ddg',
|
||||||
|
}, '/(.*)/', 'ddg'),
|
||||||
|
# Does not match.
|
||||||
|
'regexp /foo/ bar' => undef,
|
||||||
|
'match /^foo$/ foo bar' => undef,
|
||||||
|
# Should not trigger.
|
||||||
|
'What is regex?' => undef,
|
||||||
|
'regex cheatsheet' => undef,
|
||||||
|
'regex' => undef,
|
||||||
|
'/foo/ =~ foo' => undef,
|
||||||
|
'regex foo /foo/' => undef,
|
||||||
|
'BaR =~ /bar/x' => undef,
|
||||||
|
'regexp /(?<hh(h)h>h)/ h' => undef,
|
||||||
);
|
);
|
||||||
|
|
||||||
done_testing;
|
done_testing;
|
||||||
|
|
Loading…
Reference in New Issue