#!/usr/bin/perl

#  expandtab
#   2021-06-11(fri) , 2022-10-19(wed) , 2023-03-22(wed)

use 5.014 ; 
use strict ; 
use warnings ; # also confirmed on 5.011 5.014 5.018  
use FindBin qw [ $Bin $Script ] ; 
use Getopt::Std ; 
use Getopt::Long qw [ GetOptions :config bundling no_ignore_case pass_through ] ; 
use Term::ANSIColor qw [ :constants color ]  ; $Term::ANSIColor::AUTORESET = 1 ; 
use List::Util qw [ max min ] ;
BEGIN { 
  *width = eval'use Text::VisualWidth::UTF8 qw[width];1'? *Text::VisualWidth::UTF8::width : sub{length $_[0]} ; 
}  

GetOptions delcolor => \my$delcolor ; # 単に色を消すだけの、おまけの機能。
GetOptions right => \my$right ; # 右揃えにする指定。
GetOptions 'keep=s' => \my$keep ; # \r を入力から除去しない場合は, --keep CRLF と指定する。
$keep //= '' ; 
getopts 'C:D:b:c:fi:r:s:x' => \my %o ;
& demo if exists $o{D} ; 

sub delColor ( $ ) {  $_[0] =~ s{ \e\[ [\d;]* m }{}xmsgr }  # ASCIIエスケープシーケンスによる着色を消す。
if ( $delcolor ) { while ( <> ) { print delColor $_ } ; exit } # 単に色を取るというお節介な機能。下でも使う。

$o{b} //= ' ' ; # 間に埋める文字
$o{c} //= ' ' ; # 列間で、列の直前に入れる文字
$o{i} //= "\t" ; # 入力の区切り文字
$o{r} //= 0    ; # 最も右側の列を何列について、文字幅の計算の対象外とするかの 値の指定

## 値の読取り
my @C ; # セルの値
my @L ; # ビジュアルな長さ
my $row = 0 ; # 行番号
my $cmax = 0 ; # 最大の列数
while ( <> ) { 
  chomp ; 
  s/\r$//o unless $keep eq 'CRLF' ;
  $C [ $row ] = [ my @F = split /$o{i}/, $_ , -1 ] ; 
  $L [ $row ] = [ map { width ( delColor $_ ) } @F ]  ;  # <-- - length/width
  $cmax = @F if @F > $cmax ; # 列数の最大値
  $row ++ 
}

## 各列において、Visual幅を算出する。 cmw は The Column's Max Width 
#my @Lr ; for ( 0 .. $#L ) { @{$Lr[$_]} = @{$L[$_]} ; say RED "@{$L[$_]}"; splice @{$Lr[$_]} , -$o{r} ; say BOLD RED  "@{$Lr[$_]}";  } ;
my @Lr ; for ( 0 .. $#L ) { @{$Lr[$_]} = @{$L[$_]} ; splice @{$Lr[$_]} , - min ( $o{r} , scalar @{$Lr[$_]} ) } 
my @cmw = map { my $c = $_ ; max map { $Lr[$_][$c] // 0 } 0 .. $row - 1 }  0 .. $cmax - 1 ; 
#undef @Lr ; 

# -x の指定があれば、less に渡す引数(-xによるタブ位置の指定)のみを出力して、終了。
if ( $o{x} ) { my $s = 0 ; say "-x",join "," , map {$s += $_ + 1 } @cmw ; exit } 

## 出力
for my $r ( 0 .. $row -1 ) { 
  my @oneRow ; # ひとつの各行に、どんな列値を出すかを格納。
  my $spaceSpare = 0 ; # 「柔軟な伸び縮み」をするための仕組みを導入している。
  for my $c ( 0 .. ( $o{f} ? $#cmw : $#{$C[$r]}) ) { 
  #for my $c ( 0 ..  $#{$C[$r]} ) { 
    ( $C[$r][$c] , $L[$r][$c] ) = ( '' , 0 ) if  ! defined $C[$r][$c] ; #! $L[$r][$c] ; #
    my $space = exists $o{s} ? min $cmw[$c] , $o{s} : $cmw[$c] ; # 仮の割り当て空間(半角何文字分にするか)の長さ
    my $addlen = $space + $spaceSpare - $L[$r][$c]; # 半角空白文字を何個ほど追加するか; --# if $cmw[$c] > $o{s}
    $spaceSpare = $addlen < 0 ? $addlen : 0 ; # 0 + min 0 , $addlen ; 
    my $addStr = $o{b} x max 0, $addlen ; # (前か後ろに)追加する文字列 (通常は、半角空白文字の連続)
    push @oneRow, $right ? $addStr . $C[$r][$c] : $C[$r][$c] . $addStr  ; # $o{b} : 間に埋める文字列。未指定なら半角空白' '。
    #push @oneRow, $tmp ; 
  }
  say join $o{c} , @oneRow ; # $o{c} : 列間で、列の直前に入れる文字。未指定なら半角空白 ' '。(connector)
}
exit 0 ;

sub demo () {
  #print $0 ; exit ;
  $o{D} //= '' ;
  #@ARGV = @argv ; getopts 'D:' , \my%o ; 
  if ( $o{D} eq 1 ) { 
    say YELLOW '>  ' , my $cmd = "$0 @ARGV" ;
    open my $FH, '|-', $0, @ARGV ; #"$Bin/$Script @argv" ; 
    print {$FH} join "\n" , map { join "\t" , map { int exp rand(8)} 1..6 } 1..3 ; 
  }
  if ( $o{D} eq 2 ) { 
    open my $FH, '|-' ,  "tr [0-9] あいうえおかきくけこ | $0 -c '|'" ;
    print {$FH} join "\n" , map { join "\t" , map { int exp rand(12)} 1..6 } 1..3 ; 
  }
  if ( $o{D} eq 3 ) { 
    say YELLOW '>　' , my $cmd = "$0 -c '|' -s9 " ; 
    open my $FH, '|-' ,  "tr [0-9] あいうえおかきくけこ | $cmd ";
    print {$FH} join "\n" , map { join "\t" , map { int exp rand(12)} 1..6 } 1..3 ; 
  }
  if ( $o{D} eq 4 ) { 
    #my $cmd = "perl -e ''" ; 

    open my $FH , '|-' , "$0 -c'|' -b. -f " ; 
    say {$FH} join "\t" , map { "$_"x(1+rand 3) } 1 .. $_ for 0..5 ;
  }
  exit ;
}


## ヘルプの扱い
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 

  入力をTSVと見なして、同じ列は縦に揃えて空白文字で埋めて出力する。
 
 [オプション] :

  -b char ; 間に埋める文字列。未指定なら半角空白 ' ' 。例として　 -b "-" 。betweenのbのつもり。
  -c char ; 列間で、列の直前に入れる文字。未指定なら半角空白 ' ' 。例として　 -c "|" 。connectorのcのつもり。
  -f : ある行が列の数が少ないときに、最も列数の多い行に合わせて、列の数を増やす指定。
  -i delim : 区切り文字の変更。未指定なら、タブ文字。(出力は空白文字うめであり、変えない。)
  -r N  : 最も右側の列を何列について、文字幅の計算の対象外とするかの 値の指定。未指定なら 0。
  -s N : (実験的)出力の各セルにおいて、長すぎるセルを可能であれば埋めた空白をさらに幅何文字分(半角相当)まで縮めるか指定。
  -x : less に渡す引数(-xによるタブ位置の指定)のみを出力して終了。時には、とても便利。
  -D num : このコマンドの動作についてのデモを表示して終了。numは1〜4。(開発上テストでも使う。)

  --delcolor : 単に入力の着色(ASCIIエスケープシーケンスによるもの)を除去。テスト機能であり、おまけの機能である。
  --right : 右揃えにする。この指定が無いと、左揃えである。
  --keep 'CRLF' : Windows形式だと改行文字が CR LF となるが、そのCRの部分を取り除く操作を、このオプション指定だと抑制する(テスト目的などを想定)。


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

  開発メモ: 
   * 行列として変数Cを参照するときに値が未定義の時にどうすれば良いか。
   * INT{ALRM}を使って15秒ごとに、一旦出力する機能を付けたい。
   * Ctrl+ZやCtrl+Cに対する機能を付けたい。
   * -x による less の -xに渡す数の配列を STDERR に出力したい。その場合、2次情報の出力を抑制する -20も実装することになるであろう。
   * --rightにおいて、特定の列だけ、または、特定の列以外を右寄せに出来ないだろうか?
   * 出力の最後が余分に1行出力されている問題が発生中。

<<<<<<< HEAD
=======
  Text::VisualWidth のインスートルがあらかじめ必要である。

>>>>>>> 2ce37e4456311bbf10b7f2950ac1fb1d4255448b
=cut
