perltidy и cp1251
В 2000-х определилась тенденция переводить проекты в национальных кодировках в utf-8. Однако не везде их перевели одним махом, а решили рубить собаке хвост постепенно. В результате во многих проектах часть файлов c кодом в utf-8, а часть осталась в национальной кодировке (например, cp1251).
Поэтому я сделал утилиту ru-perltidy, которая определяет кодировку файлов, конвертирует в utf-8, а после форматирования переводит обратно.
Из вкусносей тут то, что ru-perltidy может отформатировать только изменённые в репозитории git файлы (Рис.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'
Алгоритм определения кодировки файла
Для определения кодировки файла используется следующий алгоритм:
Файл считывается и декодируется в указанных кодировках (по-умолчанию это utf-8, cp1251 и koi8-r).
Затем в декодированных вариантах текста подсчитываются длины русских слов начинающиеся с прописной или строчной буквы и далее из строчных.
Длины русских слов не подпадающих под указанный критерий вычитаются из результата.
Набравший наибольший балл декодированный результат и будет правильным.
Вот реализация этого алгоритма на 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;
}
Заключение
Во время разработки у нас накапливается множество мест за которыми приходится следить и исправлять их рутинными, но трудоёмкими действиями. Поэтому, если такие места невозможно, по каким-то причинам, убрать из проекта, то лучше слежку и связанные с ними действия автоматизировать.
Ссылки
Модуль Octets::To::Unicode на CPAN / https://metacpan.org/pod/Octets::To::Unicode.
ru-perltidy на github / https://github.com/darviarush/perl-octets-to-unicode.