From 6bc72cb09b3e8126fb08368a5ce5ac541eb64edd Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 15:56:38 +0000 Subject: [PATCH 01/13] Add test skeleton --- t/Regexp.t | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/t/Regexp.t b/t/Regexp.t index f251f9031..a15844864 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -8,25 +8,29 @@ use DDG::Test::Goodie; zci answer_type => 'regexp'; zci is_cached => 1; -ddg_goodie_test( - [qw( DDG::Goodie::Regexp )], - 'regexp /(hello\s)/ hello probably' => test_zci( - "hello ", - heading => 'Regexp Result', - ), - 'regexp /(dd)/ ddg' => test_zci( - "dd", - heading => 'Regexp Result', - ), - 'regex /(poss)/ many possibilities' => test_zci( - "poss", - heading => 'Regexp Result', - ), - 'regexp /(.*)/ ddg' => test_zci( - 'ddg', - heading => 'Regexp Result' - ), -); +sub build_structured_answer { + my ($result, $expression, $text) = @_; + return $result, + structured_answer => { + id => 'regexp', + name => 'Answer', + data => { + title => 'Regular Expression Match', + subtitle => "Match regular expression /$expression/ on $text", + record_data => $result, + }, + templates => { + group => 'list', + options => { + content => 'record', + }, + moreAt => 0, + }, + }; +} + +sub build_test { test_zci(build_structured_answer(@_)) } + done_testing; From 578866e4ecdba5d354d50d294bbdb592b0daecf9 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 15:57:07 +0000 Subject: [PATCH 02/13] Add tests --- t/Regexp.t | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/t/Regexp.t b/t/Regexp.t index a15844864..4257f9e76 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -31,6 +31,32 @@ sub build_structured_answer { sub build_test { test_zci(build_structured_answer(@_)) } +ddg_goodie_test([qw( DDG::Goodie::Regexp )], + 'regexp /(?Harry|Larry) is awesome/ Harry is awesome' => build_test({ + 'Full Match' => 'Harry is awesome', + 'Named Match (name)' => 'Harry', + 'Number Match (1)' => 'Harry', + }, '(?Harry|Larry) is awesome', 'Harry is awesome'), + 'regex /(he|she) walked away/ he walked away' => build_test({ + 'Full Match' => 'he walked away', + 'Number 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?', + 'Number Match (1)' => 'today', + }, 'How are (?:we|you) (doing|today)\?', 'How are you today?'), + 'regexp /(.*)/ ddg' => build_test({ + 'Full Match' => 'ddg', + 'Number 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, +); done_testing; From c5b3330ad953477533fa645e81e3b225ad0d9cee Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 15:58:09 +0000 Subject: [PATCH 03/13] Add skeleton answer template Will use the `list` group for displaying multiple results. --- lib/DDG/Goodie/Regexp.pm | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index f98e61de8..4311e3195 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -27,8 +27,23 @@ handle query => sub { @results = $str =~ compile_re($regexp, $compiler); }; - return join( ' | ', @results ), heading => 'Regexp Result' if @results; - return; + return $matches, + structured_answer => { + id => 'regexp', + name => 'Answer', + data => { + title => "Regular Expression Match", + subtitle => "Match regular expression /$regexp/ on $str", + record_data => $matches, + }, + templates => { + group => 'list', + options => { + content => 'record', + }, + moreAt => 0, + }, + }; }; 1; From b9a841cc7f6390b8cb002c909af1adfcdfc243f5 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 16:02:00 +0000 Subject: [PATCH 04/13] Update triggers and handler Remove unnecessary regex trigger and perform matches within the handler. --- lib/DDG/Goodie/Regexp.pm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index 4311e3195..9f733ad3e 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -9,7 +9,7 @@ use Safe; zci answer_type => "regexp"; zci is_cached => 1; -triggers query_lc => qr/^regex[p]? [\/\\](.+?)[\/\\] (.+)$/i; +triggers start => 'regex', 'match', 'regexp'; handle query => sub { my $regexp = $1; @@ -17,15 +17,15 @@ handle query => sub { my $compiler = Safe->new->reval(q{ sub { qr/$_[0]/ } }); - sub compile_re { - my ( $re, $compiler ) = @_; - $compiler->($re); - } +handle query => sub { + my $query = $_; + $query =~ s/^(?:match)?(?:\s*regexp?)?\s*//; + $query =~ /(?:\/(.+)\/)\s+(.+)/; + my $regexp = $1; + my $str = $2; + return unless defined $regexp && defined $str; - my @results = (); - eval { - @results = $str =~ compile_re($regexp, $compiler); - }; + my $matches = get_match_record($regexp, $str) or return; return $matches, structured_answer => { From 8651a0c33f6f64a4c3045eec0e0568718768adac Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 16:03:00 +0000 Subject: [PATCH 05/13] Add result generation functions The result produced is a hash which contains the full match, numbered matches and named matches. --- lib/DDG/Goodie/Regexp.pm | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index 9f733ad3e..11bf8afb4 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -11,11 +11,32 @@ zci is_cached => 1; triggers start => 'regex', 'match', 'regexp'; -handle query => sub { - my $regexp = $1; - my $str = $2; +sub compile_re { + my ($re, $compiler) = @_; + $compiler->($re); +} +# Using $& causes a performance penalty, apparently. +sub get_full_match { + return substr(shift, $-[0], $+[0] - $-[0]); +} + +sub get_match_record { + my ($regexp, $str) = @_; my $compiler = Safe->new->reval(q{ sub { qr/$_[0]/ } }); + my @numbered = $str =~ compile_re($regexp, $compiler) or return; + my $matches = {}; + $matches->{'Full Match'} = get_full_match($str); + foreach my $match (keys %+) { + $matches->{"Named Match ($match)"} = $+{$match}; + }; + my $i = 1; + foreach my $match (@numbered) { + $matches->{"Number Match ($i)"} = $match; + $i++; + }; + return $matches; +} handle query => sub { my $query = $_; From 96a69e6965a5a244330f1dfcecf7330ef30daef6 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 17:01:58 +0000 Subject: [PATCH 06/13] Fix first match being assigned incorrectly If expression had no paren-matches then the numbered matches list was just being passed "1" - not ideal! --- lib/DDG/Goodie/Regexp.pm | 8 ++++++++ t/Regexp.t | 3 +++ 2 files changed, 11 insertions(+) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index 11bf8afb4..2b7e04f9d 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -21,10 +21,18 @@ sub get_full_match { return substr(shift, $-[0], $+[0] - $-[0]); } +# Ensures that the correct numbered matches are being produced. +sub real_number_matches { + 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) = @_; my $compiler = Safe->new->reval(q{ sub { qr/$_[0]/ } }); my @numbered = $str =~ compile_re($regexp, $compiler) or return; + @numbered = real_number_matches($1, @numbered); my $matches = {}; $matches->{'Full Match'} = get_full_match($str); foreach my $match (keys %+) { diff --git a/t/Regexp.t b/t/Regexp.t index 4257f9e76..c2bb34d9b 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -49,6 +49,9 @@ ddg_goodie_test([qw( DDG::Goodie::Regexp )], 'Full Match' => 'ddg', 'Number Match (1)' => 'ddg', }, '(.*)', 'ddg'), + 'regexp /foo/ foo' => build_test({ + 'Full Match' => 'foo', + }, 'foo', 'foo'), # Does not match. 'regexp /foo/ bar' => undef, 'match /^foo$/ foo bar' => undef, From 242c5573ff2009e3dbf1e1646e7778faebce0fbb Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 17:32:28 +0000 Subject: [PATCH 07/13] Add support for perl-style regex matches Allows regex matches in the form `X =~ /Y/`. --- lib/DDG/Goodie/Regexp.pm | 16 ++++++++++------ t/Regexp.t | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index 2b7e04f9d..34504ed24 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -10,6 +10,7 @@ zci answer_type => "regexp"; zci is_cached => 1; triggers start => 'regex', 'match', 'regexp'; +triggers any => '=~'; sub compile_re { my ($re, $compiler) = @_; @@ -46,14 +47,17 @@ sub get_match_record { return $matches; } +sub extract_regex_text { + my $query = shift; + $query =~ /^(?.+) =~ \/(?.+)\/$/; + ($+{regex} && $+{text}) || ($query =~ /^(?:match\s*regexp?|regexp?)\s*\/(?.+)\/\s+(?.+)$/); + return unless defined $+{regex} && defined $+{text}; + return ($+{regex}, $+{text}); +} + handle query => sub { my $query = $_; - $query =~ s/^(?:match)?(?:\s*regexp?)?\s*//; - $query =~ /(?:\/(.+)\/)\s+(.+)/; - my $regexp = $1; - my $str = $2; - return unless defined $regexp && defined $str; - + my ($regexp, $str) = extract_regex_text($query) or return; my $matches = get_match_record($regexp, $str) or return; return $matches, diff --git a/t/Regexp.t b/t/Regexp.t index c2bb34d9b..636141cf2 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -45,13 +45,22 @@ ddg_goodie_test([qw( DDG::Goodie::Regexp )], 'Full Match' => 'How are you today?', 'Number 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::(?\w+)$/' => build_test({ + 'Full Match' => 'DDG::Goodie::Regexp', + 'Named Match (goodie)' => 'Regexp', + 'Number Match (1)' => 'Regexp', + }, '^DDG::Goodie::(?\w+)$', 'DDG::Goodie::Regexp'), + 'regexp /foo/ foo' => build_test({ + 'Full Match' => 'foo', + }, 'foo', 'foo'), + # Primary example query 'regexp /(.*)/ ddg' => build_test({ 'Full Match' => 'ddg', 'Number Match (1)' => 'ddg', }, '(.*)', 'ddg'), - 'regexp /foo/ foo' => build_test({ - 'Full Match' => 'foo', - }, 'foo', 'foo'), # Does not match. 'regexp /foo/ bar' => undef, 'match /^foo$/ foo bar' => undef, @@ -59,6 +68,8 @@ ddg_goodie_test([qw( DDG::Goodie::Regexp )], 'What is regex?' => undef, 'regex cheatsheet' => undef, 'regex' => undef, + '/foo/ =~ foo' => undef, + 'regex foo /foo/' => undef, ); done_testing; From ed622cf84bb2aa335301e481a5848ddf64cdca15 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 18:21:26 +0000 Subject: [PATCH 08/13] Add support for ignore-case modifier --- lib/DDG/Goodie/Regexp.pm | 25 ++++++++++++++----------- t/Regexp.t | 25 +++++++++++++++++-------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index 34504ed24..c07b5eb1c 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -13,8 +13,8 @@ triggers start => 'regex', 'match', 'regexp'; triggers any => '=~'; sub compile_re { - my ($re, $compiler) = @_; - $compiler->($re); + my ($re, $modifiers, $compiler) = @_; + $compiler->($re, $modifiers); } # Using $& causes a performance penalty, apparently. @@ -30,9 +30,9 @@ sub real_number_matches { } sub get_match_record { - my ($regexp, $str) = @_; - my $compiler = Safe->new->reval(q{ sub { qr/$_[0]/ } }); - my @numbered = $str =~ compile_re($regexp, $compiler) or return; + my ($regexp, $str, $modifiers) = @_; + my $compiler = Safe->new->reval(q{ sub { qr/(?$_[1])$_[0]/ } }); + my @numbered = $str =~ compile_re($regexp, $modifiers, $compiler) or return; @numbered = real_number_matches($1, @numbered); my $matches = {}; $matches->{'Full Match'} = get_full_match($str); @@ -47,18 +47,21 @@ sub get_match_record { return $matches; } +my $regex_re = qr/\/(?.+)\/(?i)?/; + sub extract_regex_text { my $query = shift; - $query =~ /^(?.+) =~ \/(?.+)\/$/; - ($+{regex} && $+{text}) || ($query =~ /^(?:match\s*regexp?|regexp?)\s*\/(?.+)\/\s+(?.+)$/); + $query =~ /^(?.+) =~ $regex_re$/; + ($+{regex} && $+{text}) || ($query =~ /^(?:match\s*regexp?|regexp?)\s*$regex_re\s+(?.+)$/); return unless defined $+{regex} && defined $+{text}; - return ($+{regex}, $+{text}); + my $modifiers = $+{modifiers} // ''; + return ($+{regex}, $+{text}, $modifiers); } handle query => sub { my $query = $_; - my ($regexp, $str) = extract_regex_text($query) or return; - my $matches = get_match_record($regexp, $str) or return; + my ($regexp, $str, $modifiers) = extract_regex_text($query) or return; + my $matches = get_match_record($regexp, $str, $modifiers) or return; return $matches, structured_answer => { @@ -66,7 +69,7 @@ handle query => sub { name => 'Answer', data => { title => "Regular Expression Match", - subtitle => "Match regular expression /$regexp/ on $str", + subtitle => "Match regular expression /$regexp/$modifiers on $str", record_data => $matches, }, templates => { diff --git a/t/Regexp.t b/t/Regexp.t index 636141cf2..463f7b8bc 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -16,7 +16,7 @@ sub build_structured_answer { name => 'Answer', data => { title => 'Regular Expression Match', - subtitle => "Match regular expression /$expression/ on $text", + subtitle => "Match regular expression $expression on $text", record_data => $result, }, templates => { @@ -36,31 +36,39 @@ ddg_goodie_test([qw( DDG::Goodie::Regexp )], 'Full Match' => 'Harry is awesome', 'Named Match (name)' => 'Harry', 'Number Match (1)' => 'Harry', - }, '(?Harry|Larry) is awesome', 'Harry is awesome'), + }, '/(?Harry|Larry) is awesome/', 'Harry is awesome'), 'regex /(he|she) walked away/ he walked away' => build_test({ 'Full Match' => 'he walked away', 'Number Match (1)' => 'he', - }, '(he|she) walked away', 'he walked away'), + }, '/(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?', 'Number Match (1)' => 'today', - }, 'How are (?:we|you) (doing|today)\?', 'How are you today?'), + }, '/How are (?:we|you) (doing|today)\?/', 'How are you today?'), 'abc =~ /[abc]+/' => build_test({ 'Full Match' => 'abc', - }, '[abc]+', 'abc'), + }, '/[abc]+/', 'abc'), 'DDG::Goodie::Regexp =~ /^DDG::Goodie::(?\w+)$/' => build_test({ 'Full Match' => 'DDG::Goodie::Regexp', 'Named Match (goodie)' => 'Regexp', 'Number Match (1)' => 'Regexp', - }, '^DDG::Goodie::(?\w+)$', 'DDG::Goodie::Regexp'), + }, '/^DDG::Goodie::(?\w+)$/', 'DDG::Goodie::Regexp'), 'regexp /foo/ foo' => build_test({ 'Full Match' => 'foo', - }, 'foo', 'foo'), + }, '/foo/', 'foo'), + # Modifiers + 'Foo =~ /(foo)/i' => build_test({ + 'Full Match' => 'Foo', + 'Number 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', 'Number Match (1)' => 'ddg', - }, '(.*)', 'ddg'), + }, '/(.*)/', 'ddg'), # Does not match. 'regexp /foo/ bar' => undef, 'match /^foo$/ foo bar' => undef, @@ -70,6 +78,7 @@ ddg_goodie_test([qw( DDG::Goodie::Regexp )], 'regex' => undef, '/foo/ =~ foo' => undef, 'regex foo /foo/' => undef, + 'BaR =~ /bar/x' => undef, ); done_testing; From b33428914d8d5bf4b517bbece3830457ece79e74 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 19:23:07 +0000 Subject: [PATCH 09/13] Correct ordering of results Will display Full Match, then Named, then Numbered. --- lib/DDG/Goodie/Regexp.pm | 4 ++++ t/Regexp.t | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index c07b5eb1c..97fd88bf4 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -58,10 +58,13 @@ sub extract_regex_text { 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 $matches, structured_answer => { @@ -71,6 +74,7 @@ handle query => sub { title => "Regular Expression Match", subtitle => "Match regular expression /$regexp/$modifiers on $str", record_data => $matches, + record_keys => \@key_order, }, templates => { group => 'list', diff --git a/t/Regexp.t b/t/Regexp.t index 463f7b8bc..3bba21ace 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -18,6 +18,7 @@ sub build_structured_answer { title => 'Regular Expression Match', subtitle => "Match regular expression $expression on $text", record_data => $result, + record_keys => \@{[sort (keys %$result)]}, }, templates => { group => 'list', From 5a2a0035039c1bbf1eb795edd1c4ea78024c9c55 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 19:33:03 +0000 Subject: [PATCH 10/13] Improve naming of results Named Match (X) -> Named Capture Number Match (X) -> Subpattern Match X --- lib/DDG/Goodie/Regexp.pm | 4 ++-- t/Regexp.t | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index 97fd88bf4..a3ad34ced 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -37,11 +37,11 @@ sub get_match_record { my $matches = {}; $matches->{'Full Match'} = get_full_match($str); foreach my $match (keys %+) { - $matches->{"Named Match ($match)"} = $+{$match}; + $matches->{"Named Capture <$match>"} = $+{$match}; }; my $i = 1; foreach my $match (@numbered) { - $matches->{"Number Match ($i)"} = $match; + $matches->{"Subpattern Match $i"} = $match; $i++; }; return $matches; diff --git a/t/Regexp.t b/t/Regexp.t index 3bba21ace..c068911b1 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -34,41 +34,41 @@ sub build_test { test_zci(build_structured_answer(@_)) } ddg_goodie_test([qw( DDG::Goodie::Regexp )], 'regexp /(?Harry|Larry) is awesome/ Harry is awesome' => build_test({ - 'Full Match' => 'Harry is awesome', - 'Named Match (name)' => 'Harry', - 'Number Match (1)' => 'Harry', + 'Full Match' => 'Harry is awesome', + 'Named Capture ' => 'Harry', + 'Subpattern Match 1' => 'Harry', }, '/(?Harry|Larry) is awesome/', 'Harry is awesome'), 'regex /(he|she) walked away/ he walked away' => build_test({ - 'Full Match' => 'he walked away', - 'Number Match (1)' => 'he', + '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?', - 'Number Match (1)' => 'today', + '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::(?\w+)$/' => build_test({ - 'Full Match' => 'DDG::Goodie::Regexp', - 'Named Match (goodie)' => 'Regexp', - 'Number Match (1)' => 'Regexp', + 'Full Match' => 'DDG::Goodie::Regexp', + 'Named Capture ' => 'Regexp', + 'Subpattern Match 1' => 'Regexp', }, '/^DDG::Goodie::(?\w+)$/', 'DDG::Goodie::Regexp'), 'regexp /foo/ foo' => build_test({ 'Full Match' => 'foo', }, '/foo/', 'foo'), # Modifiers 'Foo =~ /(foo)/i' => build_test({ - 'Full Match' => 'Foo', - 'Number Match (1)' => 'Foo', + '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', - 'Number Match (1)' => 'ddg', + 'Full Match' => 'ddg', + 'Subpattern Match 1' => 'ddg', }, '/(.*)/', 'ddg'), # Does not match. 'regexp /foo/ bar' => undef, From 510067aa8c32526aab1d8947b4ef0d503bda9253 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 22:32:51 +0000 Subject: [PATCH 11/13] Fix formatting issues Expanded the key width a bit so longer names can be used without wrapping. Fixed the issue with named captures being capitalized. --- share/goodie/regexp/regexp.css | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 share/goodie/regexp/regexp.css diff --git a/share/goodie/regexp/regexp.css b/share/goodie/regexp/regexp.css new file mode 100644 index 000000000..d3a1f7e6d --- /dev/null +++ b/share/goodie/regexp/regexp.css @@ -0,0 +1,4 @@ +.zci--regexp .record .record__cell--key { + width: 20em; + text-transform: none; +} From 6e03f6aee7a1879ad63d05549ee1e15d6192b463 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Sat, 2 Jan 2016 22:51:29 +0000 Subject: [PATCH 12/13] Fix issue with brackets in named captures For some reason it was displaying a result (when it shouldn't) if there were brackets in the names of the capture groups. --- lib/DDG/Goodie/Regexp.pm | 1 + t/Regexp.t | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index a3ad34ced..2fa9002bb 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -65,6 +65,7 @@ handle query => sub { 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 => { diff --git a/t/Regexp.t b/t/Regexp.t index c068911b1..32758def6 100644 --- a/t/Regexp.t +++ b/t/Regexp.t @@ -74,12 +74,13 @@ ddg_goodie_test([qw( DDG::Goodie::Regexp )], '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, + 'What is regex?' => undef, + 'regex cheatsheet' => undef, + 'regex' => undef, + '/foo/ =~ foo' => undef, + 'regex foo /foo/' => undef, + 'BaR =~ /bar/x' => undef, + 'regexp /(?h)/ h' => undef, ); done_testing; From cadd1b01542a15fc4b9b9157d71482234a5ba3c8 Mon Sep 17 00:00:00 2001 From: Ben Moon Date: Tue, 5 Jan 2016 12:42:45 +0000 Subject: [PATCH 13/13] Fix issue with invalid regexes causing warnings Suppresses warnings that relate to invalid compilation of the user's regular expression. --- lib/DDG/Goodie/Regexp.pm | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/DDG/Goodie/Regexp.pm b/lib/DDG/Goodie/Regexp.pm index 2fa9002bb..0e5841b31 100644 --- a/lib/DDG/Goodie/Regexp.pm +++ b/lib/DDG/Goodie/Regexp.pm @@ -2,6 +2,7 @@ package DDG::Goodie::Regexp; # ABSTRACT: Parse a regexp. use strict; +use warnings; use DDG::Goodie; use Safe; @@ -31,11 +32,17 @@ sub real_number_matches { sub get_match_record { my ($regexp, $str, $modifiers) = @_; - my $compiler = Safe->new->reval(q{ sub { qr/(?$_[1])$_[0]/ } }); + 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 @numbered = $str =~ compile_re($regexp, $modifiers, $compiler) or return; @numbered = real_number_matches($1, @numbered); - my $matches = {}; - $matches->{'Full Match'} = get_full_match($str); + my $matches = {}; + $matches->{'Full Match'} = get_full_match($str); foreach my $match (keys %+) { $matches->{"Named Capture <$match>"} = $+{$match}; };