#!perl use 5.010001; use strict; use warnings; 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') { require Config::IOD::INI::Reader; $reader = Config::IOD::INI::Reader->new; } else { require Config::IOD::Reader; $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 Data::Check::Structure; 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') { require Data::Check::Structure; 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') { require Text::Table::Any; print Text::Table::Any::table( rows => $rows, header_row=>1, backend => $Opts{backend}); } elsif ($ofmt eq 'csv') { require Text::Table::Any; require Text::Table::CSV; print Text::Table::Any::table( rows => $rows, header_row=>0, backend => 'Text::Table::CSV'); } elsif ($ofmt eq 'tsv') { require Text::Table::Any; require Text::Table::TSV; print Text::Table::Any::table( rows => $rows, header_row=>0, backend => 'Text::Table::TSV'); } elsif ($ofmt eq 'ansitable') { require Text::Table::Any; require Text::ANSITable; print Text::Table::Any::table( rows => $rows, header_row=>1, backend => 'Text::ANSITable'); } elsif ($ofmt eq 'asciitable') { require Text::Table::Any; require Text::ASCIITable; print Text::Table::Any::table( rows => $rows, header_row=>0, backend => 'Text::ASCIITable'); } elsif ($ofmt eq 'mdtable') { require Text::Table::Any; require Text::MarkdownTable; print Text::Table::Any::table( rows => $rows, header_row=>0, backend => 'Text::MarkdownTable'); } elsif ($ofmt eq 'orgtable') { require Text::Table::Any; require Text::Table::Org; 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: json2ansitable __END__ =pod =encoding UTF-8 =head1 NAME json2ansitable - 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 json2ansitable (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 Source repository is at L<https://github.com/perlancar/perl-App-TextTableUtils>. =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