The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Catmandu::Exporter::CSV;

use Catmandu::Sane;

our $VERSION = '0.9504';

use Text::CSV;
use Moo;
use namespace::clean;

with 'Catmandu::Exporter';

has csv          => (is => 'ro', lazy => 1, builder => 1);
has sep_char     => (is => 'ro', default => sub { ',' });
has quote_char   => (is => 'ro', default => sub { '"' });
has escape_char  => (is => 'ro', default => sub { '"' });
has always_quote => (is => 'ro');
has header       => (is => 'lazy', default => sub { 1 });

has fields => (
    is      => 'rw',
    trigger => sub {
        my ($self, $fields) = @_;
        $self->{fields} = _coerce_list($fields);
        if (ref $fields and ref $fields eq 'HASH') {
            $self->{header} = [
                map { $fields->{$_} // $_ } @{$self->{fields}} 
            ];
        }
    },
);

sub _coerce_list {
    my $fields = $_[0];
    if (ref $fields eq 'ARRAY') { return $fields }
    if (ref $fields eq 'HASH')  { return [sort keys %$fields] }
    return [split ',', $fields];
}

sub _build_csv {
    my ($self) = @_;
    Text::CSV->new({
        binary => 1,
        eol => "\n",
        sep_char => $self->sep_char,
        always_quote => $self->always_quote,        
        quote_char => $self->quote_char ? $self->quote_char : undef,
        escape_char => $self->escape_char ? $self->escape_char : undef,
    });
}

sub add {
    my ($self, $data) = @_;
    return undef unless defined $data;
    $self->fields([ sort keys %$data ]) unless $self->fields;
    my $fields = $self->fields;
    my $row = [map {
        my $val = $data->{$_} // "";
        $val =~ s/\t/\\t/g;
        $val =~ s/\n/\\n/g;
        $val =~ s/\r/\\r/g;
        $val;
    } @$fields];
    my $fh = $self->fh;
    # We need to wait for the first row that can be printed to provide us
    # with an header...
    if (!defined($self->{__seen_header__}) && $self->header) {
        $self->csv->print($fh, ref $self->header ? $self->header : $fields);
    }
    $self->{__seen_header__} = 1;
    $self->csv->print($fh, $row);
}

1;

__END__

=pod

=head1 NAME

Catmandu::Exporter::CSV - a CSV exporter

=head1 SYNOPSIS

    use Catmandu::Exporter::CSV;

    my $exporter = Catmandu::Exporter::CSV->new(
                fix => 'myfix.txt',
                quote_char => '"',
                sep_char => ',',
                escape_char => '"' ,
                always_quote => 1,
                header => 1);

    $exporter->fields("f1,f2,f3");
    $exporter->fields([qw(f1 f2 f3)]);

    $exporter->add_many($arrayref);
    $exporter->add_many($iterator);
    $exporter->add_many(sub { });

    $exporter->add($hashref);

    printf "exported %d objects\n" , $exporter->count;

=head1 DESCRIPTION

This C<Catmandu::Exporter> exports items as rows with comma-separated values
(CSV). Serialization is based on L<Text::CSV>. A header line with field names
will be included if option C<header> is set. Field names can be read from the
first item exported or set by option C<fields>. Newlines and tabulator values
are in field values are escaped as C<\n>, C<\r>, and C<\t>.

=head1 CONFIGURATION

=over

=item file

Write output to a local file given by its path or file handle.  Alternatively a
scalar reference can be passed to write to a string and a code reference can be
used to write to a callback function.

=item fh

Write the output to an L<IO::Handle>. If not specified,
L<Catmandu::Util::io|Catmandu::Util/IO-functions> is used to create the output
handle from the C<file> argument or by using STDOUT.

=item fix

An ARRAY of one or more fixes or file scripts to be applied to exported items.

=item encoding

Binmode of the output stream C<fh>. Set to "C<:utf8>" by default.

=item sep_char

Column separator (C<,> by default)

=item quote_char

Quotation character (C<"> by default)

=item escape_char

Character for escaping inside quoted field (C<"> by default)

=item fields

List of fields to be used as columns, given as array reference, comma-separated
string, or hash reference.

=item header

Include a header line with the column names, if set to C<1> (the default).
Custom field names can be supplied as hash reference. By default field names
are used as as column names.

=back

=head1 METHODS

See L<Catmandu::Exporter>, L<Catmandu::Addable>, L<Catmandu::Fixable>,
L<Catmandu::Counter>, and L<Catmandu::Logger> for a full list of methods.

=head1 SEE ALSO

L<Catmandu::Importer::CSV>, L<Catmandu::Exporter::Table>
L<Catmandu::Exporter::XLS>

=cut