File Coverage

blib/lib/URI/PackageURL/App.pm
Criterion Covered Total %
statement 71 252 28.1
branch 25 118 21.1
condition 0 15 0.0
subroutine 14 19 73.6
pod 2 2 100.0
total 112 406 27.5


line stmt bran cond sub pod time code
1             package URI::PackageURL::App;
2              
3 2     2   127435 use feature ':5.10';
  2         4  
  2         347  
4 2     2   25 use strict;
  2         3  
  2         46  
5 2     2   9 use warnings;
  2         3  
  2         117  
6 2     2   580 use utf8;
  2         312  
  2         14  
7              
8 2     2   86 use Carp ();
  2         4  
  2         46  
9 2     2   1139 use Data::Dumper ();
  2         17441  
  2         87  
10 2     2   1649 use Getopt::Long qw(GetOptionsFromArray :config gnu_compat);
  2         28473  
  2         13  
11 2     2   523 use JSON::PP ();
  2         5  
  2         32  
12 2     2   1461 use Pod::Text ();
  2         121262  
  2         95  
13 2     2   1238 use Pod::Usage qw(pod2usage);
  2         7942  
  2         177  
14              
15 2     2   660 use URI::PackageURL ();
  2         6  
  2         47  
16 2     2   12 use URI::PackageURL::Type ();
  2         3  
  2         48  
17 2     2   10 use URI::PackageURL::Util qw(purl_types);
  2         3  
  2         7610  
