# # Copyright (c) 2015 Christian Jaeger, copying@christianjaeger.ch # # This is free software, offered under either the same terms as perl 5 # or the terms of the Artistic License version 2 or the terms of the # MIT License (Expat version). See the file COPYING.md that came # bundled with this file. # =head1 NAME FP::Text::CSV - functional interface to Text::CSV =head1 SYNOPSIS use FP::Text::CSV qw(csv_line_xparser csv_fh_to_rows csv_file_to_rows rows_to_csv_fh rows_to_csv_file); my $csvparams= +{sep_char=> ";", eol=> "\n"}; # $csvparams and any of its entries are optional, # defaults are taken from $FP::Text::CSV::defaults use Method::Signatures; # func use Chj::xopen qw(xopen_read xopen_write); use FP::List; use FP::Stream; use FP::Equal 'is_equal'; mkdir ".tmp"; # -- Output: --- my $rows= cons [ "i", "i^2" ], stream_iota->map(func ($i) { [ $i, $i*$i ] })->take(100); rows_to_csv_fh (Keep($rows), xopen_write(".tmp/a1.csv"), $csvparams); # or rows_to_csv_file ($rows, ".tmp/a2.csv", $csvparams); # -- Input: --- my $p= csv_line_xparser $csvparams; my @vals= &$p("1;2;3;4\n"); is_equal \@vals, [1,2,3,4]; my $itemstream1= csv_fh_to_rows(xopen_read(".tmp/a1.csv"), $csvparams); # or my $itemstream2= csv_file_to_rows(".tmp/a2.csv", $csvparams); is_equal $itemstream1, $itemstream2; is_equal $itemstream2->first, [ "i", "i^2" ]; is_equal $itemstream2->second, [ 0, 0 ]; is_equal $itemstream2->ref(10), [ 9, 81 ]; =head1 DESCRIPTION Handle CSV input and output in the form of functional streams (lazily computed linked lists). =head1 NOTE This is alpha software! Read the package README. =cut package FP::Text::CSV; @ISA="Exporter"; require Exporter; @EXPORT=qw(); @EXPORT_OK=qw( new_csv_instance csv_line_xparser csv_fh_to_rows csv_file_to_rows csv_printer rows_to_csv_fh rows_to_csv_file ); %EXPORT_TAGS=(all=>[@EXPORT,@EXPORT_OK]); use strict; use warnings; use warnings FATAL => 'uninitialized'; use FP::List ":all"; use FP::Lazy ":all"; use FP::Stream 'Weakened', 'weaken'; use Text::CSV; use FP::HashSet 'hashset_union'; use Chj::xopen 'xopen_read'; our $defaults= +{ binary => 1, sep_char=> "\t", eol=> "\r\n", }; sub params ($) { my ($maybe_params)=@_; defined $maybe_params ? hashset_union($maybe_params, $defaults) : $defaults } sub new_csv_instance (;$) { my ($maybe_params)=@_; Text::CSV->new(params $maybe_params) } sub csv_line_xparser (;$) { my ($maybe_params)=@_; my $csv= new_csv_instance $maybe_params; sub ($) { my ($line)=@_; $csv->parse($line) or die "CSV parsing failure"; # XX how to get error message from Text::CSV? $csv->fields } } sub csv_fh_to_rows ($;$) { my ($in, $maybe_params)=@_; my $csv= new_csv_instance ($maybe_params); my $next; $next= sub { my $next=$next; lazy { if (my $row= $csv->getline ($in)) { # XX error checks? cons $row, &$next; } else { $in->xclose; null } } }; &{Weakened $next} } sub csv_file_to_rows ($;$) { my ($path, $maybe_params)=@_; my $in= xopen_read $path; binmode($in, ":encoding(utf-8)") or die "binmode"; csv_fh_to_rows $in, $maybe_params } # -- Output: --- sub csv_printer ($;$) { my ($fh, $maybe_params)=@_; my $csv= new_csv_instance ($maybe_params); sub { my ($row)=@_; $csv->print($fh, $row) or die "could not write CSV row: ".$csv->error_diag; # XX ok? } } use FP::Stream "stream_for_each"; sub rows_to_csv_fh ($$;$) { my ($s, $fh, $maybe_params)=@_; weaken $_[0]; stream_for_each csv_printer($fh, $maybe_params), $s } use Chj::xtmpfile; sub rows_to_csv_file ($$;$) { my ($s, $path, $maybe_params)=@_; weaken $_[0]; my $out= xtmpfile $path; rows_to_csv_fh ($s, $out, $maybe_params); $out->xclose; $out->xputback (0666 & ~umask); } 1