App-csvtool

 view release on metacpan or  search on metacpan

lib/App/csvtool.pm  view on Meta::CPAN

#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2021-2024 -- leonerd@leonerd.org.uk

package App::csvtool 0.04;

use v5.26;
use warnings;
use experimental 'signatures';

use Commandable 0.11;

=head1 NAME

C<App::csvtool> - implements the F<csvtool> core commands

=head1 DESCRIPTION

This module provides the main commands for the F<csvtool> wrapper script.

=head1 COMMANDS

=cut

package App::csvtool::cut
{

=head2 cut

   $ csvtool cut -fFIELDS INPUT...

Extracts the given field column(s).

=head3 --fields, -f

A comma-separated list of field indexes (defaults to 1).

A field index of C<u> will result in an undefined (i.e. empty) field being
emitted. This can be used to create spaces and pad out the data.

=cut

   use constant COMMAND_DESC => "Extract the given field(s) to output";

   use constant COMMAND_OPTS => (
      { name => "fields|f=", description => "Comma-separated list of fields to extract",
          default => "1" },
   );

   use constant WANT_READER => 1;
   use constant WANT_OUTPUT => 1;

   sub run ( $pkg, $opts, $reader, $output )
   {
      my @FIELDS = split m/,/, $opts->{fields};

      # 1-indexed
      $_ eq "u" || $_-- for @FIELDS;

      while( my $row = $reader->() ) {
         $output->( [ map { $_ eq "u" ? undef : $row->[$_] } @FIELDS ] );
      }
   }
}

package App::csvtool::grep
{

=head2 grep

   $ csvtool grep PATTERN INPUT...

Filter rows by the given pattern. The pattern is always interpreted as a Perl
regular expression.

=head3 --ignore-case, -i

Ignore case when matching.

=head3 --invert-match, -v

Output only the lines that do not match the filter pattern.

=cut

   use constant COMMAND_DESC => "Filter rows based on a regexp pattern";

   use constant COMMAND_OPTS => (
      { name => "field|f=", description => "Field to filter by",
         default => 1 },
      { name => "ignore-case|i", description => "Match ignoring case" },
      { name => "invert-match|v", description => "Selects only the non-matching rows" },
   );

   use constant COMMAND_ARGS => (
      { name => "pattern", description => "regexp pattern for filtering" },
   );

   use constant WANT_READER => 1;
   use constant WANT_OUTPUT => 1;

   sub run ( $pkg, $opts, $pattern, $reader, $output )
   {
      my $FIELD = $opts->{field};
      my $INVERT = $opts->{invert_match} // 0;

      $pattern = "(?i:$pattern)" if $opts->{ignore_case};

      # 1-based
      $FIELD--;

      my $re = qr/$pattern/;

      while( my $row = $reader->() ) {
         $output->( $row ) if $INVERT ^ $row->[ $FIELD ] =~ $re;



( run in 0.455 second using v1.01-cache-2.11-cpan-3782747c604 )