File Coverage

blib/lib/Data/Hopen/Util/NameSet.pm
Criterion Covered Total %
statement 46 46 100.0
branch 26 26 100.0
condition n/a
subroutine 12 12 100.0
pod 6 6 100.0
total 90 90 100.0


line stmt bran cond sub pod time code
1             # Data::Hopen::Util::NameSet - set of strings and regexps
2             package Data::Hopen::Util::NameSet;
3 23     23   4754 use strict;
  23         54  
  23         1056  
4 23     23   139 use Data::Hopen::Base;
  23         50  
  23         147  
5              
6             our $VERSION = '0.000017';
7              
8             # Docs {{{1
9              
10             =head1 NAME
11              
12             Data::Hopen::Util::NameSet - set of names (strings or regexps)
13              
14             =head1 SYNOPSIS
15              
16             NameSet stores strings and regexps, and can quickly tell you whether
17             a given string matches one of the stored strings or regexps.
18              
19             =cut
20              
21             # }}}1
22              
23             =head1 FUNCTIONS
24              
25             =head2 new
26              
27             Create a new instance. Usage: C<< Data::Hopen::Util::Nameset->new(...) >>.
28             The parameters are as L.
29              
30             =cut
31              
32             sub new {
33 53 100   53 1 2188 my $class = shift or croak 'Call as ' . __PACKAGE__ . '->new(...)';
34 52         198 my $self = bless { _strings => [], _regexps => [], _RE => undef }, $class;
35 52 100       211 $self->add(@_) if @_;
36 51         170 return $self;
37             } #new()
38              
39             =head2 add
40              
41             Add one or more strings or regexps to the NameSet. Usage:
42              
43             $instance->add(x1, x2, ...)
44              
45             where each C can be a scalar, regexp, arrayref (processed recursively)
46             or hashref (the keys are added and the values are ignored).
47              
48             =cut
49              
50             sub add {
51 40 100   40 1 2115 my $self = shift or croak 'Need an instance';
52 39 100       110 return unless @_;
53 38         86 $self->{_RE} = undef; # dirty the instance
54              
55 38         105 foreach my $arg (@_) {
56 61 100       285 if(!ref $arg) {
    100          
    100          
    100          
57 24         31 push @{$self->{_strings}}, "$arg";
  24         62  
58             } elsif(ref $arg eq 'Regexp') {
59 29         53 push @{$self->{_regexps}}, $arg;
  29         102  
60             } elsif(ref $arg eq 'ARRAY') {
61 4         11 $self->add(@$arg);
62             } elsif(ref $arg eq 'HASH') {
63 3         10 $self->add(keys %$arg);
64             } else {
65 23     23   10363 use Data::Dumper;
  23         56  
  23         5300  
66 1         7 croak "I don't know how to handle this: " . Dumper($arg)
67             }
68             }
69             } #add()
70              
71             =head2 contains
72              
73             Return truthy if the NameSet contains the argument. Usage:
74             C<< $set->contains('foo') >>.
75              
76             =cut
77              
78             sub contains {
79 95 100   95 1 6817 my $self = shift or croak 'Need an instance';
80 94 100       232 $self->{_RE} = $self->_build unless $self->{_RE}; # Clean
81             #say STDERR $self->{_RE};
82 94         918 return shift =~ $self->{_RE};
83             } #contains()
84              
85             =head2 smartmatch overload
86              
87             For convenience, C<< 'foo' ~~ $nameset >> invokes
88             C<< $nameset->contains('foo') >>. This is inspired by the Raku behaviour,
89             in which C<< $x ~~ $y >> calls C<< $y.ACCEPTS($x) >>
90              
91             NOTE: C<< $nameset ~~ 'foo' >> (object first) is officially not supported by
92             this module. This form is possible in stable perls at least through 5.26.
93             However, the changes (since reverted) in 5.27.7 would not have supported this
94             form. See
95             L.
96             However, as far as I can tell, even 5.27.7 would have supported the
97             C<< 'foo' ~~ $nameset >> form.
98              
99             =cut
100              
101             use overload
102             fallback => 1,
103             '~~' => sub {
104             #my ($self, $other, $swap) = @_;
105 69     69   11177 $_[0]->contains($_[1])
106 23     23   23525 };
  23         19051  
  23         249  
107              
108             =head2 strings
109              
110             Accessor for the strings in the set. Returns an arrayref.
111              
112             =cut
113              
114 2     2 1 359 sub strings { (shift)->{_strings} }
115              
116             =head2 regexps
117              
118             Accessor for the regexps in the set. Returns an arrayref.
119              
120             =cut
121              
122 2     2 1 10 sub regexps { (shift)->{_regexps} }
123              
124             =head2 complex
125              
126             Returns truthy if the nameset has any regular expressions.
127              
128             =cut
129              
130 4     4 1 18 sub complex { @{(shift)->{_regexps}} > 0 }
  4         19  
131              
132             =head2 _build
133              
134             (Internal) Build a regex from all the strings and regexps in the set.
135             Returns the new regexp --- does not mutate $self.
136              
137             In the current implementation, strings are matched case-sensitively.
138             Regexps are matched with whatever flags they were compiled with.
139              
140             =cut
141              
142             sub _build {
143 8 100   8   864 my $self = shift or croak 'Need an instance';
144              
145 7         11 my @quoted_strs;
146 7 100       12 if(@{$self->{_strings}}) {
  7         23  
147             push @quoted_strs,
148 3         7 join '|', map { quotemeta } @{$self->{_strings}};
  18         41  
  3         7  
149             # TODO should I be using qr/\Q$_\E/ instead, since quotemeta
150             # isn't quite right on 5.14? Or should I be using 5.16+?
151             }
152              
153 7         13 my $pattern = join '|', @{$self->{_regexps}}, @quoted_strs;
  7         23  
154             # Each regexp stringifies with surrounding parens, so we
155             # don't need to add any.
156              
157 7 100       165 return $pattern ? qr/\A(?:$pattern)\z/ : qr/(*FAIL)/;
158             # If $pattern is empty, the nameset is empty (`(*FAIL)`). Without the
159             # ?:, qr// would match anything, when we want to match nothing.
160             } #_build()
161              
162             1;
163             __END__