18              
19             our $VERSION = '2.25';
20              
21             sub cli_error {
22 0     0 1 0 my ($error) = @_;
23 0         0 $error =~ s/ at .* line \d+.*//;
24 0         0 say STDERR "ERROR: $error";
25             }
26              
27             sub run {
28              
29 16     16 1 185294 my ($class, @args) = @_;
30              
31 16         66 my %options = (format => 'json');
32              
33 16 50       121 GetOptionsFromArray(
34             \@args, \%options, qw(
35             help|h
36             man
37             v
38              
39             download-url
40             repository-url
41              
42             validate
43             quiet|q
44             info=s
45             list
46              
47             type=s
48             namespace=s
49             name=s
50             version=s
51             qualifiers|qualifier=s%
52             subpath=s
53              
54             null|0
55             format=s
56              
57             json
58             yaml
59             dumper
60             env
61             )
62             ) or pod2usage(-verbose => 0);
63              
64 16 50       35156 pod2usage(-exitstatus => 0, -verbose => 2) if defined $options{man};
65 16 50       57 pod2usage(-exitstatus => 0, -verbose => 0) if defined $options{help};
66              
67 16 50       57 if (defined $options{v}) {
68              
69 0         0 (my $progname = $0) =~ s/.*\///;
70              
71 0         0 say <<"VERSION";
72             $progname version $URI::PackageURL::VERSION
73              
74             Copyright 2022-2026, Giuseppe Di Terlizzi
75              
76             This program is part of the "URI-PackageURL" distribution and is free software;
77             you can redistribute it and/or modify it under the same terms as Perl itself.
78              
79             Complete documentation for $progname can be found using 'man $progname'
80             or on the internet at .
81             VERSION
82              
83 0         0 return 0;
84              
85             }
86              
87 16 50       62 if (defined $options{info}) {
88 0         0 return _definition_help(lc $options{info});
89             }
90              
91 16 50       47 if (defined $options{list}) {
92 0         0 return _purl_list();
93             }
94              
95 16 100       50 if (defined $options{type}) {
96              
97 1         4 my $purl = eval {
98             URI::PackageURL->new(
99             type => $options{type},
100             namespace => $options{namespace},
101             name => $options{name},
102             version => $options{version},
103             qualifiers => $options{qualifiers},
104             subpath => $options{subpath},
105 1         19 );
106             };
107              
108 1 50       6 if ($@) {
109 0         0 cli_error($@);
110 0         0 return 1;
111             }
112              
113 1 50       7 print "$purl" . (defined $options{null} ? "\0" : "\n");
114 1         13 return 0;
115              
116             }
117              
118 15         39 my ($purl_string) = @args;
119              
120 15 50       51 pod2usage(-verbose => 1) if !$purl_string;
121              
122 15 100       45 $options{format} = 'json' if defined $options{json};
123 15 50       44 $options{format} = 'yaml' if defined $options{yaml};
124 15 50       38 $options{format} = 'dumper' if defined $options{dumper};
125 15 50       45 $options{format} = 'env' if defined $options{env};
126              
127 15         34 my $purl = eval { URI::PackageURL->from_string($purl_string) };
  15         94  
128              
129 15 100       75 if ($options{validate}) {
130              
131 14 50       59 unless ($options{quiet}) {
132 0 0       0 say STDERR $purl ? 'true' : 'false';
133             }
134              
135 14 100       95 return $purl ? 0 : 1;
136              
137             }
138              
139 1 50       2 if ($@) {
140 0         0 cli_error($@);
141 0         0 return 1;
142             }
143              
144 1         5 my $purl_urls = $purl->to_urls;
145              
146 1 50       4 if ($options{'download-url'}) {
147              
148 0 0       0 return 2 unless defined $purl_urls->{download};
149              
150 0 0       0 print $purl_urls->{download} . (defined $options{null} ? "\0" : "\n");
151 0         0 return 0;
152              
153             }
154              
155 1 50       3 if ($options{'repository-url'}) {
156              
157 0 0       0 return 2 unless defined $purl_urls->{repository};
158              
159 0 0       0 print $purl_urls->{repository} . ($options{null} ? "\0" : "\n");
160 0         0 return 0;
161             }
162              
163 1 50       3 if ($options{format} eq 'json') {
164 1         9 print JSON::PP->new->canonical->pretty(1)->convert_blessed(1)->encode($purl);
165 1         355 return 0;
166             }
167              
168 0 0         if ($options{format} eq 'dumper') {
169 0           print Data::Dumper->new([$purl->to_hash])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump;
170 0           return 0;
171             }
172              
173 0 0         if ($options{format} eq 'yaml') {
174              
175 0 0         if (eval { require YAML::XS }) {
  0            
176 0           print YAML::XS::Dump($purl->to_hash);
177 0           return 0;
178             }
179 0 0         if (eval { require YAML }) {
  0            
180 0           print YAML::Dump($purl->to_hash);
181 0           return 0;
182             }
183              
184 0           cli_error 'YAML or YAML::XS module are missing';
185 0           return 255;
186              
187             }
188              
189 0 0         if ($options{format} eq 'env') {
190 0           return _purl_env($purl);
191             }
192              
193             }
194              
195             sub _md_to_pod {
196              
197 0     0     my $text = shift;
198              
199 0           $text =~ s/(``([^``]*)``)/C<$2>/gm;
200 0           $text =~ s/(`([^`]*)`)/C<$2>/gm;
201              
202 0           return $text;
203              
204             }
205              
206             sub _purl_env {
207              
208 0     0     my $purl = shift;
209              
210 0           my $purl_urls = $purl->to_urls;
211              
212             my %PURL_ENVS = (
213             PURL => $purl->to_string,
214             PURL_TYPE => $purl->type,
215             PURL_NAMESPACE => $purl->namespace,
216             PURL_NAME => $purl->name,
217             PURL_VERSION => $purl->version,
218             PURL_SUBPATH => $purl->subpath,
219 0           PURL_QUALIFIERS => (join ' ', sort keys %{$purl->qualifiers}),
  0            
220             );
221              
222             # Preserve order
223 0           my @PURL_ENVS = qw(PURL PURL_TYPE PURL_NAMESPACE PURL_NAME PURL_VERSION PURL_SUBPATH PURL_QUALIFIERS);
224              
225 0           my $qualifiers = $purl->qualifiers;
226              
227 0           foreach my $qualifier (sort keys %{$qualifiers}) {
  0            
228 0           my $key = "PURL_QUALIFIER_$qualifier";
229 0           push @PURL_ENVS, $key;
230 0           $PURL_ENVS{$key} = $qualifiers->{$qualifier};
231             }
232              
233 0 0         if ($purl_urls) {
234 0 0         if (defined $purl_urls->{download}) {
235 0           push @PURL_ENVS, 'PURL_DOWNLOAD_URL';
236 0           $PURL_ENVS{PURL_DOWNLOAD_URL} = $purl_urls->{download};
237             }
238 0 0         if (defined $purl_urls->{repository}) {
239 0           push @PURL_ENVS, 'PURL_REPOSITORY_URL';
240 0           $PURL_ENVS{PURL_REPOSITORY_URL} = $purl_urls->{repository};
241             }
242             }
243              
244 0           foreach my $key (@PURL_ENVS) {
245 0   0       print sprintf qq{%s="%s"\n}, $key, $PURL_ENVS{$key} || q{};
246             }
247              
248 0           return 0;
249              
250             }
251              
252             sub _purl_list {
253              
254 0     0     my @types = purl_types();
255              
256 0           my $pattern = "%15s | %10s | %10s | %10s | %10s | %s";
257              
258 0           say sprintf $pattern, 'TYPE', 'NAMESPACE', 'NAME', 'VERSION', 'SUBPATH', 'QUALIFIERS';
259              
260 0           say sprintf "%s-|-%s-|-%s-|-%s-|-%s-|-%s", '-' x 15, '-' x 10, '-' x 10, '-' x 10, '-' x 10, '-' x 10;
261              
262 0           for my $type (@types) {
263              
264 0           my $definition = URI::PackageURL::Type->new($type);
265              
266 0           my $namespace = '-';
267 0           my $name = '-';
268 0           my $version = '-';
269 0           my $subpath = '-';
270 0           my $qualifiers = '-';
271              
272 0 0         if ($definition->component_have_definition('namespace')) {
273 0   0       $namespace = $definition->component_requirement('namespace') // '-';
274             }
275              
276 0 0         if ($definition->component_have_definition('name')) {
277 0   0       $name = $definition->component_requirement('name') // '-';
278             }
279              
280 0 0         if ($definition->component_have_definition('version')) {
281 0   0       $version = $definition->component_requirement('version') // '-';
282             }
283              
284 0 0         if ($definition->component_have_definition('subpath')) {
285 0   0       $subpath = $definition->component_requirement('subpath') // '-';
286             }
287              
288 0 0         if (@{$definition->qualifiers_definition}) {
  0            
289 0           $qualifiers = join ", ", map { $_->{key} } @{$definition->qualifiers_definition};
  0            
  0            
290             }
291              
292 0           say sprintf $pattern, $type, $namespace, $name, $version, $subpath, $qualifiers;
293              
294             }
295              
296 0           return 0;
297              
298             }
299              
300              
301             sub _definition_help {
302              
303 0     0     my $type = shift;
304              
305 0           my $definition = URI::PackageURL::Type->new($type);
306              
307 0 0         unless (%{$definition->definition}) {
  0            
308 0           say "No known PURL type definition for '$type'";
309 0           exit 1;
310             }
311              
312 0           my $type_name = $definition->type_name;
313 0           my $description = $definition->description;
314 0           my $reference_urls = $definition->reference_urls;
315 0           my $examples = $definition->examples;
316 0           my $note = $definition->note;
317 0           my $repository = $definition->repository;
318 0           my $schema_id = $definition->schema_id;
319              
320 0           my $qualifiers_definition = $definition->qualifiers_definition;
321              
322 0   0       my $have_ns = ($definition->component_is_required('namespace') || $definition->component_is_optional('namespace'));
323              
324 0           my $purl_syntax = "pkg:$type";
325 0 0         $purl_syntax .= '/EnamespaceE' if $have_ns;
326 0           $purl_syntax .= '/EnameE@EversionE?EqualifiersE#EsubpathE';
327              
328 0           my $man = <<"MAN";
329             =head1 NAME
330              
331             $type - $type_name
332              
333             =head1 DESCRIPTION
334              
335             $description
336              
337             =head1 SYNTAX
338              
339             The structure of a PURL for this package type is:
340              
341             C<$purl_syntax>
342              
343             MAN
344              
345 0           foreach my $component (qw[namespace name version subpath]) {
346              
347 0 0         next unless $definition->component_have_definition($component);
348              
349 0           my $requirement = $definition->component_requirement($component);
350 0           my $permitted_characters = $definition->component_permitted_characters($component);
351 0           my $normalization_rules = $definition->component_normalization_rules($component);
352 0           my $case_sensitive = $definition->component_case_sensitive($component);
353 0           my $native_name = $definition->component_native_name($component);
354 0           my $note = $definition->component_note($component);
355              
356 0           $man .= sprintf "=head2 %s\n\n", ucfirst $component;
357 0           $man .= "=over 2\n\n";
358              
359 0 0         if ($requirement) {
360 0           $man .= sprintf "=item B: %s\n\n", ucfirst($requirement);
361             }
362              
363 0 0         if ($permitted_characters) {
364 0           $man .= sprintf "=item B: %s\n\n", ucfirst($permitted_characters);
365             }
366              
367 0 0         if ($case_sensitive) {
368 0 0         $man .= sprintf "=item B: %s\n\n", ($case_sensitive ? 'Yes' : 'No');
369             }
370              
371 0 0         if (@{$normalization_rules}) {
  0            
372              
373 0           $man .= "=item B:\n\n";
374 0           $man .= "=over 2\n\n";
375              
376 0           foreach (@{$normalization_rules}) {
  0            
377 0           $man .= sprintf "=item * %s\n\n", $_;
378             }
379              
380 0           $man .= "=back\n\n";
381              
382             }
383              
384 0 0         if ($native_name) {
385 0           $man .= "=item B: $native_name\n\n";
386             }
387              
388 0           $man .= "=back\n\n";
389              
390 0 0         if ($note) {
391 0           $man .= sprintf "%s\n\n", _md_to_pod($note);
392             }
393             }
394              
395 0 0         if (@{$qualifiers_definition}) {
  0            
396              
397 0           $man .= "=head2 Qualifiers\n\n";
398 0           $man .= "=over 2\n\n";
399              
400 0           foreach my $qualifier (@{$qualifiers_definition}) {
  0            
401              
402 0           $man .= sprintf "=item C<%s>\n\n", $qualifier->{key};
403              
404 0 0         if (my $requirement = $qualifier->{requirement}) {
405 0           $man .= sprintf "Requirement: %s\n\n", ucfirst($requirement);
406             }
407              
408 0 0         if (my $native_name = $qualifier->{native_name}) {
409 0           $man .= sprintf "Native name: %s\n\n", $native_name;
410             }
411              
412 0 0         if (my $default_value = $qualifier->{default_value}) {
413 0           $man .= sprintf "Default value: %s\n\n", $default_value;
414             }
415              
416 0           $man .= sprintf "%s\n\n", _md_to_pod($qualifier->{description});
417              
418             }
419              
420 0           $man .= "=back\n\n";
421              
422             }
423              
424 0 0         if ($repository) {
425              
426 0 0         my $use_repository = $repository->{use_repository} ? 'Yes' : 'No';
427 0           my $default_repository_url = $repository->{default_repository_url};
428              
429 0           $man .= "=head1 REPOSITORY\n\n";
430 0           $man .= "=over\n\n";
431 0 0         $man .= sprintf "=item B: %s\n\n", $repository->{use_repository} ? 'Yes' : 'No';
432 0   0       $man .= sprintf "=item B: %s\n\n", $repository->{default_repository_url} || '(none)';
433 0           $man .= "=back\n\n";
434              
435 0 0         if (my $note = $repository->{note}) {
436 0           $man .= sprintf "%s\n\n", _md_to_pod($note);
437             }
438              
439             }
440              
441 0 0         if (@{$examples}) {
  0            
442              
443 0           $man .= "=head1 EXAMPLES\n\n";
444 0           $man .= "=over 2\n\n";
445              
446 0           foreach (@{$examples}) {
  0            
447 0           $man .= sprintf "=item * %s\n\n", $_;
448             }
449              
450 0           $man .= "=back\n\n";
451              
452             }
453              
454 0 0         if ($note) {
455 0           $man .= "=head1 NOTES\n\n";
456 0           $man .= sprintf "$note\n\n";
457             }
458              
459 0           $man .= "=head1 REFERENCES\n\n";
460 0           $man .= "=over 2\n\n";
461              
462 0           $man .= sprintf "=item * %s schema ID, L<%s>\n\n", $type_name, $schema_id;
463              
464 0           foreach (@{$reference_urls}) {
  0            
465 0           $man .= sprintf "=item * %s reference, L<%s>\n\n", $type_name, $_;
466             }
467              
468 0           $man .= "=item * PURL specification, L\n\n";
469 0           $man .= "=item * VERS specification, L\n\n";
470              
471 0           $man .= "=back\n\n";
472              
473 0           Pod::Text->new->parse_string_document($man, \my $output);
474              
475 0           exit;
476              
477             }
478              
479             1;
480              
481             __END__