File Coverage

lib/Data/Identifier/Util.pm
Criterion Covered Total %
statement 209 310 67.4
branch 114 218 52.2
condition 31 97 31.9
subroutine 21 27 77.7
pod 12 14 85.7
total 387 666 58.1


line stmt bran cond sub pod time code
1             # Copyright (c) 2023-2026 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::Util;
9              
10 3     3   214927 use v5.14;
  3         9  
11 3     3   12 use strict;
  3         4  
  3         57  
12 3     3   9 use warnings;
  3         4  
  3         145  
13              
14 3     3   9 use parent qw(Data::Identifier::Interface::Userdata Data::Identifier::Interface::Subobjects);
  3         5  
  3         20  
15              
16 3     3   126 use Carp;
  3         5  
  3         128  
17              
18 3     3   510 use Data::Identifier;
  3         4  
  3         15  
19 3     3   578 use Data::Identifier::Generate;
  3         5  
  3         187  
20              
21             our $VERSION = v0.31;
22              
23             use constant {
24 3         14 BOOL_TRUE => Data::Identifier->new(uuid => 'eb50b3dc-28be-4cfc-a9ea-bd7cee73aed5')->register,
25             BOOL_FALSE => Data::Identifier->new(uuid => '6d34d4a1-8fbc-4e22-b3e0-d50f43d97cb1')->register,
26 3     3   16 };
  3         3  
