The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
####
# BsvWriter.pm:  A Perl module defining a class for generating BSV data from
# a list of field names and a list of references to hashes that encapsulate
# records.  The class provides a method for returning the BSV data as a
# reference to an array of strings, and a method for writing the BSV data to
# a file.
#
####
#
# Copyright 2010 by Benjamin Fitch.
#
# This library is free software; you can redistribute it and/or modify it
# under the same terms as Perl itself.
####
package Text::BSV::BsvWriter;

use 5.010001;
use strict;
use warnings;
use utf8;

use English "-no_match_vars";
use Hash::Util ("lock_keys");
use List::Util ("first", "max", "min", "sum");
use Scalar::Util ("looks_like_number");

use Text::BSV::BsvParsing;
use Text::BSV::Exception;

# Version:
our $VERSION = '1.04';

# Constants:
my $POUND     = "#";
my $SQ        = "'";
my $DQ        = "\"";
my $SEMICOLON = ";";
my $CR        = "\r";
my $LF        = "\n";
my $SPACE     = " ";
my $EMPTY     = "";
my $TRUE      = 1;
my $FALSE     = 0;

# Constructor:
sub new {
    my ($class, $field_names, $records) = @_;
    my %bsv_writer;

    unless (scalar(@{ $field_names }) >= 2) {
        die Text::BSV::Exception->new(
          $Text::BSV::Exception::ILLEGAL_ARGUMENT,
          "There must be at least two field names.");
    } # end unless

    unless (scalar(@{ $records }) >= 1) {
        die Text::BSV::Exception->new(
          $Text::BSV::Exception::ILLEGAL_ARGUMENT,
          "There must be at least one record.");
    } # end unless

    # Bless the hash into the class:
    bless \%bsv_writer, $class;

    # Restrict the hash keys:
    lock_keys(%bsv_writer, "_rows");

    # Generate the header row:
    push @{ $bsv_writer{"_rows"} }, generate_header_row($field_names);

    # Generate the records:
    for my $record (@{ $records }) {
        push @{ $bsv_writer{"_rows"} }, generate_row($record, $field_names);
    } # next $record

    # Return the object:
    return \%bsv_writer;
} # end constructor

# Methods:

sub get_rows {
    return $_[0]->{"_rows"};
} # end sub

sub write_to_file {
    my $bsv_writer = $_[0];
    my $file_path = $_[1];

    if (open my $BSV_FILE, ">:utf8", $file_path) {
        for my $row (@{ $bsv_writer->{"_rows"} }) {
            say $BSV_FILE $row;
        } # next $row

        close $BSV_FILE;
    }
    else {
        die Text::BSV::Exception->new($Text::BSV::Exception::IO_ERROR,
          "Couldn't open $DQ$file_path$DQ for writing.");
    } # end if
} # end sub

# Module return value:
1;
__END__

=head1 NAME

Text::BSV::BsvWriter - generates BSV data from a list of field names and a
list of references to hashes that encapsulate records.  Provides a method
for returning the BSV data as a reference to an array of strings, and a
method for writing the BSV data to a file.

=head1 SYNOPSIS

  use Text::BSV::BsvWriter;
  use Text::BSV::Exception;

  # Create a Text::BSV::BsvWriter instance:
  my $bsv_writer;

  eval {
      $bsv_writer = Text::BSV::BsvWriter->new($field_names, $records);
  };

  if ($EVAL_ERROR) {
      if ($EVAL_ERROR->get_type()
        == $Text::BSV::Exception::ILLEGAL_ARGUMENT) {
          say STDERR "Bad arguments passed to the Text::BSV::BsvWriter "
            . "constructor:  " . $EVAL_ERROR->get_message();
          exit(1);
      }
      else {
          say STDERR "An unknown exception occurred:  "
            . $EVAL_ERROR->get_message();
          exit(1);
      } # end if
  } # end if

  # Write the BSV data generated by the constructor to a file:
  eval {
      $bsv_writer->write_to_file($file_path);
  };

  if ($EVAL_ERROR) {
      say STDERR $EVAL_ERROR->get_message();
      exit(1);
  } # end if

  # Get the BSV data rows in case you want to do something with them other
  # than writing them to a file:
  my $rows = $bsv_writer->get_rows();

=head1 DESCRIPTION

This module defines a class for generating BSV data from a list of field
names and a list of references to hashes that encapsulate records.  The
class provides a method for returning the BSV data as a reference to an
array of strings, and a method for writing the BSV data to a file.

For a complete specification of the BSV (Bar-Separated Values) format, see
F<bsv_format.txt>.

In addition to the class-name argument, which is passed in automatically
when you use the C<Text::BSV::BsvWriter-E<gt>new()> syntax, the
constructor takes a reference to an array of field names and a reference to
an array of references to hashes encapsulating the records.

The constructor returns a reference to a Text::BSV::BsvWriter object,
which is implemented internally as a hash.  All functionality is exposed
through methods.

  NOTE:  This module uses the Text::BSV::Exception module for error
  handling.  When an error occurs during the execution of a method
  (including the constructor), the method creates a new
  Text::BSV::Exception object of the appropriate type and then passes
  it to "die".  When you call the constructor or a method documented
  to throw an exception, do so within an "eval" statement and then
  query $EVAL_ERROR ($@) to catch any exceptions that occurred.  For
  more information, see the documentation for Text::BSV::Exception.

=head1 PREREQUISITES

This module requires Perl 5 (version 5.10.1 or later), the
Text::BSV::BsvParsing module, and the Text::BSV::Exception module.

=head1 METHODS

=over

=item Text::BSV::BsvWriter->new($field_names, $records);

This is the constructor.  If the field names are not unique or do not
match the keys in each hash, or if there are not at least two fields
and at least one record, the constructor throws an exception of type
$Text::BSV::Exception::ILLEGAL_ARGUMENT.

=item $bsv_writer->get_rows();

Returns a reference to an array of rows (including the header row)
that compose the BSV data.  The rows returned by this method do not
have any end-of-line characters added.

=item $bsv_writer->write_to_file();

Takes the path to a BSV file (which will be overwritten if it already
exists) and writes the BSV data generated by the constructor to the
file.  If the file cannot be written to, the method throws an
exception of type $Text::BSV::Exception::IO_ERROR.

=back

=head1 AUTHOR

Benjamin Fitch, <blernflerkl@yahoo.com>

=head1 COPYRIGHT AND LICENSE

Copyright 2010 by Benjamin Fitch.

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut