File Coverage

blib/lib/Build/Hopen/Util/NameSet.pm
Criterion Covered Total %
statement 39 40 97.5
branch 18 24 75.0
condition 2 3 66.6
subroutine 11 11 100.0
pod 6 6 100.0
total 76 84 90.4


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