27              
28             _update_tag(BOOL_TRUE, 46, 190, 'true');
29             _update_tag(BOOL_FALSE, 45, 189, 'false');
30              
31             my @truths = (BOOL_TRUE, qw(https://schema.org/True http://schema.org/True), Data::Identifier->new(wd => 'Q16751793'));
32             my @falses = (BOOL_FALSE, qw(https://schema.org/False http://schema.org/False), Data::Identifier->new(wd => 'Q5432619'));
33              
34             foreach my $id (@truths, @falses) {
35             $id = Data::Identifier->new(from => $id)->register;
36             }
37              
38             my $_DEFAULT_INSTANCE = __PACKAGE__->new;
39              
40             my %_4plus12_prefix = (
41             sni => (0<<15)|(0<<14),
42             sid => (0<<15)|(1<<14),
43             hdi => (1<<15)|(0<<14),
44             udi => (1<<15)|(1<<14),
45             );
46              
47             my $_logical = '5e80c7b7-215e-4154-b310-a5387045c336';
48              
49             my %_raes_to_raen = (
50             NONE => 0,
51             NOENT => 2,
52             NOSYS => 6,
53             NOTSUP => 7,
54             NOMEM => 12,
55             INVAL => 13,
56             FAULT => 18,
57             IO => 19,
58             NODATA => 25,
59             NOSPC => 38,
60             TYPEMM => 39,
61             RO => 45,
62             ILLSEQ => 56,
63             BADEXEC => 79,
64             BADFH => 83,
65             );
66             my %_logicals_to_sni = (
67             sni => 10,
68             sid => 115,
69             raen => 116,
70             chat0w => 118,
71             uuid => 119,
72             uri => 121,
73             asciicp => 122,
74             oid => 120,
75             wd => 123,
76             logical => 129,
77             false => 189,
78             true => 190,
79             );
80             my %_logicals_to_sid = (
81             asi => 1,
82             tagname => 3,
83             SEEK_SET => 34,
84             SEEK_CUR => 35,
85             SEEK_END => 36,
86             backwards => 43,
87             forwards => 44,
88             black => 61,
89             white => 62,
90             grey => 63,
91             red => 119,
92             green => 120,
93             blue => 121,
94             cyan => 122,
95             magenta => 123,
96             yellow => 124,
97             orange => 125,
98             gtin => 160,
99             left => 192,
100             right => 193,
101             up => 194,
102             down => 195,
103             north => 208,
104             east => 209,
105             south => 210,
106             west => 211,
107             );
108              
109             my %_wk = (
110             bunit_ns => {uuid => 'e8e9846a-37ec-42fd-8e89-d15f5467aa9c', displayname => 'unit-namespace'},
111             bunit_gen => {uuid => 'b1620795-b29a-4aea-ba46-371b187d0a4b', displayname => 'unit-generator'},
112             dunit_ns => {uuid => 'da8a7fe4-935c-4bf7-9bd1-aaf8fc39305b', displayname => 'derived-unit-namespace'},
113             dunit_gen => {uuid => 'c3446cff-672b-4247-b62c-755a295ee15f', displayname => 'derived-unit-generator'},
114             #x => {uuid => '', displayname => ''},
115             );
116              
117             foreach my $value (values %_wk) {
118             my $uuid = delete $value->{uuid};
119             $value = Data::Identifier->new(uuid => $uuid, %{$value})->register;
120             }
121              
122             my %_base_units = (
123             map {
124             $_->{id} = Data::Identifier::Generate->generic(
125             namespace => $_wk{bunit_ns},
126             generator => $_wk{bunit_gen},
127             style => 'name-based',
128             request => $_->{symbol},
129             displayname => $_->{name},
130             )->register;
131             $_->{symbol} => $_
132             } (
133             {name => 'second', symbol => 's', dimension => 'T', quantity => 'time', variable => [qw(t)]},
134             {name => 'metre', symbol => 'm', dimension => 'L', quantity => 'length', variable => [qw(l x r)]},
135             {name => 'kilogram', symbol => 'kg', dimension => 'M', quantity => 'mass', variable => [qw(m)]},
136             {name => 'ampere', symbol => 'A', dimension => 'I', quantity => 'electric current', variable => [qw(I i)]},
137             {name => 'kelvin', symbol => 'K', dimension => "\N{U+0398}", quantity => 'thermodynamic temperature', variable => [qw(T)]},
138             {name => 'mole', symbol => 'mol', dimension => 'N', quantity => 'amount of substance', variable => [qw(n)]},
139             {name => 'candela', symbol => 'cd', dimension => 'J', quantity => 'luminous intensity', variable => [qw(Iv)]},
140             )
141             );
142              
143             my %_number_units = map { $_ => Data::Identifier::Generate->integer($_)->register } (2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73); # primes
144              
145             my %_composite_unit_elements = (
146             Hz => {s => -1},
147             N => {s => -2, m => 1, kg => 1},
148             Pa => {s => -2, m => -1, kg => 1},
149             J => {s => -2, m => 2, kg => 1},
150             W => {s => -3, m => 2, kg => 1},
151             C => {s => 1, A => 1},
152             V => {s => -3, m => 2, kg => 1, A => -1},
153             F => {s => 4, m => -2, kg => -1, A => 2},
154             "\N{U+03A9}" => {s => -3, m => 2, kg => 1, A => -2},
155             "\N{U+2126}" => {s => -3, m => 2, kg => 1, A => -2}, # alias
156             ohm => {s => -3, m => 2, kg => 1, A => -2}, # alias
157             S => {s => 3, m => -2, kg => -1, A => 2},
158             Wb => {s => -2, m => 2, kg => 1, A => -1},
159             T => {s => -2, kg => 1, A => -1},
160             H => {s => -2, m => 2, kg => 1, A => -2},
161             kat => {s => -1, mol => 1},
162             10 => {2 => 1, 5 => 1},
163             12 => {2 => 2, 3 => 1},
164             24 => {2 => 3, 3 => 1},
165             28 => {2 => 2, 7 => 1},
166             30 => {2 => 1, 3 => 1, 5 => 1},
167             60 => {2 => 2, 3 => 1, 5 => 1},
168             365 => { 5 => 1, 73 => 1},
169             366 => {2 => 1, 3 => 1, 61 => 1},
170             3600 => {2 => 4, 3 => 2, 5 => 2},
171             86400 => {2 => 7, 3 => 3, 5 => 2},
172             );
173              
174             my %_component_to_name = (
175             # Units:
176             (map {$_base_units{$_}{id}->uuid => $_} keys %_base_units),
177             # Numbers:
178             (map {$_number_units{$_}->uuid => $_} keys %_number_units),
179             );
180              
181             my %_si_prefix = (
182             quetta => 30, Q => 30,
183             ronna => 27, R => 27,
184             yotta => 24, Y => 24,
185             zetta => 21, Z => 21,
186             exa => 18, E => 18,
187             peta => 15, P => 15,
188             tera => 12, T => 12,
189             giga => 9, G => 9,
190             mega => 6, M => 6,
191             kilo => 3, k => 3,
192             hecto => 2, h => 2,
193             deca => 1, da => 1,
194             deci => -1, d => -1,
195             centi => -2, c => -2,
196             milli => -3, m => -3,
197 3     3   20 micro => -6, "\N{GREEK SMALL LETTER MU}" => -6,
  3         4  
  3         18  
198             nano => -9, n => -9,
199             pico => -12, p => -12,
200             femto => -15, f => -15,
201             atto => -18, a => -18,
202             zepto => -21, z => -21,
203             yocto => -24, y => -24,
204             ronto => -27, r => -27,
205             quecto => -30, q => -30,
206             );
207              
208              
209             sub new {
210 4     4 1 309 my ($pkg, @opts) = @_;
211              
212 4 50       12 croak 'Stray options passed' if scalar @opts;
213              
214 4         8 return bless {}, $pkg;
215             }
216              
217              
218             #@returns Data::Identifier
219             sub from_bool {
220 0     0 1 0 my ($self, $bool, @opts) = _normalise_args(@_);
221              
222 0 0       0 croak 'Stray options passed' if scalar @opts;
223              
224 0 0       0 return $bool ? BOOL_TRUE : BOOL_FALSE;
225             }
226              
227              
228             sub is_true {
229 0     0 1 0 my ($self, $identifier, @opts) = _normalise_args(@_);
230              
231 0 0       0 croak 'Stray options passed' if scalar @opts;
232              
233 0         0 foreach my $ref (@truths) {
234 0 0       0 return !!1 if $ref->eq($identifier);
235             }
236              
237 0         0 return !!0;
238             }
239              
240              
241             sub is_false {
242 0     0 1 0 my ($self, $identifier, @opts) = _normalise_args(@_);
243              
244 0 0       0 croak 'Stray options passed' if scalar @opts;
245              
246 0         0 foreach my $ref (@falses) {
247 0 0       0 return !!1 if $ref->eq($identifier);
248             }
249              
250 0         0 return !!0;
251             }
252              
253              
254             sub pack {
255 16     16 1 2081 my ($self, $template, $identifier, @opts) = _normalise_args(@_);
256 16         21 my $pack_template;
257             my $v;
258              
259 16 50       33 croak 'Stray options passed' if scalar @opts;
260              
261 16   50     37 $template //= '';
262 16 50       13 $identifier = Data::Identifier->new(from => $identifier) unless eval {$identifier->isa('Data::Identifier')};
  16         40  
263              
264 16 100       87 if ($template =~ /^(sid|sni|hdi|udi)([1-9][0-9]*)$/) {
    50          
    100          
    100          
    100          
    50          
265 6         11 my $bits = int($2);
266              
267 6         17 $v = $identifier->as(Data::Identifier->new(wellknown => $1), no_defaults => 1);
268              
269 6 100       14 if ($bits == 8) {
    100          
    50          
270 2         4 $pack_template = 'C';
271             } elsif ($bits == 16) {
272 2         3 $pack_template = 'n';
273             } elsif ($bits == 32) {
274 2         3 $pack_template = 'N';
275             } else {
276 0         0 croak 'Invalid width: '.$bits;
277             }
278             } elsif ($template eq '4+12') {
279 0         0 my $prefix;
280              
281 0         0 foreach my $type (qw(sid sni hdi udi)) {
282 0         0 $v = $identifier->as(Data::Identifier->new(wellknown => $type), no_defaults => 1, default => undef);
283 0 0       0 if (defined $v) {
284 0 0 0     0 if ($v < 0 || $v > 0x0FFF) {
285 0         0 next;
286             }
287 0         0 $v |= $_4plus12_prefix{$type};
288 0         0 $pack_template = 'n';
289 0         0 last;
290             }
291             }
292             } elsif ($template eq 'uuid128') {
293 1         3 return pack('H*', $identifier->uuid(no_defaults => 1) =~ tr/-//dr);
294             } elsif ($template eq 'uuidhexdash') {
295 1         4 return $identifier->uuid(no_defaults => 1);
296             } elsif ($template eq 'uuidHEXDASH') {
297 1         3 return $identifier->uuid(no_defaults => 1) =~ tr/a-f/A-F/r;
298             } elsif ($template eq 'Data::Identifier') {
299 7         37 return $identifier;
300             }
301              
302 6 50 33     14 if (defined($v) && defined($pack_template)) {
303 6         6 my ($min, $max);
304              
305 6 100       12 if ($pack_template eq 'C') {
    100          
    50          
306 2         2 ($min, $max) = (0, 0xFF);
307             } elsif ($pack_template eq 'n') {
308 2         3 ($min, $max) = (0, 0xFFFF);
309             } elsif ($pack_template eq 'N') {
310 2         2 ($min, $max) = (0, 0xFFFF_FFFF);
311             }
312              
313 6 50 33     27 if ((defined($min) && $v < $min) || (defined($max) && $v > $max)) {
      33        
      33        
314 0         0 croak 'Identifier not in range for '.$template.': '.$v;
315             }
316              
317 6         37 return pack($pack_template, $v);
318             }
319              
320 0         0 croak 'Unknown template: '.$template;
321             }
322              
323              
324             #@returns Data::Identifier
325             sub unpack {
326 9     9 1 21 my ($self, $template, $data, @opts) = _normalise_args(@_);
327 9         13 my $pack_template;
328             my $type;
329              
330 9 50       14 croak 'Stray options passed' if scalar @opts;
331              
332 9 100       53 if ($template =~ /^(sid|sni|hdi|udi)([1-9][0-9]*)$/) {
    50          
    100          
    100          
    50          
    0          
333 6         13 my $bits = int($2);
334 6         10 $type = $1;
335              
336 6 100       12 if ($bits == 8) {
    100          
    50          
337 2         3 $pack_template = 'C';
338             } elsif ($bits == 16) {
339 2         3 $pack_template = 'n';
340             } elsif ($bits == 32) {
341 2         3 $pack_template = 'N';
342             } else {
343 0         0 croak 'Invalid width: '.$bits;
344             }
345             } elsif ($template eq '4+12') {
346 0         0 my $v;
347             my $prefix;
348              
349 0 0       0 croak 'Input has bad length, expected 2 bytes, got '.length($data) unless length($data) == 2;
350              
351 0         0 $v = unpack('n', $data);
352 0         0 $prefix = $v & 0xF000;
353 0         0 $v = $v & 0x0FFF;
354              
355 0         0 foreach my $key (keys %_4plus12_prefix) {
356 0 0       0 if ($prefix == $_4plus12_prefix{$key}) {
357 0         0 return Data::Identifier->new($key => $v);
358             }
359             }
360              
361 0         0 croak sprintf('Invalid/unknown prefix: 0x%04x', $prefix);
362             } elsif ($template eq 'uuid128') {
363 1 50       2 croak 'Input has bad length, expected 16 bytes, got '.length($data) unless length($data) == 16;
364 1         9 return Data::Identifier->new(uuid => join('-', unpack('H8H4H4H4H12', $data)));
365             } elsif ($template eq 'uuidhexdash') {
366 1         4 return Data::Identifier->new(uuid => $data);
367             } elsif ($template eq 'uuidHEXDASH') {
368 1         4 return Data::Identifier->new(uuid => $data);
369             } elsif ($template eq 'Data::Identifier') {
370             # We don't care too much here if it is actually a Data::Identifier or just any other supported type.
371             # Data::Identifier will do the right thing anyway.
372 0         0 return Data::Identifier->new(from => $data);
373             }
374              
375 6 50 33     15 if (defined($type) && defined($pack_template)) {
376 6         7 my $len = length($data);
377 6         5 my $exp;
378              
379 6 100       12 if ($pack_template eq 'C') {
    100          
    50          
380 2         3 $exp = 1;
381             } elsif ($pack_template eq 'n') {
382 2         2 $exp = 2;
383             } elsif ($pack_template eq 'N') {
384 2         2 $exp = 4;
385             }
386              
387 6 50       12 croak 'Input has bad length, expected '.$exp.' bytes, got '.$len unless $len == $exp;
388              
389 6         25 return Data::Identifier->new($type => unpack($pack_template, $data));
390             }
391              
392 0         0 croak 'Unknown template: '.$template;
393             }
394              
395              
396             #@returns Data::Identifier
397             sub parse_sirtx {
398 16     16 1 2364 my ($self, $data, @opts) = _normalise_args(@_);
399              
400 16 50       25 croak 'Stray options passed' if scalar @opts;
401              
402 16         26 $self->_load_well_known;
403              
404 16         67 $data =~ s/^\[(.+)\]$/$1/;
405              
406             # Experimental:
407 16 100       37 if (my ($d, $v) = $data =~ /^(\[.+?\]):(.+)$/) {
408 1         4 $d = $self->parse_sirtx($d);
409 1         3 $v =~ s/^\[(.+)\]$/$1/;
410 1         4 return Data::Identifier->new($d => $v);
411             }
412              
413 15 100       86 if ($data =~ /^'([0-9]*)$/) {
    50          
    100          
    100          
    50          
    50          
    100          
    100          
    50          
    100          
    100          
    50          
    50          
414 2   50     17 my $num = int($1 || '0');
415 2         13 require Data::Identifier::Generate;
416 2         14 return Data::Identifier::Generate->integer($num);
417             } elsif ($data =~ /^\/([0-9]+)$/) {
418 0         0 return Data::Identifier->new('d73b6550-5309-46ad-acc9-865c9261065b' => int($1));
419             } elsif ($data =~ /^(sid|sni):([0-9]+)$/) {
420 4         16 return Data::Identifier->new($1 => int($2));
421             } elsif ($data =~ /^uuid:([0-9a-fA-F-]+)$/) {
422 2         7 return Data::Identifier->new(uuid => $1);
423             } elsif ($data =~ /^wd:([QPL][1-9][0-9]*)$/) {
424 0         0 return Data::Identifier->new(wd => $1);
425             } elsif ($data =~ /^~([0-9]+)$/) {
426 0         0 return Data::Identifier->new(hdi => int($1));
427             } elsif ($data =~ /^raen:([0-9]+)$/) {
428 1         6 return Data::Identifier->new('2bffc55d-7380-454e-bd53-c5acd525d692' => int($1));
429             } elsif ($data =~ /^chat0w:([0-9]+)$/) {
430 1         6 return Data::Identifier->new('2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a' => int($1));
431             } elsif ($data =~ /^asciicp:([0-9]+)$/) {
432 0         0 require Data::Identifier::Generate;
433 0         0 return Data::Identifier::Generate->unicode_character(ascii => int($1));
434             } elsif ($data =~ /^raes:(.+)/) {
435 1 50       4 if (defined(my $raen = $_raes_to_raen{$1})) {
436 1         4 return Data::Identifier->new('2bffc55d-7380-454e-bd53-c5acd525d692' => $raen);
437             }
438             } elsif (defined $_logicals_to_sni{$data}) {
439 2         7 return Data::Identifier->new(sni => $_logicals_to_sni{$data});
440             } elsif (defined $_logicals_to_sid{$data}) {
441 0         0 return Data::Identifier->new(sid => $_logicals_to_sid{$data});
442             } elsif ($data =~ /^logical:(.+)$/) {
443 2         3 $data = $1;
444 2 50       5 if (defined $_logicals_to_sni{$data}) {
    0          
445 2         6 return Data::Identifier->new(sni => $_logicals_to_sni{$data});
446             } elsif (defined $_logicals_to_sid{$data}) {
447 0         0 return Data::Identifier->new(sid => $_logicals_to_sid{$data});
448             }
449             }
450              
451 0         0 croak 'Unsupported/invalid SIRTX identifier';
452             }
453              
454              
455             sub render_sirtx {
456 0     0 1 0 my ($self, $identifier, @opts) = _normalise_args(@_);
457 0         0 state $map = [
458             [sid => Data::Identifier->new(wellknown => 'sid')->register],
459             [sni => Data::Identifier->new(wellknown => 'sni')->register],
460             [wd => Data::Identifier->new(wellknown => 'wd')->register],
461             ['/' => Data::Identifier->new(uuid => 'd73b6550-5309-46ad-acc9-865c9261065b')->register],
462             [raen => Data::Identifier->new(uuid => '2bffc55d-7380-454e-bd53-c5acd525d692')->register],
463             [chat0w => Data::Identifier->new(uuid => '2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a')->register],
464             ['~' => Data::Identifier->new(wellknown => 'hdi')->register],
465             ];
466              
467 0 0       0 croak 'Stray options passed' if scalar @opts;
468              
469 0         0 $identifier = Data::Identifier->new(from => $identifier);
470              
471 0 0 0     0 if (defined(my Data::Identifier $generator = $identifier->generator(default => undef)) && defined(my $req = $identifier->request(default => undef))) {
472 0 0 0     0 if ($generator->eq('53863a15-68d4-448d-bd69-a9b19289a191')) {
    0          
473 0         0 return sprintf('\'%u', $req);
474             } elsif ($generator->eq('d74f8c35-bcb8-465c-9a77-01010e8ed25c') && $req =~ /^[Uu]\+([0-9a-fA-F]{4,6})$/) {
475 0         0 my $cp = hex $1;
476 0 0       0 if ($cp < 0x80) {
477 0         0 return sprintf('asciicp:%u', $cp);
478             }
479             }
480             }
481              
482 0         0 foreach my $ent (@{$map}) {
  0         0  
483 0   0     0 my $v = $identifier->as($ent->[1], no_defaults => 1, default => undef) // next;
484 0 0       0 $v = sprintf($ent->[0] =~ /^[a-z]/ ? '%s:%s' : '%s%s', $ent->[0], $v);
485 0 0       0 $v = '['.$v.']' if $v =~ /-/;
486 0         0 return $v;
487             }
488              
489             # Fallback:
490 0         0 return sprintf('[uuid:%s]', $identifier->uuid);
491             }
492              
493             # Too experimental for listing in public API.
494             # TODO: Get this on track for the public API!
495             sub parse_unit_request {
496 0     0 0 0 my ($self, $template, $request, %opts) = _normalise_args(@_);
497 0   0     0 my $exponentas = delete($opts{exponentas}) // 'int';
498 0         0 my @res;
499              
500 0 0       0 croak 'Stray options passed' if scalar keys %opts;
501              
502 0 0       0 if ($template eq 'request') {
503 0         0 foreach my $subreq (split(/--/, $request)) {
504 0         0 my ($uuid, $neg, $exp_mul) = $subreq =~ /^([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})~(-?)([0-9])\z/;
505 0         0 my $exp;
506              
507             #printf("%-42s -> <%s> <%1s> <%s>\n", $subreq, $uuid, $neg, $exp_mul);
508              
509 0 0       0 if ($exponentas eq 'int') {
510 0         0 $exp = int($exp_mul);
511 0 0       0 $exp = -$exp if $neg eq '-';
512             } else {
513 0         0 croak 'Invalid exponentas: '.$exponentas;
514             }
515              
516 0         0 push(@res, {
517             component => Data::Identifier->new(uuid => $uuid),
518             exponent => $exp,
519             });
520             }
521             } else {
522 0         0 croak 'Unknown template: '.$template;
523             }
524              
525 0         0 return \@res;
526             }
527              
528             # Too experimental for listing in public API.
529             # TODO: Get this on track for the public API!
530             sub render_unit_request {
531 7     7 0 20 my ($pkg, $template, $request, %opts) = _normalise_args(@_);
532 7         14 my $displayname = delete $opts{displayname};
533 7         14 my $input;
534             my %took;
535              
536 7 50       22 croak 'Stray options passed' if scalar keys %opts;
537              
538 7 50       20 if (ref($request) eq 'ARRAY') {
539 0   0     0 $request = {map {($_component_to_name{$_->{component}->uuid} // croak 'Unknown base unit: '.$_->{component}->uuid) => $_->{exponent}} @{$request}};
  0         0  
  0         0  
540             }
541              
542 7 100       22 if (defined(my $prefix = delete($request->{prefix}))) {
543 5   33     18 $prefix = $_si_prefix{$prefix} // croak 'Bad prefix: '.$prefix;
544 5   50     28 $request->{10} //= 0;
545 5         10 $request->{10} += $prefix;
546             }
547              
548 7         45 foreach my $key (keys(%_composite_unit_elements)) {
549 182         293 my $n = $_composite_unit_elements{$key};
550 182   100     408 my $mul = delete($request->{$key}) // 0;
551 182 50       295 croak 'Bad exponent: '.$mul if $mul != int($mul);
552 182         189 $mul = int($mul);
553 182 100       329 next if $mul == 0;
554 6         9 foreach my $prime_element (keys %{$n}) {
  6         18  
555 13   50     40 $request->{$prime_element} //= 0;
556 13         28 $request->{$prime_element} += $n->{$prime_element} * $mul;
557             }
558             }
559              
560 7         66 foreach my $key (keys(%_base_units), keys(%_number_units)) {
561 196   100     277 my $mul = delete($request->{$key}) // 0;
562 196 50       180 croak 'Bad exponent: '.$mul if $mul != int($mul);
563 196         123 $mul = int($mul);
564 196 100       198 $took{$key} = $mul if $mul != 0;
565             }
566              
567             {
568 7         12 my @keys = keys %{$request};
  7         6  
  7         11  
569 7 50       11 croak 'Bad extra units: '.join(', ', @keys) if scalar @keys;
570             }
571              
572 7 100       15 unless (scalar(grep {!defined $_number_units{$_}} keys %took)) {
  21         36  
573 1         4 my $i = 1;
574              
575 1         2 foreach my $key (keys %took) {
576 1         4 $i *= $key ** $took{$key};
577             }
578              
579 1 50       3 if ($i == int($i)) {
580 1         5 return $pkg->pack($template => Data::Identifier::Generate->integer($i));
581             }
582 0         0 croak 'Invalid numeric only request';
583             }
584              
585             # Rename from units to UUIDs as keys.
586 6         7 foreach my $key (keys %took) {
587 20   66     54 my $uuid = ($_number_units{$key} // $_base_units{$key}{id})->uuid;
588 20         32 $took{$uuid} = delete $took{$key};
589             }
590              
591 6 100       13 if (scalar(keys %took) == 1) {
592 1         2 my ($mul) = values %took;
593 1 50       2 if ($mul == 1) {
594 1         2 my ($uuid) = keys %took;
595 1         4 return $pkg->pack($template => Data::Identifier->new(uuid => $uuid));
596             }
597             }
598              
599 5         17 $input = join('--', map{$_.'~'.$took{$_}} sort keys %took);
  19         43  
600              
601 5 50       12 if ($template eq 'request') {
602 0         0 return $input;
603             }
604              
605             $opts{namespace} //= $_wk{dunit_ns},
606             $opts{generator} //= $_wk{dunit_gen},
607 5   33     26 $opts{input} = $input;
      33        
608 5         6 $opts{request} = $input;
609 5         5 $opts{displayname} = $displayname;
610              
611 5         19 return $pkg->pack($template => Data::Identifier::Generate->generic(%opts));
612             }
613              
614              
615             #@returns Data::Identifier
616             sub register_namespace {
617 1     1 1 3 my ($self, $identifier, %opts) = _register_base(@_);
618              
619 1         4 $identifier->uuid; # ensure we map to an UUID
620              
621 1 50       3 croak 'Stray options passed' if scalar keys %opts;
622              
623 1         2 return $identifier->register;
624             }
625              
626              
627             #@returns Data::Identifier
628             sub register_generator {
629 1     1 1 5 my ($self, $identifier, %opts) = _register_base(@_);
630 1         2 my %pass;
631              
632 1         5 $pass{$_} = delete $opts{$_} foreach qw(namespace style type);
633 1         3 delete $opts{$_} foreach qw(native_case source_role up_relation for_type copy_tagnames native_ise_template);
634              
635 1 50       17 $pass{namespace} = $self->register_namespace($pass{namespace}) if defined $pass{namespace};
636 1 50       3 $pass{type} = $self->register_type($pass{type}) if defined $pass{type};
637              
638 1         5 Data::Identifier::Generate->_register_generator($identifier, %pass);
639              
640 1 50       2 croak 'Stray options passed' if scalar keys %opts;
641              
642 1         2 return $identifier->register;
643             }
644              
645              
646             #@returns Data::Identifier
647             sub register_type {
648 0     0 1 0 my ($self, $identifier, %opts) = _register_base(@_);
649 0         0 my $uuid = $identifier->uuid; # ensure we map to an UUID
650              
651 0 0       0 if (defined(my $validate = delete $opts{validate})) {
652 0   0     0 $identifier->{validate} //= $validate;
653             }
654              
655 0 0       0 if (defined(my $namespace = delete $opts{namespace})) {
656 0   0     0 $identifier->{namespace} //= $self->register_namespace($namespace);
657             }
658              
659 0 0       0 if (defined(my $null_value = delete $opts{null_value})) {
660 0         0 my $null = Data::Identifier->new(sid => 0);
661              
662 0   0     0 $null->{id_cache}{$uuid} //= $null_value;
663              
664 0         0 $null->register;
665             }
666              
667 0 0       0 croak 'Stray options passed' if scalar keys %opts;
668              
669 0         0 return $identifier->register;
670             }
671              
672              
673             #@returns Data::Identifier
674             sub regenerate {
675 2     2 1 6 my ($self, $identifier, %opts) = _normalise_args(@_);
676              
677 2         6 $identifier = Data::Identifier->new(from => $identifier);
678              
679 2 100       6 if (defined(my $generator = delete $opts{generator})) {
680             # TODO compare with what is in $identifier already and die on mismatch.
681 1   33     6 $identifier->{generator} //= Data::Identifier->new(from => $generator);
682             }
683              
684 2 100       4 if (defined(my $request = delete $opts{request})) {
685             # TODO compare with what is in $identifier already and die on mismatch.
686 1 50       3 croak 'Invalid request' unless length($request);
687 1   33     5 $identifier->{request} //= $request;
688             }
689              
690 2 50       4 croak 'Stray options passed' if scalar keys %opts;
691              
692 2 50 33     7 if (defined(my $generator = $identifier->{generator}) && defined(my $request = $identifier->{request})) {
693 2         3 my $n = eval { Data::Identifier::Generate->generic(generator => $generator, request => $request) };
  2         17  
694              
695 2 100       6 if (defined $n) {
696 1         3 foreach my $key (qw(displayname displaycolour description icontext)) {
697 4   100     15 $identifier->{$key} //= $n->{$key} // next;
      33        
698             }
699             }
700             }
701              
702 2         6 return $identifier;
703             }
704              
705             # ---- Private helpers ----
706              
707             sub _normalise_args {
708 52     52   118 my (@args) = @_;
709              
710 52 50       100 if (scalar(@args)) {
711 52 100 66     136 if (ref($args[0]) && eval {$args[0]->isa(__PACKAGE__)}) {
  45 50       157  
712             # no-op
713             } elsif ($args[0] eq __PACKAGE__) {
714 7         14 $args[0] = $_DEFAULT_INSTANCE;
715             } else {
716 0         0 unshift(@args, $_DEFAULT_INSTANCE);
717             }
718              
719 52         136 return @args;
720             }
721              
722 0         0 return ($_DEFAULT_INSTANCE);
723             }
724              
725             sub _load_well_known {
726 16     16   15 state $done = do {
727 1         3 my %meta = (
728             sni => \%_logicals_to_sni,
729             sid => \%_logicals_to_sid,
730             );
731              
732 1         3 foreach my $type (keys %meta) {
733 2         3 my $hash = $meta{$type};
734              
735 2         2 foreach my $key (keys %{$hash}) {
  2         9  
736 38         63 my $id = Data::Identifier->new($type => $hash->{$key});
737              
738 38 100       47 next unless defined $id->uuid(no_defaults => 1, default => undef);
739              
740 16   50     22 $id->{id_cache} //= {};
741 16   33     38 $id->{id_cache}->{$_logical} //= $key;
742              
743 16         21 $id->register;
744             }
745             }
746             };
747             }
748              
749             #@returns Data::Identifier
750             sub _update_tag {
751 6     6   15 my ($identifier, $sid, $sni, $tagname) = @_;
752 6   50     63 my $id_cache = $identifier->{id_cache} //= {};
753              
754 6 50 33     29 $id_cache->{Data::Identifier::WK_SID()} //= $sid if defined $sid;
755 6 50 33     22 $id_cache->{Data::Identifier::WK_SNI()} //= $sni if defined $sni;
756              
757 6 50       7 if (defined $tagname) {
758 6         21 my %tagnames = map {$_ => undef} $tagname, $identifier->tagname(list => 1, default => [], no_defaults => 1);
  6         17  
759 6         17 $identifier->{tagname} = [keys %tagnames];
760             }
761              
762 6         17 $identifier->register; # re-register
763              
764 6         6 return $identifier;
765             }
766              
767             # prepares arguments for register_*().
768             # Removes any common arguments from %opts
769             # does NOT call $identifier->register as register_*() might make additional changes that register needs to know about,
770             # so doing it here would just mean to do it twice.
771             sub _register_base {
772 2     2   3 my ($self, $identifier, %opts) = _normalise_args(@_);
773              
774 2         6 $identifier = Data::Identifier->new(from => $identifier);
775              
776 2 50       4 if (defined(my $displayname = delete $opts{displayname})) {
777 0   0     0 $identifier->{displayname} //= $displayname;
778             }
779              
780 2 50       4 if (defined(my $tagname = delete $opts{tagname})) {
781 0 0       0 my %tagnames = map {$_ => undef} (ref $tagname ? @{$tagname} : $tagname), $identifier->tagname(list => 1, default => [], no_defaults => 1);
  0         0  
  0         0  
782 0         0 $identifier->{tagname} = [keys %tagnames];
783             }
784              
785 2         6 return ($self, $identifier, %opts);
786             }
787              
788             1;
789              
790             __END__