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   1006 use Build::Hopen::Base;
  16         32  
  16         98  
4              
5             our $VERSION = '0.000007'; # 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 853 my $class = shift or croak 'Call as ' . __PACKAGE__ . '->new(...)';
33 36         151 my $self = bless { _strings => [], _regexps => [], _RE => undef }, $class;
34 36 100       146 $self->add(@_) if @_;
35 36         98 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 458 my $self = shift or croak 'Need an instance';
51 27 50       77 return unless @_;
52 27         58 $self->{_RE} = undef; # dirty the instance
53              
54 27         67 foreach my $arg (@_) {
55 49 100       154 if(!ref $arg) {
    100          
    100          
    50          
56 22         29 push @{$self->{_strings}}, "$arg";
  22         62  
57             } elsif(ref $arg eq 'Regexp') {
58 21         56 push @{$self->{_regexps}}, $arg;
  21         77  
59             } elsif(ref $arg eq 'ARRAY') {
60 4         13 $self->add(@$arg);
61             } elsif(ref $arg eq 'HASH') {
62 2         7 $self->add(keys %$arg);
63             } else {
64 16     16   7418 use Data::Dumper;
  16         45  
  16         3484  
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 5429 my $self = shift or croak 'Need an instance';
79 90 100       223 $self->{_RE} = $self->_build unless $self->{_RE}; # Clean
80             #say STDERR $self->{_RE};
81 90         878 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   11407 $_[0]->contains($_[1])
105 16     16   15829 };
  16         12437  
  16         152  
106              
107             =head2 strings
108              
109             Accessor for the strings in the set. Returns an arrayref.
110              
111             =cut
112              
113 2     2 1 369 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 20 sub complex { @{(shift)->{_regexps}} > 0 }
  4         26  
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   15 my $self = shift or croak 'Need an instance';
143              
144 5         11 my $strs = join '|', map { quotemeta } @{$self->{_strings}};
  14         33  
  5         15  
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     11 my $str = join '|', @{$self->{_regexps}}, ($strs || ());
  5         31  
149             # Each regexp stringifies with surrounding parens, so we
150             # don't need to add any.
151              
152 5 100       124 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__