File Coverage

lib/Data/Identifier/Wellknown.pm
Criterion Covered Total %
statement 41 147 27.8
branch 2 60 3.3
condition 2 48 4.1
subroutine 12 15 80.0
pod 2 2 100.0
total 59 272 21.6


line stmt bran cond sub pod time code
1             # Copyright (c) 2023-2025 Philipp Schafft
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: format independent identifier object
6              
7              
8             package Data::Identifier::Wellknown;
9              
10 1     1   3172 use v5.14;
  1         4  
11 1     1   4 use strict;
  1         2  
  1         37  
12 1     1   5 use warnings;
  1         1  
  1         54  
13 1     1   683 use utf8;
  1         290  
  1         5  
14              
15 1     1   29 use Carp;
  1         1  
  1         80  
16 1     1   4 use Fcntl qw(SEEK_SET);
  1         1  
  1         36  
17              
18 1     1   4 use Data::Identifier;
  1         1  
  1         7  
19 1     1   1704 use Data::Identifier::Generate;
  1         2  
  1         57  
20              
21 1     1   7 use parent 'Data::Identifier::Interface::Known';
  1         2  
  1         11  
22              
23             our $VERSION = v0.28;
24              
25             use constant {
26 1         1556 WK_UUID => '8be115d2-dc2f-4a98-91e1-a6e3075cbc31', # uuid
27             WK_SID => 'f87a38cb-fd13-4e15-866c-e49901adbec5', # small-identifier
28             WK_SNI => '039e0bb7-5dd3-40ee-a98c-596ff6cce405', # sirtx-numerical-identifier
29 1     1   123 };
  1         1  
