ClarID-Tools
view release on metacpan or search on metacpan
lib/ClarID/Tools/Command/qrcode.pm view on Meta::CPAN
package ClarID::Tools::Command::qrcode;
use strict;
use warnings;
use feature 'say';
use Moo;
use MooX::Options
auto_help => 1,
auto_version => 1,
usage => 'USAGE',
config_from_hash => {};
use Types::Standard qw(Enum Int Str);
use Text::CSV_XS;
use File::Path qw(make_path);
use Carp qw(croak);
# Tell App::Cmd this is a command
use App::Cmd::Setup -command;
use namespace::autoclean;
# CLI options
# NB: Invalid parameter values (e.g., --format=foo) trigger App::Cmd usage/help
# This hides the detailed Types::Standard error
# Fix by overriding usage_error/options_usage
option 'action' => (
is => 'ro',
format => 's',
isa => Enum[qw/encode decode/],
required => 1,
doc => 'encode | decode',
);
option 'input' => (
is => 'ro',
format => 's',
isa => Str,
required => 1,
doc => 'CSV file (encode) or directory of PNGs, or single PNG file (decode)',
);
option 'column' => (
is => 'ro',
format => 's',
isa => Str,
doc => 'Column name to read (defaults to clar_id or stub_id, encode only)',
);
option 'outdir' => (
is => 'ro',
format => 's',
isa => Str,
default => sub { 'qrcodes' },
doc => 'Where to write PNGs (encode only)',
);
option 'outfile' => (
is => 'ro',
format => 's',
isa => Str,
default => sub { 'decoded.csv' },
doc => 'Where to write CSV (decode directory mode only)',
);
option 'sep' => (
is => 'ro',
format => 's',
isa => Str,
default => sub { ',' },
doc => 'CSV separator',
);
option 'size' => (
is => 'ro',
format => 'i',
isa => Int,
default => sub { 3 },
doc => 'module size for qrencode (-s flag)',
);
sub execute {
my $self = shift;
if ($self->action eq 'encode') {
$self->_run_encode;
} else {
$self->_run_decode;
}
}
sub _run_encode {
my $self = shift;
# Check for qrencode
system("which qrencode >/dev/null 2>&1") == 0
or croak "ERROR: 'qrencode' not found in PATH";
# Prepare output directory
unless (-d $self->outdir) {
make_path($self->outdir)
or croak "ERROR: cannot create directory '$self->{outdir}'";
}
# Open CSV file
my $csv = Text::CSV_XS->new({ sep_char => $self->sep, binary => 1, auto_diag => 1 });
open my $fh, '<', $self->input
or croak "ERROR: Cannot open '$self->input': $!";
# Read header
my $hdr_ref = $csv->getline($fh)
or croak "ERROR: CSV is empty";
my @hdr = @$hdr_ref;
my %idx = map { $hdr[$_] => $_ } 0..$#hdr;
# Determine column
my $col = $self->column
|| (exists $idx{clar_id} ? 'clar_id'
: exists $idx{stub_id} ? 'stub_id'
: croak "ERROR: No --column and neither clar_id nor stub_id in header");
croak "ERROR: Column '$col' not found" unless exists $idx{$col};
say "Encoding column '$col' into PNG files in directory '$self->{outdir}'";
# Process rows
while (my $row = $csv->getline($fh)) {
my $val = $row->[$idx{$col}] // '';
next unless length $val;
(my $safe = $val) =~ s/[^A-Za-z0-9_-]/_/g;
my $png = "$self->{outdir}/$safe.png";
system("qrencode", "-s", $self->size, "-o", $png, $val) == 0
or warn "WARNING: qrencode failed for value '$val'" and next;
say " - $val -> $png";
}
close $fh;
}
sub _run_decode {
my $self = shift;
# Check for zbarimg
system("which zbarimg >/dev/null 2>&1") == 0
or croak "ERROR: 'zbarimg' not found in PATH";
# Single-file decode mode
if (-f $self->input && $self->input =~ /\.png$/i) {
my $file = $self->input;
chomp(my $decoded = qx(zbarimg --raw "$file" 2>/dev/null));
die "ERROR: no QR code found in '$file'\n" unless length $decoded;
say $decoded;
return 1;
}
# Directory decode mode
my $col_name = $self->column || 'clar_id';
opendir my $dh, $self->input
or croak "ERROR: Cannot open directory '$self->input': $!";
my @files = grep { /\.png$/i } readdir $dh;
closedir $dh;
open my $out, '>', $self->outfile
or croak "ERROR: Cannot write to '$self->outfile': $!";
print $out "$col_name\n";
for my $f (@files) {
my $path = "$self->{input}/$f";
chomp(my $decoded = qx(zbarimg --raw "$path" 2>/dev/null));
next unless length $decoded;
$decoded =~ s/\r?\n//g;
print $out "$decoded\n";
}
close $out;
say "Decoded CSV written to '$self->{outfile}'";
}
1;
( run in 0.543 second using v1.01-cache-2.11-cpan-2398b32b56e )