#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use open qw/:std :utf8/;

use Getopt::Long qw(:config no_ignore_case);
use Pod::Usage;
use Term::ANSIColor qw/:constants/;
use List::Util qw/max/;

use Octets::To::Unicode;

my $parse_options_ok = GetOptions(
    'help|h' => \( my $help = 0 ),
    'man'    => \( my $man  = 0 ),

    'e|ext=s'          => \( my $ext          = 'pm,pl,plx,t' ),
    'i|interpreters=s' => \( my $interpreters = 'perl,perl5' ),
    'n|encodings=s'    =>
      \( my $encodings = $ENV{'RU-ENCODINGS'} // 'utf-8,cp1251,koi8-r' ),
    'b|in-branch' => \( my $in_branch ),
    'd|in-dir=s'  => \( my $in_dir ),
    'c|command=s' => \( my $command ),
    's|script=s'  => \( my $script ),
    'o|overall'   => \( my $overall ),
);

if ( !$parse_options_ok ) {
    pod2usage(2);
}
elsif ($help) {
    pod2usage(
        -sections => "NAME|SYNOPSIS|DESCRIPTION|OPTIONS|SUBCOMMANDS",
        -verbose  => 99
    );
}
elsif ($man) {
    pod2usage( -exitval => 0, -verbose => 2 );
}
elsif ( !defined($command) && !defined($script) ) {
    print STDERR "use: ru-utf8 -c 'command'\n";
}
else {

    my @encodings = split /,/, $encodings;

    my @files =
      @ARGV ? @ARGV
      : test_files(
        [
              $in_branch ? change_files_in_branch()
            : $in_dir    ? map( file_find($_), split /,/, $in_dir )
            :              change_files()
        ],
        $ext,
        $interpreters
      );

    my $max_length = max map length, @files;

    if ($overall) {

        my @tmp_files_and_encodes = map {
            my ( $unicode, $encoding ) = file_decode $_, \@encodings;
            ( my $tmp_file_in  = $_ ) =~ s!(\.[^/.]+)?$!.ru-utf8-in$&!n;
            ( my $tmp_file_out = $_ ) =~ s!(\.[^/.]+)?$!.ru-utf8-out$&!n;
            file_write $tmp_file_in,  $unicode;
            file_write $tmp_file_out, $unicode;
            [ $tmp_file_out, $_, $encoding, $tmp_file_in ]
        } @files;

        my $result;
        if ($command) {
			my $i = 0;
			my %x = map {
				$i++;
				(
					"o$i" => $_->[0],
					"x$i" => $_->[1],
					"e$i" => $_->[2],
					"f$i" => $_->[3],
				)
			} @tmp_files_and_encodes;
			
            ( my $duplicate_command = $command ) =~
s!\$(?<v>\w+)|\$\{(?<v>\w+)\}!exists $x{$+{v}}? $x{$+{v}}: $&!ge;

            $result = system $duplicate_command;
        }
        else {
            my @o = map $_->[0], @tmp_files_and_encodes;
            my @x = map $_->[1], @tmp_files_and_encodes;
            my @e = map $_->[2], @tmp_files_and_encodes;
            my @f = map $_->[3], @tmp_files_and_encodes;
            eval $script;
            $result = $@ ? do { warn $@; 1 } : 0;
        }

        if ($result) {
            unlink( $_->[0] ), unlink( $_->[3] ) for @tmp_files_and_encodes;
        }
        else {
            for (@tmp_files_and_encodes) {
                my ( $tmp_file_in, $file, $encoding, $tmp_file_out ) = @$_;
                file_encode $file, $encoding, file_read $tmp_file_out;
                unlink $tmp_file_in;
                unlink $tmp_file_out;
            }
        }

        exit $result;
    }

    my $result = 0;

    for my $file (@files) {

        ( my $tmp_file_in  = $file ) =~ s!(\.[^/.]+)?$!.ru-utf8-in$&!n;
        ( my $tmp_file_out = $file ) =~ s!(\.[^/.]+)?$!.ru-utf8-out$&!n;

        my ( $unicode, $encoding ) = file_decode $file, \@encodings;
        file_write $tmp_file_in,  $unicode;
        file_write $tmp_file_out, $unicode;

        print "$file  ", " " x ( $max_length - length $file ), GREEN, " in ",
          YELLOW,
          $encoding, RED, " ⟶ ", RESET, $tmp_file_in, "\n";

        my $res;
        if ( defined $command ) {
			my %x = (
				f => $tmp_file_in,
				o => $tmp_file_out,
				e => $encoding,
				x => $file,
			);
			
            ( my $duplicate_command = $command ) =~
              s!\$(?<v>\w+)|\$\{(?<v>\w+)\}!exists $x{$+{v}}? $x{$+{v}}: $&!ge;
            $res = system $duplicate_command;
        }
        else {
            my $x = $file;
            my $f = $tmp_file_in;
            my $o = $tmp_file_out;
            my $e = $encoding;
            eval $script;
            $res = $@ ? do { warn $@; 1 } : 0;
        }

        $result = max $res, $result;
        print WHITE, "[ ", RED, "FAIL", RESET, " # $? ", WHITE, " ]", RESET,
          "\n"
          if $?;

        file_encode $file, $encoding, file_read $tmp_file_out if !$?;
        unlink $tmp_file_in;
        unlink $tmp_file_out;
    }

    exit $result;
}

__END__

=encoding utf-8

=head1 NAME

B<ru-utf8> - утилита подмены файлов в национальных кодировках на файлы в utf-8.

=head1 VERSION

Version 0.04

=head1 SYNOPSIS

    ru-utf8 [-h] [--man] [<files> ...] [--ext exts] [--in-branch] [--in-dir dirs] [--command <command> | --script <script>]

=head1 DESCRIPTION

Утилита переводит файлы во временные в кодировке utf-8 (в /tmp) и после выполнения команды и их изменения переписывает обратно в определённой кодировке.

Обработать файлы командой shell:

	$ ru-utf8 -c 'echo $f $o $x $e'
	$ ru-utf8 -c 'perltidy $f -st > $o'
	
	$ ru-utf8 -o -c 'echo $f1 $o1 $x1 $e1 -- $f2 $o2 $x2 $e2'
	
Обработать файлы скриптом perl:
	
	$ ru-utf8 -s 'print "$f $o $x $e $unicode"'
	$ ru-utf8 -o -s 'print "@f @o @x @e"'

Есть 4 основные режима работы:

	# Обработать все изменённые, но ещё не закомиченные файлы:
	$ ru-utf8
	
	# Обработать изменённые и закомиченные файлы в ветке (branch-е):
	$ ru-utf8 --in-branch

	# Обработать файлы в директориях:
	$ ru-utf8 --in-dir .,/tmp/mydir

	# Обработать указанные файлы:
	$ ru-utf8 file1 /root/file2
	
С помощью опции -e (--ext) можно указать расширения файлов для форматирования (по умолчанию это pm,pl,plx,t):

	$ ru-utf8 -e pm,t
	
А для файлов, которые расширений не имеют, можно указать список интерпретаторов, указываемых в первой строке скрипта (#!/usr/bin/env perl):

	$ ru-utf8 -i perl,perl5
	
Так же можно указать кодировки и порядок в котором они будут проверяться:

	$ ru-utf8 -n cp1251,utf-8

`ru-utf8` возвращает максимальный код из возвращённых при запуске команды над файлами.

=head2 OPTIONS

=over 4

=item B<-h>, B<--help>

Показать помощь и выйти.

=item B<--man>

Распечатать мануал и завершиться.

=item B<-e> exts, B<--ext> exts

Список расширений через запятую.

По умолчанию: B<pm,pl,plx,t>.

Пустая строка обозначает любые расширения.

=item B<-i> interpreters, B<--interpreters> interpreters

Список интерпретаторов через запятую.

По умолчанию: B<perl,perl5>.

Пустая строка отменяет файлы без расширений.

=item B<-n> encodings, B<--encodings> encodings

Список кодировок через запятую.

По умолчанию берётся из переменной окружения B<RU-ENCODINGS>, а если она пуста, равняется: B<utf-8,cp1251,koi8-r>.

=item B<-b>, B<--in-branch>

Форматировать изменённые и закомиченные файлы в ветке (branch-е).

=item B<-d> dirs, B<--in-dir> dirs

Форматировать изменённые и закомиченные файлы в директориях.
Директории через запятую.

=item B<-с> command, B<--command> command

Команда.

Обязательный параметр, если параметр -s не указан.

=item B<-s> script, B<--script> script

Код perl.

Обязательный параметр, если параметр -c не указан.

=item B<-o>, B<--overall>

Указывает, что команда одна для всех файлов, а не для каждого по отдельности.

При этом обращение к файлам происходит как в шелле: $1, $2...

Например:

	ru-utf8 file1 file2 -c 'cat $1 $2'

=back

=head2 ARGS

=over 4

=item B<files>...

Файлы или директории с файлами, которые нужно отформатировать.

=back

=head1 LICENSE

⚖ B<GPLv3>

=head1 AUTHOR

Yaroslav O. Kosmina E<lt>darviarush@mail.ruE<gt>

=cut
