App-td

 view release on metacpan or  search on metacpan

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

            summary => 'Allow/show duplicates',
            schema => 'bool*',
            cmdline_aliases => {r=>{}},
            tags => [
                'category:shuf-action', 'category:pick-action',
                'category:uniq-action', 'category:nauniq-action',
            ],
            description => <<'_',

For shuf/pick actions, setting this option means sampling with replacement which
makes a single row can be sampled/picked multiple times. The default is to
sample without replacement.

For uniq/nauniq actions, setting this option means instructing to return
duplicate rows instead of the unique rows.

_
        },

        weight_column => {
            summary => 'Select a column that contains weight',
            schema => 'str*',
            tags => ['category:shuf-action', 'category:pick-action'],
        },

        exclude_columns => {
            'x.name.is_plural' => 1,
            'x.name.singular' => 'exclude_column',
            schema => ['array*', of=>'str*'],
            cmdline_aliases => {E=>{}},
            tags => ['category:select-action', 'category:uniq-action', 'category:nauniq-action'],
        },
        include_columns => {
            'x.name.is_plural' => 1,
            'x.name.singular' => 'include_column',
            schema => ['array*', of=>'str*'],
            cmdline_aliases => {I=>{}},
            tags => ['category:select-action', 'category:uniq-action', 'category:nauniq-action'],
        },

        case_insensitive => {
            schema => ['true*'],
            cmdline_aliases => {i=>{}},
            tags => ['category:uniq-action', 'category:nauniq-action'],
        },

        no_header_column => {
            summary => "Don't make the first column as column names of the transposed table; ".
                "instead create column named 'row1', 'row2', ...",
            schema => 'true*',
            tags => ['category:transpose-action'],
        },
    },
};
sub td {
    my %args = @_;
    my $action = $args{action};
    my $argv   = $args{argv};

    my ($input, $input_form, $input_obj, $input_form_err);
  GET_INPUT:
    {
        last unless $actions{$action}{req_input} // 1;
        eval {
            local $/;
            $input = _decode_json(~~<STDIN>);
        };
        return [400, "Input is not valid JSON: $@"] if $@;

        # give envelope if not enveloped
        unless (ref($input) eq 'ARRAY' &&
                    @$input >= 2 && @$input <= 4 &&
                    $input->[0] =~ /\A[2-5]\d\d\z/ &&
                    !ref($input->[1])
                ) {
            $input = [200, "Envelope added by td", $input];
        }

        # detect table form
        ($input_form, $input_obj, $input_form_err) = _get_td_obj($input);
        return [400, "Input is not table data: $input_form_err"]
            unless $input_form;
    } # GET_INPUT

    my $output;
  PROCESS:
    {
        if ($action eq 'actions') {
            if ($args{detail}) {
                $output = [200, "OK", [map {+{name=>$_, summary=>$actions{$_}{summary}}} sort keys %actions]];
            } else {
                $output = [200, "OK", [sort keys %actions]];
            }
            last;
        }

        if ($action eq 'cat') {
            my $cols = $input_obj->cols_by_idx;
            $output = [200, "OK", $input_obj->{data}, {'table.fields'=>$cols}];
            last;
        }

        if ($action eq 'info') {
            my $form = ref($input_obj); $form =~ s/^Data::TableData::Object:://;
            my $info = {
                form => $form,
                rowcount => $input_obj->row_count,
                colcount => $input_obj->col_count,
                cols => join(", ", @{ $input_obj->cols_by_idx }),
            };
            $output = [200, "OK", $info];
            last;
        }

        if ($action eq 'rowcount' || $action eq 'wc') {
            $output = [200, "OK", $input_obj->row_count];
            last;
        }

        if ($action eq 'as-aoaos') {
            my $cols = $input_obj->cols_by_idx;
            my $rows = $input_obj->rows_as_aoaos;
            $output = [200, "OK", $rows, {'table.fields' => $cols}];
            last;
        }

        if ($action eq 'as-csv') {
            require Text::CSV_XS;
            my $csv = Text::CSV_XS->new({binary=>1}) or die "Can't instantiate CSV parser";
            my $cols = $input_obj->cols_by_idx;
            my $res = "";
            $csv->combine(@$cols) or die "Can't combine header row to CSV: ".join(", ", @$cols);
            $res .= $csv->string . "\n";
            for my $row (@{ $input_obj->rows_as_aoaos }) {
                $csv->combine(@$row) or die "Can't combine data row to CSV: ".join(", ", @$row);
                $res .= $csv->string . "\n";
            }
            $output = [200, "OK", $res, {'cmdline.skip_format' => 1}];
            last;
        }

        if ($action eq 'as-aohos') {
            my $cols = $input_obj->cols_by_idx;

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

            my $output_cols = [];
            for my $col_idx (0 .. $#{ $input_cols }) {
                my $code_res;
                {
                    no warnings 'once';
                    local $_              = $input_cols->[$col_idx];
                    local $main::colname  = $input_cols->[$col_idx];
                    local $main::colidx   = $col_idx;
                    local $main::td       = $input_obj;
                    $code_res = $code->($_);
                }
                push @$output_cols, $code_res ? $input_cols->[$col_idx] : undef;
            }

            if ($input_form eq 'hash' || $input_form eq 'aos') {
                $input_obj = _get_td_obj($input_form->rows_as_aoaos);
                $input_form = 'aoaos';
            }
            for my $col_idx (reverse 0..$#{ $output_cols }) {
                unless (defined $output_cols->[$col_idx]) {
                    $input_obj->del_col($col_idx);
                }
            }
            $output = [
                200,
                "OK",
                $input_form eq 'aohos' ? $input_obj->rows_as_aohos : $input_obj->rows_as_aoaos,
                $input_form eq 'aohos' ? $input->[3] : undef
            ];
            last;
        }

        if ($action eq 'transpose') {
            my $input_rows = $input_obj->rows_as_aoaos;
            my $input_cols = $input_obj->cols_by_idx;

            my @output_cols;
            if ($args{no_header_column} || !@$input_rows) {
                @output_cols = map {"row$_"} 1 .. @$input_rows;
            } else {
                @output_cols = map { $input_rows->[$_-1][0] } 1 .. @$input_rows;
            }

            my @output_rows;

            for my $inputrowidx (0..$#{ $input_rows }) {
                my $inputrow = $input_rows->[$inputrowidx];
                for my $inputcolidx (0..$#{ $input_cols }) {
                    $output_rows[$inputcolidx] //= [];
                    $output_rows[$inputcolidx][$inputrowidx] = $inputrow->[$inputcolidx];
                }
            }

            $output = [200, "OK", \@output_rows, {'table.fields'=>\@output_cols}];
            last;
        }

        return [400, "Unknown action '$action'"];
    } # PROCESS

  POSTPROCESS_OUTPUT:
    {
        require Pipe::Find;
        my $pipeinfo = Pipe::Find::get_stdout_pipe_process();
        last unless $pipeinfo;
        last unless
            $pipeinfo->{exe} =~ m![/\\]td\z! ||
            $pipeinfo->{cmdline} =~ m!\A([^\0]*[/\\])?perl\0([^\0]*[/\\])?td\0!;
        $output->[3]{'cmdline.default_format'} = 'json';
    }
    $output;
}

