#!/usr/bin/perl

# csv2tsv 
#   Toshiyuki Shimono at Uhuru Corporation in Tokyo.
#   2015-09-28 , 2016-07-06

use 5.014 ; use strict ; use warnings ;  # Confirmed also for 5.010 
use Getopt::Std ; getopts "f:g:nwWq2", \my %o ;
use Text::CSV_XS ; 
use FindBin qw [ $Script ] ; 
use Term::ANSIColor qw[ :constants color ] ; $Term::ANSIColor::AUTORESET = 1 ; 

sub initOpt ( ) ;
sub main ( ) ; 

my $tTo = defined $o{f} ? eval qq[qq[$o{f}]] : "\t" ; # レコード(セル)に現れたタブ文字を何の文字列に置き換えるか
my $nTo = defined $o{g} ? eval qq[qq[$o{g}]] : "\n" ; # レコード(セル)に現れた改行文字を何の文字列に置き換えるか

my $replaceFlag = 1 if defined $o{f} || defined $o{g} || ! $o{n} ;
my ( $cntN , $fldN, $linesN ) = (0,0,0) ; # レコード中の改行文字の数 <-- - "\n" 以外の改行文字の対策はどうする?  perlport をよく読もう
my ( $cntT , $fldT, $linesT ) = (0,0,0) ; # レコード中のタブ文字の数

#END{ warn YELLOW "wer"} ;

initOpt ; 
main ; 
exit 0 ;

# 1. 読取りのハンドラの文字コードを決めたりする。
sub initOpt ( ) { 
  if ( @ARGV ) { 
  	pipe *STDIN , my $WH ; 
  	print {$WH} $_ while ( <> ) ; # <- やや思い処理になる可能性がある。
  	close $WH ;
  }
  binmode STDIN, ":encoding(utf8)" if ! $o{w} ; 
  binmode STDIN, ":encoding(cp932)" if $o{w} ; # <-- - "cp932" 以外が良い可能性は? 絵文字はどうするか?
  binmode STDOUT,":encoding(utf8)";
  $/ = "\r\n" if $o{W} ;
}

sub main ( ) { 
  print STDERR RED "Waiting CSV-formatted input from STDIN.. ($Script)  \n" if -t ; 
  eval { reading ( ) } ; 
  warn $@ if $@ ; # <-- CSV形式から少し違うと、END{}は実行を続けることはできるが、この行は実行しないようだ。この行は何の役に立つのか?
}

sub reading ( ) {
  my $li = 1 ; # CSV で読み込んでいるので、$. は2以上増えることがある。読み取る度に、 $li から $. 行目までと認識するため。
  my $csv = Text::CSV_XS -> new ( { binary => 1 } );
  while ( my $x = $csv -> getline( *STDIN ) ) { 
    my @F = @$x ; 
    # 入力レコード中にタブ文字か改行文字が現れた場合に、カウントし、表示する。
    if ( ! $o{n} ) { 
      my ( $cn, $ct ) ; 
      print STDERR BRIGHT_RED qq[Warning: "\\n" detected at input line $li - $.. ($Script)\n] if $cn = grep { m/\n/ } @F and ++ $linesN and $fldN += $cn ; 
      print STDERR BRIGHT_RED qq[Warning: "\\t" detected at input line $li - $.. ($Script)\n] if $ct = grep { m/\t/ } @F and ++ $linesT and $fldT += $ct ; 
    }

    # タブ文字と改行文字を置換する。(改行文字を任意の指定文字、タブ文字を任意の指定文字に変換するだけだが、ややこしいアルゴリズムを採用せざるを得ず)
    if ( $replaceFlag ) { 
      for ( @F ) { 
        my @tmp = split /(\t|\n)/ , $_ , -1 ; 
        $_ = join '' , map {  $_ eq "\t" ? do{ $cntT++ ; $tTo } : $_ eq "\n" ? do{ $cntN++ ;$nTo }: $_ } @tmp  ;
      }
    }

    # 中心的な処理
    print join "\t", @F ;  
    print "\n" ;

    # 出力各行の間に空行を挿入する場合の処理
    if ( $o{2} ) { 
        print "\n" ; 
    }
  }
  continue { 
    $li = $. + 1 ;
  }
  $csv->eof; # <-- - 必要か?

  return if $o{n} ; 
  print STDERR GREEN qq[Totally "\\n" appeared in $fldN record(s) in $linesN line(s) for output.] if $linesN ; 
  print STDERR GREEN qq[ $cntN count(s) of "\\n" in input.] if $cntN ; 
  print STDERR "\n" if $linesN || $cntN ; 
  print STDERR GREEN qq[Totally "\\t" appeared in $fldT record(s) in $linesT line(s) for output.] if $linesT ; 
  print STDERR GREEN qq[ $cntT count(s) of "\\t" in input.] if $cntT ; 
  print STDERR "\n" if $linesT || $cntT ; 
}

## ヘルプの扱い
sub VERSION_MESSAGE {}
sub HELP_MESSAGE {
    use FindBin qw[ $Script ] ; 
    $ARGV[1] //= '' ;
    open my $FH , '<' , $0 ;
    while(<$FH>){
        s/\$0/$Script/g ;
        print $_ if s/^=head1// .. s/^=cut// and $ARGV[1] =~ /^o(p(t(i(o(ns?)?)?)?)?)?$/i ? m/^\s+\-/ : 1;
    }
    close $FH ;
    exit 0 ;
}

=encoding utf8

=head1 

  $0 file.csv > file.tsv 
  $0 < file.csv > file.tsv 

  CSV 形式のファイルを TSV形式 に変換する。
  出力については、文字コード UTF-8 で改行コードは "\n" となる。


 注意点:

  内部ではText::CSV_XS のライブラリに依存している。
  "絵文字" に対応していないことに注意。(要改良)


 オプション:

   -n : 入力のレコード内に、タブ文字または改行文字があっても、警告を出さない(no check)
   -f str : 入力にタブ文字があれば、それを何に置き換えるかを文字列表現で指定する。
   -g str : 入力に改行文字があれば、それを何に置き換えるかを文字列表現で指定する。

   -w : 入力の文字コードを cp932  (シフトJIS) と見なす。
   -W : 入力の改行コードを \r\n と見なす。
   -2 : レコードの区切りを単一の \n ではなくて、2個続けた \n\n にする。CSVのセル内に改行文字がある場合に使うかもしれない。

  --help : この $0 のヘルプメッセージを出す。  perldoc -t $0 | cat でもほぼ同じ。
  --help opt : オプションのみのヘルプを出す。opt以外でも options と先頭が1文字以上一致すれば良い。

 開発上のメモ: 
   * (改行文字が、Unix 形式でない場合にも "自動的に" 対処するようにしたい。)
   * 改行文字やタブ文字を、置換した場合の文字列、さらに、その文字列が他の文字列と一緒になる場合の対策も、実装したい。
=cut
