В 2000-х определилась тенденция переводить проекты в национальных кодировках в utf-8. Однако не везде их перевели одним махом, а решили рубить собаке хвост постепенно. В результате во многих проектах часть файлов c кодом в utf-8, а часть осталась в национальной кодировке (например, cp1251).

Поэтому я сделал утилиту ru-perltidy, которая определяет кодировку файлов, конвертирует в utf-8, а после форматирования переводит обратно.

Из вкусносей тут то, что ru-perltidy может отформатировать только изменённые в репозитории git файлы (Рис.1).

Рис.1. Установка утилиты и форматирование ею только изменённых файлов.

Если же файлы были закомичены: не беда — укажите опцию --in-branch и ru-perltidy отформатирует все изменённые файлы в ветке воспользовавшись командой :

git diff --name-only --diff-filter=AM origin/master...

Имеет команда и несколько иных опций:

# Отформатировать указанные файлы perltidy:
	$ ru-perltidy file1 file2

	# Указать кодировку:
	$ ru-perltidy file1 file2 -e utf-8,cp1251

	# Форматирует только изменённые файлы в репозитории git:
	$ ru-perltidy

	# Форматирует изменённые файлы в ветке 
  # (на случай, если забыл отформатировать перед комитом):
	$ ru-perltidy --in-branch

	# Указать расширения файлов:
	$ ru-perltidy --ext 'pl,pm,'

Впрочем может понадобится и другая операция, а не только perltidy. Поэтому я добавил в пакет утилиту ru-utf8. Она аналогична ru-perltidy, но требует указания обязательной опции в которой передаётся команда для обработки переведённых в utf-8 файлов. А после выполнения команды файлы переписывается обратно, в той же кодировке, в которой и были.

# Перевести файлы во временные в кодировке utf-8 (в /tmp) и после выполнения команды 
  # и их изменения переписать обратно в определённой кодировке:
	# (тут $1 - первый файл, $2 - второй и т.д., $* - все файлы через пробел. 
  # Так же работают подстановки ${1} и т.д.)
	$ ru-utf8 file1 file2 -c 'perltidy $1 -st > /tmp/1 && mv /tmp/1 $1'

Алгоритм определения кодировки файла

Для определения кодировки файла используется следующий алгоритм:

  1. Файл считывается и декодируется в указанных кодировках (по-умолчанию это utf-8, cp1251 и koi8-r).

  2. Затем в декодированных вариантах текста подсчитываются длины русских слов начинающиеся с прописной или строчной буквы и далее из строчных.

  3. Длины русских слов не подпадающих под указанный критерий вычитаются из результата.

  4. Набравший наибольший балл декодированный результат и будет правильным.

Вот реализация этого алгоритма на perl:

#@category Кодировка

# Определяет кодировку.
# В koi8-r и в cp1251 большие и малые буквы как бы поменялись местами, 
# поэтому у правильной кодировки вес будет больше
sub bohemy($) {
    my ($s) = @_;
    my $c = 0;
    while ( $s =~ /[а-яё]+/gi ) {
        my $x = $&;
        if   ( $x =~ /^[А-ЯЁа-яё][а-яё]*$/ ) { $c += length $x }
        else                                 { $c -= length $x }
    }
    $c;
}

# Определить кодировку и декодировать
sub decode(@) {
    my ( $octets, $encodings ) = @_;

    return if !length $octets;

    utf8::encode($octets) if utf8::is_utf8($octets);

    $encodings //= [qw/utf-8 cp1251 koi8-r/];

    my @x = grep length $_->[0], map {

		    # TODO: В случае ошибки Encode::decode помещает пустую строку в свой
		    # второй аргумент. Сейчас это исправлено копированием значения в
        # дополнительную переменную, но было бы неплохо разобраться в причине.
        my $save = $octets;
        eval { [ Encode::decode( $_, $save, Encode::FB_CROAK ), $_ ] };
    } @$encodings;

    my ( $unicode, $mem_encoding );
    ( $unicode, $mem_encoding ) = @{ $x[0] } if @x == 1;

    if ( @x > 1 ) {
        ( $unicode, $mem_encoding ) =
          @{ ( sort { bohemy( $b->[0] ) <=> bohemy( $a->[0] ) } @x )[0] };
    }

    wantarray ? ( $unicode, $mem_encoding ) : $unicode;
}

Заключение

Во время разработки у нас накапливается множество мест за которыми приходится следить и исправлять их рутинными, но трудоёмкими действиями. Поэтому, если такие места невозможно, по каким-то причинам, убрать из проекта, то лучше слежку и связанные с ними действия автоматизировать.

Ссылки

  1. Модуль Octets::To::Unicode на CPAN / https://metacpan.org/pod/Octets::To::Unicode.

  2. ru-perltidy на github / https://github.com/darviarush/perl-octets-to-unicode.