package Text::TSV;
use strict;
use warnings;

sub new {
    my ($pkg) = @_;
    return bless { eol=>"\n", eof=>0 }, $pkg;
}

sub print {
    my ($self, $out, $f) = @_;
    my $line = join("\t", map { escape_tsv($_) } @$f) . $self->{eol};
    my $ok = print $out $line;
    if (!$ok) {
        $self->{error} = $!;
    }
    return $ok;
}

sub getline {
    my ($self, $in) = @_;
    my $old_err = $!;
    undef $!;
    my $line = <$in>;
    if (!defined $line && $!) {
        $self->{error} = $!;
        return undef;
    }
    $! = $old_err;
    if (!defined $line) {
        $self->{eof} = 1;
        return undef;
    }
    $line =~ s/\r?\n\z//s;
    my @f = map { unescape_tsv($_) } split /\t/, $line;
    return \@f;
}

sub eof {
    my ($self) = @_;
    return $self->{eof};
}

sub eol {
    my ($self, $eol) = @_;
    $self->{eol} = $eol;
    return;
}

sub error_diag {
    my ($self) = @_;
    return $self->{error};
}

my %tsv_escape_map = (
    "\\" => "\\",
    "\t" => "t",
    "\n" => "n",
    "\r" => "r",
    "\0" => "0",
);
my %tsv_unescape_map = map { $tsv_escape_map{$_} => $_ } keys %tsv_escape_map;

sub escape_tsv {
        my $v = shift;
        return "\\" unless defined $v;
        for ($v) {
                s/([\\\t\n\r\0])/"\\".$tsv_escape_map{$1}/gse;
        }
        return $v;
}

sub unescape_tsv {
    my $v = shift;
    return undef if $v eq "\\";
    for ($v) {
        s/\\(.)/$tsv_unescape_map{$1}/gse;
    }
    return $v;
}

1
