File Coverage

blib/lib/String/Wildcard/Bash.pm
Criterion Covered Total %
statement 59 60 98.3
branch 39 46 84.7
condition 17 19 89.4
subroutine 8 8 100.0
pod 3 3 100.0
total 126 136 92.6


line stmt bran cond sub pod time code
1             package String::Wildcard::Bash;
2              
3             our $DATE = '2019-08-30'; # DATE
4             our $VERSION = '0.043'; # VERSION
5              
6 1     1   71823 use 5.010001;
  1         13  
7 1     1   5 use strict;
  1         3  
  1         35  
8 1     1   6 use warnings;
  1         2  
  1         23  
9              
10 1     1   5 use Exporter;
  1         9  
  1         248  
11             our @ISA = qw(Exporter);
12             our @EXPORT_OK = qw(
13             $RE_WILDCARD_BASH
14             contains_wildcard
15             convert_wildcard_to_sql
16             convert_wildcard_to_re
17             );
18              
19             our $re_bash_brace_element =
20             qr(
21             (?:(?:\\\\ | \\, | \\\{ | \\\} | [^\\\{,\}])*)
22             )x;
23              
24             # note: order is important here, brace encloses the other
25             our $RE_WILDCARD_BASH =
26             qr(
27             # non-escaped brace expression, with at least one comma
28             (?P
29             (?\\\\)*\{
30             (?P
31             $re_bash_brace_element(?:, $re_bash_brace_element )+
32             )
33             (?
34             )
35             |
36             # non-escaped brace expression, to catch * or ? or [...] inside so
37             # they don't go to below pattern, because bash doesn't consider them
38             # wildcards, e.g. '/{et?,us*}' expands to '/etc /usr', but '/{et?}'
39             # doesn't expand at all to /etc.
40             (?P
41             (?
42             $re_bash_brace_element
43             (?
44             )
45             |
46             (?P
47             # non-empty, non-escaped character class
48             (?
49             (?: \\\\ | \\\[ | \\\] | [^\\\[\]] )+
50             (?
51             )
52             |
53             (?P
54             # non-escaped * and ?
55             (?
56             )
57             |
58             (?P
59             # non-escaped % and ?
60             (?
61             )
62             |
63             (?P
64             [^\\\[\]\{\}*?%_]+
65             |
66             .+?
67             )
68             )ox;
69              
70             sub contains_wildcard {
71 43     43 1 16511 my $str = shift;
72              
73 43         367 while ($str =~ /$RE_WILDCARD_BASH/go) {
74 1     1   480 my %m = %+;
  1         458  
  1         697  
  85         648  
75 85 100 100     772 return 1 if $m{bash_brace} || $m{bash_class} || $m{bash_joker};
      100        
76             }
77 20         99 0;
78             }
79              
80             sub convert_wildcard_to_sql {
81 15 50   15 1 3720 my $opts = ref $_[0] eq 'HASH' ? shift : {};
82 15         31 my $str = shift;
83              
84 15         29 my @res;
85             my $p;
86 15         155 while ($str =~ /$RE_WILDCARD_BASH/g) {
87 41         288 my %m = %+;
88 41 100       201 if (defined($p = $m{bash_brace_content})) {
    100          
    100          
    100          
    100          
    50          
89 1         11 die "Cannot convert brace pattern '$p' to SQL";
90             } elsif ($p = $m{bash_joker}) {
91 7 100 100     29 if ($m{bash_joker} eq '*' || $m{bash_joker} eq '**') {
92 4         25 push @res, "%";
93             } else {
94 3         15 push @res, "_";
95             }
96             } elsif ($p = $m{sql_joker}) {
97 2         12 push @res, "\\$p";
98             } elsif (defined($p = $m{literal_brace_single_element})) {
99 1         18 die "Currently cannot convert brace literal '$p' to SQL";
100             } elsif (defined($p = $m{bash_class})) {
101 1         14 die "Currently cannot convert class pattern '$p' to SQL";
102             } elsif (defined($p = $m{literal})) {
103 29         196 push @res, $p;
104             }
105             }
106              
107 12         78 join "", @res;
108             }
109              
110             sub convert_wildcard_to_re {
111 12 100   12 1 3825 my $opts = ref $_[0] eq 'HASH' ? shift : {};
112 12         24 my $str = shift;
113              
114 12   100     45 my $opt_brace = $opts->{brace} // 1;
115 12   100     34 my $opt_dotglob = $opts->{dotglob} // 0;
116              
117 12         21 my @res;
118             my $p;
119 12         113 while ($str =~ /$RE_WILDCARD_BASH/g) {
120 24         185 my %m = %+;
121 24 100       122 if (defined($p = $m{bash_brace_content})) {
    100          
    100          
    100          
    100          
    50          
122             push @res, quotemeta($m{slashes_before_bash_brace}) if
123 2 50       7 $m{slashes_before_bash_brace};
124 2 100       7 if ($opt_brace) {
125 1         2 my @elems;
126 1         66 while ($p =~ /($re_bash_brace_element)(,|\z)/g) {
127 2         8 push @elems, $1;
128 2 100       12 last unless $2;
129             }
130             #use DD; dd \@elems;
131             push @res, "(?:", join("|", map {
132 1   33     3 convert_wildcard_to_re({
  2         17  
133             bash_brace => 0,
134             dotglob => $opt_dotglob || @res,
135             }, $_)} @elems), ")";
136             } else {
137 1         8 push @res, quotemeta($m{bash_brace});
138             }
139              
140             } elsif (defined($p = $m{bash_joker})) {
141 9 100       48 if ($p eq '?') {
    50          
    0          
142 1         7 push @res, '.';
143             } elsif ($p eq '*') {
144 8 100 100     58 push @res, $opt_dotglob || @res ? '.*' : '[^.].*';
145             } elsif ($p eq '**') {
146 0         0 push @res, '.*';
147             }
148              
149             } elsif (defined($p = $m{literal_brace_single_element})) {
150 1         8 push @res, quotemeta($p);
151             } elsif (defined($p = $m{bash_class})) {
152             # XXX no need to escape some characters?
153 1         6 push @res, $p;
154             } elsif (defined($p = $m{sql_joker})) {
155 1         5 push @res, quotemeta($p);
156             } elsif (defined($p = $m{literal})) {
157 10         74 push @res, quotemeta($p);
158             }
159             }
160              
161 12         88 join "", @res;
162             }
163              
164             1;
165             # ABSTRACT: Bash wildcard string routines
166              
167             __END__