1;
# ABSTRACT: Manipulate table data

__END__

=pod

=encoding UTF-8

=head1 NAME

App::td - Manipulate table data

=head1 VERSION

This document describes version 0.112 of App::td (from Perl distribution App-td), released on 2024-06-26.

=head1 FUNCTIONS


=head2 td

Usage:

 td(%args) -> [$status_code, $reason, $payload, \%result_meta]

Manipulate table data.

B<What is td?>

I<td> receives table data from standard input and performs an action on it. It
has functionality similar to some Unix commands like I<head>, I<tail>, I<wc>,
I<cut>, I<sort> except that it operates on table rows/columns instead of
lines/characters. This is convenient to use with CLI scripts that output table
data.

B<What is table data?>

A I<table data> is JSON-encoded data in the form of either: C<hos> (hash of
scalars, which is viewed as a two-column table where the columns are C<key> and
C<value>), C<aos> (array of scalars, which is viewed as a 1-column array where the
column is C<elem>), C<aoaos> (array of arrays of scalars), or C<aohos> (array of
hashes of scalars).

The input can also be an I<enveloped> table data, where the envelope is an array:
C<[status, message, content, meta]> and C<content> is the actual table data. This
kind of data is produced by C<Perinci::CmdLine>-based scripts and can contain
more detailed table specification in the C<meta> hash, which C<td> can parse.



( run in 0.987 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )