From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

#!/usr/bin/env perl
my $copyright = <<'COPYRIGHT';
# Copyright 2021 by Christian Jaeger <ch@christianjaeger.ch>
# Published under the same terms as perl itself
COPYRIGHT
use strict;
use warnings FATAL => 'uninitialized';
use experimental 'signatures';
my ($email_full) = $copyright =~ / by ([^\n]*)/s;
my ($mydir, $myname);
BEGIN {
$0 =~ /(.*?)([^\/]+)\z/s or die "?";
($mydir, $myname) = ($1, $2);
}
use lib "$mydir/../lib";
use FP::Text::CSV qw(csv_file_to_rows);
use FP::JSON qw(to_json);
use FP::Hash qw(ziphash);
use FP::Div qw(identity);
use Chj::xIOUtil qw(stdout_utf8 stdin_utf8);
sub usage {
print STDERR map {"$_\n"} @_ if @_;
print "$myname file.csv file.mint
Convert CSV to JSON or Mint record literal syntax. It expects the
input file to have a title row, and uses the fields in that as the
field names (with Mint compatible mangling).
file.csv or file.mint can also be '-' for stdin or stdout,
respectively
Options:
--json output JSON (default)
--mint output Mint record literal syntax
--auto-numbers
treat strings that look like numbers as numbers
--auto-integers
strings that look like numbers and only have zeroes after
the dot are treated as integers; currently implies
--auto-numbers
--repl
open a repl instead of carrying out the identity
conversion
--conversion 'code'
code is Perl that must evaluate to a function that
receives the inputs and translate to the outputs.
Example code:
'sub(\$records) { groupByNumber(\$records, \"Quadrat\")}'
or
'sub(\$records) { groupByNumber(\$records, \"Quadrat\")->map(sub (\$group){ \$group->sort(on(hashkey(\"Frequency\"), \\&real_cmp))})}'
($email_full)
";
exit(@_ ? 1 : 0);
}
my $verbose = 0;
our ($opt_json, $opt_mint, $opt_auto_numbers, $opt_auto_integers, $opt_repl,
$opt_conversion);
GetOptions(
"verbose" => \$verbose,
"help" => sub {usage},
"json" => \$opt_json,
"mint" => \$opt_mint,
"auto-numbers" => \$opt_auto_numbers,
"auto-integers" => \$opt_auto_integers,
"repl" => \$opt_repl,
"conversion=s" => \$opt_conversion,
) or exit 1;
usage unless @ARGV == 2;
our $output_format
= $opt_json
? ($opt_mint ? usage "both --json and --mint given" : "JSON")
: ($opt_mint ? "Mint" : "JSON");
# Not sure this is ideal but people will be confused if giving
# --auto-integers and it doesn't do anything.
$opt_auto_numbers = 1 if $opt_auto_integers;
my $settings = {
output_format => $output_format,
auto_numbers => $opt_auto_numbers,
auto_integers => $opt_auto_integers,
};
my ($file_csv, $file_mint) = @ARGV;
#my $rows= csv_file_to_rows $file_csv;
sub convertfile ($fn) {
my $csvinput = $file_csv eq "-" ? stdin_utf8 : $file_csv;
my $rows = csv_file_to_rows($csvinput, { sep_char => ",", eol => "\n" });
my $titles = $rows->first;
my $body = $rows->rest;
my $body_hms = $body->map(sub ($row) { ziphash $titles, $row });
my $body_hms2 = $fn->($body_hms);
my $mint = "[\n" . $body_hms2->map(
sub ($v) {
to_json $v, $settings
}
)->strings_join(",\n")
. "\n]\n";
my $out;
if ($file_mint eq "-") {
$out = stdout_utf8;
} else {
open $out, ">:encoding(UTF-8)", $file_mint
or die "can't open file for writing: '$file_mint'";
}
print $out $mint or die "print: $!";
close $out or die "close($file_mint): $!";
}
sub groupByNumber ($records, $key) {
$records->sort(on(hashkey($key), \&real_cmp))
->group(on(hashkey($key), \&number_eq))
}
if ($opt_repl) {
require FP::Repl;
FP::Repl::repl();
} elsif ($opt_conversion) {
my $function = eval $opt_conversion;
die $@ if $@;
ref($function) eq "CODE"
or die
"expecting --conversion's argument to evaluate to a function, but got: $function";
convertfile($function);
} else {
convertfile(\&identity);
}
#use Chj::ruse;
#use Chj::Backtrace;