30              
31             my %imported;
32             my %loaded = (wellknown => undef, registered => undef); # consider two loaded to begin with.
33             my $start_of_data = DATA->tell;
34              
35             foreach my $class (keys %loaded) {
36             foreach my $id (Data::Identifier->known($class)) {
37             _add_classes($id, $class);
38             }
39             }
40              
41             sub _known_provider {
42 0     0   0 my ($pkg, $class, %opts) = @_;
43 0 0       0 croak 'Unsupported options passed' if scalar(keys %opts);
44              
45 0 0       0 unless (exists $loaded{$class}) {
46 0         0 __PACKAGE__->import($class);
47             }
48              
49 0 0 0     0 if ($class eq 'wellknown' || $class eq 'registered' || $class eq ':all') {
      0        
50 0         0 goto &Data::Identifier::_known_provider;
51             } else {
52 0         0 return ($imported{$class}, rawtype => 'Data::Identifier');
53             }
54             }
55              
56             sub import {
57 1     1   17 my ($pkg, @args) = @_;
58 1         16 my $id_type = Data::Identifier->new(wellknown => 'uuid');
59 1         7 my $default_class;
60             my %generator;
61 1         0 my $namespace;
62 1         0 my %found;
63 1         0 my @extra_classes;
64              
65 1 50       4 return if exists $loaded{':all'}; # if we done :all we ... did all!
66              
67 1         4 @args = grep {!exists($loaded{$_})} @args;
  0         0  
68              
69 1 50       23 return unless scalar @args;
70              
71 0         0 DATA->seek($start_of_data, SEEK_SET);
72              
73 0         0 while (my $line = ) {
74 0         0 my ($classes, $id, $displayname, $special);
75 0         0 my @classes;
76 0         0 my $found;
77              
78 0         0 $line =~ s/\s{2,}%.*$//;
79 0         0 $line =~ s/^\s*%.*$//;
80 0         0 $line =~ s/\s+$//;
81 0         0 $line =~ s/^\s+//;
82 0         0 $line =~ s/\r?\n$//;
83              
84 0 0       0 next if $line eq '';
85              
86 0 0       0 if ($line =~ /^\$/) {
87 0         0 my ($command, $arg) = split(/\s+/, $line, 2);
88            
89 0 0       0 if ($command eq '$type') {
    0          
    0          
    0          
    0          
    0          
90 0 0       0 if ($arg =~ /^(.+)=(.+)$/) {
91 0         0 $id_type = Data::Identifier->new($1, $2);
92             } else {
93 0         0 $id_type = Data::Identifier->new(wellknown => $arg);
94             }
95 0         0 $namespace = undef;
96             } elsif ($command eq '$class') {
97 0         0 $default_class = $arg;
98 0         0 @extra_classes = ();
99             } elsif ($command eq '$extra_classes') {
100 0         0 @extra_classes = split(/,/, $arg);
101             } elsif ($command eq '$generator') {
102 0         0 %generator = split(/[,=]/, $arg);
103 0         0 $id_type = undef;
104 0         0 $namespace = undef;
105             } elsif ($command eq '$namespace') {
106 0         0 $namespace = $arg;
107             } elsif ($command eq '$end') {
108 0         0 last;
109             } else {
110 0         0 croak 'BUG';
111             }
112 0         0 next;
113             }
114              
115 0         0 ($classes, $id, $displayname, $special) = split(/\s{2,}/, $line, 4);
116 0 0       0 if ($classes eq '.') {
117 0         0 @classes = ($default_class);
118             } else {
119 0         0 @classes = split(',', $classes);
120             }
121              
122 0         0 push(@classes, @extra_classes);
123              
124 0         0 foreach my $class_a (@classes) {
125 0         0 foreach my $class_b (@args) {
126 0 0 0     0 if ($class_a eq $class_b || $class_b eq ':all') {
127 0         0 $found = 1;
128 0         0 last;
129             }
130             }
131             }
132 0 0       0 next unless $found;
133              
134             {
135 0         0 my $identifier;
  0         0  
136              
137 0 0       0 if (defined $id_type) {
138 0 0 0     0 $identifier = Data::Identifier->new(
139             $id_type => $id,
140             (defined($displayname) && $displayname ne '.') ? (displayname => $displayname) : (),
141             );
142             } else {
143 0 0 0     0 $identifier = Data::Identifier::Generate->generic(
144             %generator,
145             request => $id,
146             (defined($displayname) && $displayname ne '.') ? (displayname => $displayname) : (),
147             );
148             }
149              
150 0 0       0 if (defined $namespace) {
151 0         0 my $uuid;
152              
153 0 0       0 if ($namespace =~ /^(.+),lc$/) {
154 0         0 $uuid = Data::Identifier::Generate->_uuid_v5($1, lc($id));
155             } else {
156 0         0 $uuid = Data::Identifier::Generate->_uuid_v5($namespace, $id);
157             }
158 0   0     0 $identifier->{id_cache} //= {};
159 0   0     0 $identifier->{id_cache}->{WK_UUID()} //= $uuid;
160             }
161              
162 0 0 0     0 if (defined($special) && length($special)) {
163 0         0 my %special = split(/[,=]/, $special);
164              
165 0 0       0 if (defined $special{sid}) {
166 0   0     0 $identifier->{id_cache} //= {};
167 0   0     0 $identifier->{id_cache}->{WK_SID()} //= $special{sid};
168             }
169              
170 0 0       0 if (defined $special{sni}) {
171 0   0     0 $identifier->{id_cache} //= {};
172 0   0     0 $identifier->{id_cache}->{WK_SNI()} //= $special{sni};
173             }
174             }
175              
176 0         0 $identifier->register;
177              
178 0         0 foreach my $class (@classes) {
179 0         0 $found{$class} = undef;
180 0   0     0 $imported{$class} //= [];
181 0         0 push(@{$imported{$class}}, $identifier);
  0         0  
182             }
183              
184 0         0 _add_classes($identifier, @classes);
185             }
186             }
187              
188             # deduplicate:
189 0         0 foreach my $class (keys %found) {
190 0         0 my %tmp = map {$_ => $_} @{$imported{$class}};
  0         0  
  0         0  
191 0         0 @{$imported{$class}} = values %tmp;
  0         0  
192             }
193              
194             # Mark classes loaded that we found data for.
195 0         0 foreach my $class (@args) {
196 0 0 0     0 if (exists($found{$class}) || $class eq ':all') {
197 0         0 $loaded{$class} = undef;
198             } else {
199 0         0 croak 'Unsupported class: '.$class;
200             }
201             }
202             }
203              
204              
205             sub classes_of {
206 0     0 1 0 my ($pkg, $identifier) = @_;
207 0         0 $identifier = Data::Identifier->new(from => $identifier);
208              
209 0   0     0 return keys %{$identifier->userdata(__PACKAGE__, 'classes') // {}};
  0         0  
210             }
211              
212              
213             sub announce {
214 0     0 1 0 my ($pkg, $type, $regs, @opts) = @_;
215              
216 0 0       0 croak 'Stray options passed' if scalar @opts;
217              
218 0         0 $type = Data::Identifier->new(from => $type); # Force to Data::Identifier
219 0         0 $type->register; # Ensure it is registered
220              
221 0 0       0 if (ref($regs) eq 'HASH') {
    0          
222 0         0 $type = $type->uuid; # Convert to UUID for cache usage
223 0         0 foreach my $key (keys %{$regs}) {
  0         0  
224 0         0 my $identifier = Data::Identifier->new(from => $regs->{$key});
225 0   0     0 $identifier->{id_cache} //= {};
226 0   0     0 $identifier->{id_cache}->{$type} //= $key;
227 0         0 $identifier->register;
228             }
229             } elsif (ref($regs) eq 'ARRAY') {
230 0         0 foreach my $key (@{$regs}) {
  0         0  
231 0 0       0 if (ref $key) {
232 0         0 Data::Identifier->new(from => $key)->register;
233             } else {
234 0         0 Data::Identifier->new($type => $key)->register;
235             }
236             }
237             } else {
238 0         0 croak 'Unsupported data type';
239             }
240             }
241              
242             # ---- Private helpers ----
243              
244             sub _add_classes {
245 122     122   153 my ($identifier, @classes) = @_;
246 122   66     151 my $set = $identifier->userdata(__PACKAGE__, 'classes') // $identifier->userdata(__PACKAGE__, 'classes' => {});
247 122         254 $set->{$_} = undef foreach @classes, ':all', 'registered';
248             }
249              
250             1;
251              
252             =pod
253              
254             =encoding UTF-8
255              
256             =head1 NAME
257              
258             Data::Identifier::Wellknown - format independent identifier object
259              
260             =head1 VERSION
261              
262             version v0.28
263              
264             =head1 SYNOPSIS
265              
266             use Data::Identifier::Wellknown qw(classes...);
267             e.g.:
268             use Data::Identifier::Wellknown ':all';
269              
270             (experimental since v0.07)
271              
272             This package provides a simple list of well known identifiers.
273             Classes are loaded on demand. However for speedy lookup classes can
274             be imported (given via C arguments).
275              
276             If a class is already loaded, it is not reloaded.
277             If a program knows the classes it will use early it makes sense to
278             include this module in the main program (or root module) before other modules that make
279             use of this module are used with all the used classes listed.
280             This improves speed as it will reduce the read of the full list to a single pass.
281             In contrast if every use will only list a single class that is not yet loaded loading will be most in-efficient.
282              
283             B
284             This is an B package.
285             It's methods and classes are not stable.
286             But the package itself and the class C<:all> is.
287              
288             This package implements L.
289              
290             =head1 METHODS
291              
292             =head2 classes_of
293              
294             my @classes = Data::Identifier::Wellknown->classes_of($identifier);
295              
296             (experimental since v0.07)
297              
298             Returns the classes the identifier is known for.
299             C<$identifier> is parsed as per C of L.
300              
301             B
302             This module does not guarantee any specific order of the returned list.
303              
304             B
305             Classes may not be included in returned list unless they (or C<:all>) have been
306             imported before.
307              
308             B
309             This is an B method. It may be changed, renamed, or removed without notice.
310              
311             =head2 announce
312              
313             Data::Identifier::Wellknown->announce($type => $data);
314              
315             (experimental since v0.26)
316              
317             This method can be used to announce additional well known identifiers.
318             It is mostly used by other modules to register identifiers for a given type related to those modules
319             and alias them to UUIDs.
320             This is mostly useful if those identifiers are mapped to UUIDs via a register (in contrast to a generator).
321              
322             The type is passed as C<$type> which is parsed as per C of L.
323             It must resolve to an identifier with a defined UUID.
324             This module might make restrictions on the type identifiers can be registered for.
325              
326             If C<$data> is a hashref the keys are understood as the identifiers of the type given via C<$type>
327             and the values are the corresponding identifiers (parsed as per C of L) to map to.
328              
329             If C<$data> is an arrayref the values are understood as the identifiers.
330             L objects are created as needed and registered.
331              
332             C<$type> and all identifiers updated by this method will be registered as per L.
333              
334             =head1 AUTHOR
335              
336             Philipp Schafft
337              
338             =head1 COPYRIGHT AND LICENSE
339              
340             This software is Copyright (c) 2023-2025 by Philipp Schafft .
341              
342             This is free software, licensed under:
343              
344             The Artistic License 2.0 (GPL Compatible)
345              
346             =cut
347              
348             __DATA__