Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success Learn more

#!perl
use 5.010001;
use strict;
our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2023-12-03'; # DATE
our $DIST = 'App-TextTableUtils'; # DIST
our $VERSION = '0.010'; # VERSION
my ($ifmt, $ofmt) = $0 =~ /(\w+)2(\w+)\z/ or die "Please call me as <foo>2<bar> (not $0)";
# parse options
my %Opts;
{
require Getopt::Long;
Getopt::Long::Configure("bundling", "no_ignore_case", "permute", "no_getopt_compat");
Getopt::Long::GetOptions(
"backend|b=s" => \$Opts{backend},
"transpose|t" => \$Opts{transpose},
"csv-sep|s=s" => \$Opts{csv_sep},
"csv-quote|q=s" => \$Opts{csv_quote},
"csv-escape|e=s" => \$Opts{csv_escape},
"csv-loose|l" => \$Opts{csv_loose}
) or die "$0: Error in getting options, bailing out\n";
}
# get input handle
my $fh;
{
if (@ARGV == 1) {
open $fh, "<:encoding(utf8)", $ARGV[0] or die "Can't open $ARGV[0]: $!";
} elsif (!@ARGV) {
binmode(STDIN, ":encoding(utf8)");
$fh = \*STDIN;
} else {
die "Usage: $0 <filename>\n";
}
}
my $rows;
# parse input into rows
{
if ($ifmt eq 'csv') {
require Text::CSV;
my $csv = Text::CSV->new({ binary => 1 })
or die "Cannot use CSV: ".Text::CSV->error_diag;
if ($Opts{csv_sep}) {
$csv->sep_char($Opts{csv_sep});
}
if ($Opts{csv_quote}) {
$csv->quote_char($Opts{csv_quote});
}
if ($Opts{csv_escape}) {
$csv->escape_char($Opts{csv_escape});
}
if ($Opts{csv_loose}) {
$csv->allow_loose_quotes(1);
$csv->allow_loose_escapes(1);
}
$rows = [];
while ( my $row = $csv->getline($fh) ) {
push @$rows, $row;
}
$csv->eof or $csv->error_diag();
} elsif ($ifmt eq 'tsv') {
$rows = [];
while (my $row = <$fh>) {
chomp $row;
push @$rows, [split /\t/, $row];
}
} elsif ($ifmt eq 'ini' || $ifmt eq 'iod') {
my $reader;
if ($ifmt eq 'ini') {
$reader = Config::IOD::INI::Reader->new;
} else {
$reader = Config::IOD::Reader->new;
}
my $content = do { local $/; scalar <$fh> };
my $hoh = $reader->read_string($content);
$rows = [];
for my $section (sort keys %$hoh) {
my $hash = $hoh->{$section};
for my $key (sort keys %$hash) {
push @$rows, [$key, $hash->{$key}];
}
}
} elsif ($ifmt eq 'json') {
require JSON::MaybeXS;
local $/;
$rows = JSON::MaybeXS::decode_json(scalar <$fh>);
die "Input data is not an array-of-arrays-of scalars"
unless Data::Check::Structure::is_aoaos($rows);
} elsif ($ifmt eq 'dd') {
local $/;
$rows = eval scalar <$fh>; ## no critic: BuiltinFunctions::ProhibitStringyEval
die if $@;
die "Input data is not an array-of-arrays-of scalars"
unless Data::Check::Structure::is_aoaos($rows);
} else {
die "Unknown input format '$ifmt'";
}
}
# optionally transpose
if ($Opts{transpose}) {
my $new_rows = [];
for my $i (0..$#{$rows}) {
my $row = $rows->[$i];
for my $j (0..$#{$row}) {
$new_rows->[$j][$i] = $row->[$j];
}
}
$rows = $new_rows;
}
# format as output
{
if ($ofmt eq 'dd') {
no warnings 'once';
require Data::Dumper;
$Data::Dumper::Indent = 0;
$Data::Dumper::Terse = 1;
# produce nicer, one-row-at-a-line output
print "[\n";
for (@$rows) {
print " ", Data::Dumper::Dumper($_), ",\n";
}
print "]\n";
} elsif ($ofmt eq 'json') {
require JSON::MaybeXS;
# produce nicer, one-row-at-a-line output
print "[\n";
for (0..$#{$rows}) {
print " ", JSON::MaybeXS::encode_json($rows->[$_]),
($_ == $#{$rows} ? "" : ","), "\n";
}
print "]\n";
} elsif ($ofmt eq 'texttable') {
print Text::Table::Any::table(
rows => $rows, header_row=>1, backend => $Opts{backend});
} elsif ($ofmt eq 'csv') {
print Text::Table::Any::table(
rows => $rows, header_row=>0, backend => 'Text::Table::CSV');
} elsif ($ofmt eq 'tsv') {
print Text::Table::Any::table(
rows => $rows, header_row=>0, backend => 'Text::Table::TSV');
} elsif ($ofmt eq 'ansitable') {
require Text::ANSITable;
print Text::Table::Any::table(
rows => $rows, header_row=>1, backend => 'Text::ANSITable');
} elsif ($ofmt eq 'asciitable') {
print Text::Table::Any::table(
rows => $rows, header_row=>0, backend => 'Text::ASCIITable');
} elsif ($ofmt eq 'mdtable') {
print Text::Table::Any::table(
rows => $rows, header_row=>0, backend => 'Text::MarkdownTable');
} elsif ($ofmt eq 'orgtable') {
print Text::Table::Any::table(
rows => $rows, header_row=>1, backend => 'Text::Table::Org');
} else {
die "Unknown output format '$ofmt'";
}
}
# ABSTRACT: Convert/render {CSV,TSV,INI,IOD,JSON/Perl array-of-arrays} into {CSV/TSV/JSON/Perl/text tables}
# PODNAME: dd2texttable
__END__
=pod
=encoding UTF-8
=head1 NAME
dd2texttable - Convert/render {CSV,TSV,INI,IOD,JSON/Perl array-of-arrays} into {CSV/TSV/JSON/Perl/text tables}
=head1 VERSION
This document describes version 0.010 of dd2texttable (from Perl distribution App-TextTableUtils), released on 2023-12-03.
=head1 SYNOPSIS
The distribution App-TextTableUtils provides the following CLIs:
=over
=item 1. L<csv2ansitable>
=item 2. L<csv2asciitable>
=item 3. L<csv2dd>
=item 4. L<csv2json>
=item 5. L<csv2mdtable>
=item 6. L<csv2orgtable>
=item 7. L<csv2texttable>
=item 8. L<dd2ansitable>
=item 9. L<dd2asciitable>
=item 10. L<dd2csv>
=item 11. L<dd2mdtable>
=item 12. L<dd2orgtable>
=item 13. L<dd2texttable>
=item 14. L<dd2tsv>
=item 15. L<ini2ansitable>
=item 16. L<ini2asciitable>
=item 17. L<ini2csv>
=item 18. L<ini2mdtable>
=item 19. L<ini2orgtable>
=item 20. L<ini2texttable>
=item 21. L<ini2tsv>
=item 22. L<iod2ansitable>
=item 23. L<iod2asciitable>
=item 24. L<iod2csv>
=item 25. L<iod2mdtable>
=item 26. L<iod2orgtable>
=item 27. L<iod2texttable>
=item 28. L<iod2tsv>
=item 29. L<json2ansitable>
=item 30. L<json2asciitable>
=item 31. L<json2csv>
=item 32. L<json2mdtable>
=item 33. L<json2orgtable>
=item 34. L<json2texttable>
=item 35. L<json2tsv>
=item 36. L<texttableutils-convert>
=item 37. L<tsv2ansitable>
=item 38. L<tsv2asciitable>
=item 39. L<tsv2dd>
=item 40. L<tsv2json>
=item 41. L<tsv2mdtable>
=item 42. L<tsv2orgtable>
=item 43. L<tsv2texttable>
=back
Some examples for using the scripts:
To render CSV as Org table:
% csv2orgtable TABLE.CSV
To render CSV as JSON:
% csv2json < TABLE.CSV
To render TSV as a text table (using L<Text::Table::Any>) with LTSV backend:
% echo "SELECT * FROM table1" | mysql DBNAME | tsv2texttable -b Text::Table::LTSV
=head1 OPTIONS
=head2 --backend (-b)
Only for texttable output (C<*2texttable> scripts).
Select L<Text::Table::Any> backend to use.
=head2 --transpose (-t)
Transpose table prior to output.
=head2 --csv-sep (-s)
Use the character(s) specified by this option, when input is a CSV files with a
different separator.
=head2 --csv-loose (-l)
Enable C<allow_loose_escapes> and C<allow_loose_quotes> in L<Text::CSV> (the
backend used to read CSV files).
=head1 HOMEPAGE
Please visit the project's homepage at L<https://metacpan.org/release/App-TextTableUtils>.
=head1 SOURCE
=head1 SEE ALSO
L<App::texttable>, L<texttable>
=head1 AUTHOR
perlancar <perlancar@cpan.org>
=head1 CONTRIBUTING
To contribute, you can send patches by email/via RT, or send pull requests on
GitHub.
Most of the time, you don't need to build the distribution yourself. You can
simply modify the code, then test via:
% prove -l
If you want to build the distribution (e.g. to try to install it locally on your
system), you can install L<Dist::Zilla>,
L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
that are considered a bug and can be reported to me.
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2023, 2022, 2021, 2019, 2016 by perlancar <perlancar@cpan.org>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-TextTableUtils>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=cut