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   5097 use strict;
  23         49  
  23         712  
4 23     23   126 use Data::Hopen::Base;
  23         42  
  23         145  
5              
6             our $VERSION = '0.000019';
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 2059 my $class = shift or croak 'Call as ' . __PACKAGE__ . '->new(...)';
34 52         192 my $self = bless { _strings => [], _regexps => [], _RE => undef }, $class;
35 52 100       232 $self->add(@_) if @_;
36 51         146 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 2039 my $self = shift or croak 'Need an instance';
52 39 100       108 return unless @_;
53 38         93 $self->{_RE} = undef; # dirty the instance
54              
55 38         112 foreach my $arg (@_) {
56 61 100       226 if(!ref $arg) {
    100          
    100          
    100          
57 24         32 push @{$self->{_strings}}, "$arg";
  24         64  
58             } elsif(ref $arg eq 'Regexp') {
59 29         51 push @{$self->{_regexps}}, $arg;
  29         101  
60             } elsif(ref $arg eq 'ARRAY') {
61 4         13 $self->add(@$arg);
62             } elsif(ref $arg eq 'HASH') {
63 3         12 $self->add(keys %$arg);
64             } else {
65 23     23   10392 use Data::Dumper;
  23         54  
  23         5586  
66 1         9 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 6884 my $self = shift or croak 'Need an instance';
80 94 100       223 $self->{_RE} = $self->_build unless $self->{_RE}; # Clean
81             #say STDERR $self->{_RE};
82 94         976 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   11310 $_[0]->contains($_[1])
106 23     23   24251 };
  23         18738  
  23         207  
107              
108             =head2 strings
109              
110             Accessor for the strings in the set. Returns an arrayref.
111              
112             =cut
113              
114 2     2 1 436 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 17 sub complex { @{(shift)->{_regexps}} > 0 }
  4         24  
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   729 my $self = shift or croak 'Need an instance';
144              
145 7         13 my @quoted_strs;
146 7 100       11 if(@{$self->{_strings}}) {
  7         22  
147             push @quoted_strs,
148 3         7 join '|', map { quotemeta } @{$self->{_strings}};
  18         44  
  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         17 my $pattern = join '|', @{$self->{_regexps}}, @quoted_strs;
  7         26  
154             # Each regexp stringifies with surrounding parens, so we
155             # don't need to add any.
156              
157 7 100       204 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__