File Coverage

bin/clarid-tools
Criterion Covered Total %
statement 57 73 78.0
branch 12 24 50.0
condition 7 19 36.8
subroutine 11 11 100.0
pod n/a
total 87 127 68.5


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             #
3             # ClarID-Tools CLI
4             #
5             # Last Modified: Aug/08/2025
6             #
7             # $VERSION taken from ClarID::Tools
8             #
9             # Copyright (C) 2025 Manuel Rueda - CNAG
10             #
11             # License: Artistic License 2.0
12             #
13             # If this program helps you in your research, please cite.
14              
15 41     41   220870 use strict;
  41         110  
  41         1931  
16 41     41   327 use warnings;
  41         159  
  41         2402  
17 41     41   21066 use FindBin qw($Bin);
  41         61663  
  41         6745  
18 41     41   27330 use lib "$Bin/../lib";
  41         35103  
  41         299  
19 41     41   26090 use ClarID::Tools;
  41         197  
  41         777  
20 41     41   44207 use JSON::XS qw(encode_json);
  41         254594  
  41         3590  
21 41     41   19542 use POSIX qw(strftime);
  41         310497  
  41         328  
22 41     41   72134 use Cwd qw(getcwd);
  41         78  
  41         2792  
23              
24 41     41   292 use Path::Tiny;
  41         257  
  41         105276  
25              
26 41         8798133 my ( $LOG_PATH, $ARGV_ORIG ) = _extract_log_path();
27 41         306 _log_invocation( $LOG_PATH, $ARGV_ORIG );
28              
29             # Shared usage text
30 41         118 my $USAGE = <<'END_USAGE';
31             Error: no command given.
32              
33             Usage:
34             clarid-tools [options]
35             clarid-tools help [all|code|validate|qrcode]
36             clarid-tools --version
37              
38             Commands:
39             code Encode or decode IDs using your codebook
40             validate Validate a codebook against its JSON schema
41             qrcode Encode or decode ClarIDs to/from QR codes
42              
43             Run `clarid-tools help all` to see every option at once.
44             END_USAGE
45              
46             # Dispatch
47             # NOTE: we read @ARGV *after* stripping -log so users can put -log anywhere
48 41   50     253 my $cmd = shift @ARGV // '';
49              
50             # version flag
51 41 50 33     407 if ( $cmd eq '-v' || $cmd eq '--v' || $cmd eq '--version' ) {
      33        
52 0         0 printf "clarid-tools version %s\n", ClarID::Tools->VERSION;
53 0         0 exit;
54             }
55              
56 41 50       256 if ( $cmd =~ /^-?-?help$/ ) {
57 0   0     0 my $sub = shift @ARGV // 'all';
58 0 0       0 if ( $sub eq 'all' ) {
59 0         0 for my $c (qw(code validate qrcode)) {
60 0         0 print "\n=== clarid-tools $c options ===\n\n";
61 0         0 system( $^X, $0, $c, '--help' );
62             }
63             }
64             else {
65 0         0 exec $^X, $0, $sub, '--help';
66             }
67 0         0 exit;
68             }
69              
70             # No command or unknown
71 41 50 33     448 if ( !$cmd || $cmd !~ /^(?:code|validate|qrcode)$/ ) {
72 0         0 die $USAGE;
73             }
74              
75             # Delegate to subcommands
76 41 100       192 if ( $cmd eq 'code' ) {
    50          
    0          
77 36         31796 require ClarID::Tools::Command::code;
78 36         786 ClarID::Tools::Command::code->new_with_options->execute;
79             }
80             elsif ( $cmd eq 'validate' ) {
81 5         3415 require ClarID::Tools::Command::validate;
82 5         83 ClarID::Tools::Command::validate->new_with_options->execute;
83             }
84             elsif ( $cmd eq 'qrcode' ) {
85 0         0 require ClarID::Tools::Command::qrcode;
86 0         0 ClarID::Tools::Command::qrcode->new_with_options->execute;
87             }
88              
89             # --- logging glue ----------------------------------------------------
90             sub _extract_log_path {
91              
92             # Returns (log_path_or_undef, \@argv_original)
93 41     41   621 my @orig = @ARGV;
94              
95             # We must handle -log, --log, and --log=FILE, anywhere in ARGV
96 41         387 for ( my $i = 0 ; $i < @ARGV ; $i++ ) {
97 712         1137 my $arg = $ARGV[$i];
98              
99             # --log=/path/file
100 712 50       1755 if ( $arg =~ /^--log=(.*)$/ ) {
101 0 0       0 my $path = $1 ne '' ? $1 : './clarid-cli.log';
102 0         0 splice @ARGV, $i, 1;
103 0         0 return ( $path, \@orig );
104             }
105              
106             # -log or --log [optional path]
107 712 100 66     2630 if ( $arg eq '-log' || $arg eq '--log' ) {
108 1         1 my $path = './clarid-cli.log';
109 1 50 33     6 if ( defined $ARGV[ $i + 1 ] && $ARGV[ $i + 1 ] !~ /^-/ ) {
110 1         1 $path = $ARGV[ $i + 1 ];
111 1         3 splice @ARGV, $i, 2; # remove flag and path
112             }
113             else {
114 0         0 splice @ARGV, $i, 1; # remove flag only
115             }
116 1         3 return ( $path, \@orig );
117             }
118             }
119 40         273 return ( undef, \@orig );
120             }
121              
122             sub _log_invocation {
123 41     41   150 my ( $log_file, $argv_snapshot ) = @_;
124 41 100       163 return unless $log_file;
125              
126             # Never crash CLI on logging errors
127 1         2 eval {
128 1         154 my $rec = {
129             ts => strftime( '%Y-%m-%dT%H:%M:%SZ', gmtime ),
130             cmd => $0,
131             argv => $argv_snapshot, # original ARGV (including -log)
132             cwd => getcwd(),
133             pid => $$,
134             version => ClarID::Tools->VERSION,
135             };
136 1         37 my $json = JSON::XS->new->utf8->canonical->encode($rec); # utf-8
137 1         10 path($log_file)->spew($json);
138             };
139 1         5924 return;
140             }
141              
142             1;