File Coverage

blib/lib/String/Util/Match.pm
Criterion Covered Total %
statement 45 45 100.0
branch 34 34 100.0
condition n/a
subroutine 6 6 100.0
pod 3 3 100.0
total 88 88 100.0


line stmt bran cond sub pod time code
1             package String::Util::Match;
2              
3 2     2   457203 use strict;
  2         2  
  2         66  
4 2     2   8 use warnings;
  2         3  
  2         122  
5              
6 2     2   8 use Exporter qw(import);
  2         12  
  2         1478  
7              
8             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
9             our $DATE = '2024-01-10'; # DATE
10             our $DIST = 'String-Util-Match'; # DIST
11             our $VERSION = '0.005'; # VERSION
12              
13             our @EXPORT_OK = qw(
14             match_string
15             match_array_or_regex
16             num_occurs
17             );
18              
19             our %SPEC;
20              
21             $SPEC{':package'} = {
22             v => 1.1,
23             summary => 'String utilities related to matching',
24             };
25              
26             my $_str_or_re = ['any*'=>{of=>['re*','str*']}];
27              
28             $SPEC{match_string} = {
29             v => 1.1,
30             summary => 'Match a string (with one of several choices)',
31             args => {
32             ignore_case => {
33             schema => 'bool*',
34             description => <<'MARKDOWN',
35              
36             Only relevant for string vs string matching.
37              
38             MARKDOWN
39             },
40             str => {
41             summary => 'String to match against',
42             schema => 'str*',
43             req => 1,
44             },
45             matcher => {
46             summary => 'Matcher',
47             #schema => 'matcher::str*',
48             schema => ['any*', of=> [
49             'str*',
50             'aos*',
51             'obj::re*',
52             'code*',
53             ]],
54             },
55             },
56             #args_as => 'array',
57             result_naked => 1,
58             };
59             sub match_string {
60 14     14 1 412707 my %args = @_;
61              
62 14         27 my $str = $args{str};
63 14 100       31 return 0 unless defined $str;
64              
65 13         20 my $matcher = $args{matcher};
66 13         17 my $ref = ref $matcher;
67 13 100       33 if (!$ref) {
    100          
    100          
    100          
68 4 100       23 return $args{ignore_case} ? lc($str) eq lc($matcher) : $str eq $matcher;
69             } elsif ($ref eq 'ARRAY') {
70 4 100       7 if ($args{ignore_case}) {
71 1         5 my $lc = lc $str;
72 1         1 for (@$matcher) {
73 2 100       7 return 1 if $lc eq lc($_);
74             }
75             } else {
76 3         5 for (@$matcher) {
77 6 100       14 return 1 if $str eq $_;
78             }
79             }
80 2         10 return 0;
81             } elsif ($ref eq 'Regexp') {
82 2         37 return $str =~ $matcher;
83             } elsif ($ref eq 'CODE') {
84 2 100       5 return $matcher->($str) ? 1:0;
85             } else {
86 1         11 die "Matcher must be string/array/Regexp/code (got $ref)";
87             }
88             }
89              
90             $SPEC{match_array_or_regex} = {
91             v => 1.1,
92             summary => 'Check whether an item matches (list of) values/regexes',
93             description => <<'_',
94              
95             This routine can be used to match an item against a regex or a list of
96             strings/regexes, e.g. when matching against an ACL.
97              
98             Since the smartmatch (`~~`) operator can already match against a list of strings
99             or regexes, this function is currently basically equivalent to:
100              
101             if (ref($haystack) eq 'ARRAY') {
102             return $needle ~~ @$haystack;
103             } else {
104             return $needle =~ /$haystack/;
105             }
106              
107             except that the smartmatch operator covers more cases and is currently
108             deprecated in the current perl versions and might be removed in future versions.
109              
110             _
111             examples => [
112             {args=>{needle=>"abc", haystack=>["abc", "abd"]}, result=>1},
113             {args=>{needle=>"abc", haystack=>qr/ab./}, result=>1},
114             {args=>{needle=>"abc", haystack=>[qr/ab./, "abd"]}, result=>1},
115             ],
116             args_as => 'array',
117             args => {
118             needle => {
119             schema => ["str*"],
120             pos => 0,
121             req => 1,
122             },
123             haystack => {
124             # XXX checking this schema might actually take longer than matching
125             # the needle! so when arg validation is implemented, provide a way
126             # to skip validating this schema
127              
128             schema => ["any*" => {
129             of => [$_str_or_re, ["array*"=>{of=>$_str_or_re}]],
130             }],
131             pos => 1,
132             req => 1,
133             },
134             },
135             result_naked => 1,
136             };
137             sub match_array_or_regex {
138 9     9 1 3090 my ($needle, $haystack) = @_;
139 9         17 my $ref = ref($haystack);
140 9 100       26 if ($ref eq 'ARRAY') {
    100          
    100          
141 5         9 for (@$haystack) {
142 9 100       14 if (ref $_ eq 'Regexp') {
143 2 100       20 return 1 if $needle =~ $_;
144             } else {
145 7 100       24 return 1 if $needle eq $_;
146             }
147             }
148 2         7 return 0;
149             } elsif (!$ref) {
150 1         16 return $needle =~ /$haystack/;
151             } elsif ($ref eq 'Regexp') {
152 2         25 return $needle =~ $haystack;
153             } else {
154 1         8 die "Invalid haystack, must be regex or array of strings/regexes";
155             }
156             }
157              
158             $SPEC{num_occurs} = {
159             v => 1.1,
160             summary => "Count how many times a substring occurs (or a regex pattern matches) a string",
161             args => {
162             string => {
163             schema => 'str*',
164             req => 1,
165             pos => 0,
166             },
167             substring => {
168             schema => $_str_or_re,
169             req => 1,
170             pos => 1,
171             },
172             },
173             args_as => 'array',
174             result => {
175             schema => 'uint*',
176             },
177             result_naked => 1,
178             };
179             sub num_occurs {
180 4     4 1 2565 my ($string, $substr) = @_;
181              
182 4 100       11 if (ref $substr eq 'Regexp') {
183 1         2 my $n = 0;
184 1         25 $n++ while $string =~ /$substr/g;
185 1         9 return $n;
186             } else {
187 3         4 my $n = 0;
188 3         72 $n++ while $string =~ /\Q$substr\E/g;
189 3         13 return $n;
190             }
191             }
192              
193             1;
194             # ABSTRACT: String utilities related to matching
195              
196             __END__