File Coverage

blib/lib/Aion/Format.pm
Criterion Covered Total %
statement 180 180 100.0
branch 50 52 96.1
condition 12 12 100.0
subroutine 48 48 100.0
pod 25 25 100.0
total 315 317 99.3


line stmt bran cond sub pod time code
1             package Aion::Format;
2             # Библиотека форматирования и вывода
3              
4 1     1   120640 use common::sense;
  1         1  
  1         6  
5              
6             our $VERSION = "0.1.3";
7              
8             require POSIX;
9             require Term::ANSIColor;
10 1     1   544 use Guard qw//;
  1         533  
  1         26  
11              
12 1     1   5 use Exporter qw/import/;
  1         1  
  1         133  
13             our @EXPORT = our @EXPORT_OK = grep {
14             *{$Aion::Format::{$_}}{CODE} && !/^(_|(NaN|import)\z)/n
15             } keys %Aion::Format::;
16              
17              
18             #@category Вывод структур
19              
20 1     1   2019 use DDP qw//;
  1         29342  
  1         2218  
21              
22             sub _extends_ddp_properties {
23 2     2   6 my ($properties) = @_;
24             +{
25             colored => 1,
26             deparse => 1,
27             show_unicode => 1,
28             show_readonly => 1,
29             print_escapes => 1,
30             show_refcount => 1,
31 2         779 show_memsize => eval { require Devel::Size; 1 },
  2         793  
32             caller_info => 1,
33             #output => 'stdout',
34             unicode_charnames => 1,
35             %$properties,
36             class => {
37             expand => "all",
38             inherited => "all",
39             show_reftype => 1,
40 2         8 %{$properties->{class}},
  2         44  
41             },
42             }
43             }
44              
45             # p с предустановленными опциями
46             sub p($;%) {
47 1     1 1 9 my ($arg, %properties) = @_;
48 1         2 %properties = %{_extends_ddp_properties(\%properties)};
  1         5  
49 1         12 DDP::p $arg, %properties
50             }
51              
52             # np с предустановленными опциями
53             sub np($;%) {
54 1     1 1 4302 my ($arg, %properties) = @_;
55 1         3 %properties = %{_extends_ddp_properties(\%properties)};
  1         4  
56 1         11 DDP::np $arg, %properties
57             }
58              
59             #@category Ловушки
60              
61             # Ловушка для STDERR
62             sub trapperr(&) {
63 4     4 1 15579 my $sub = shift;
64 4         13 local *STDERR;
65 4     4   55 open STDERR, '>:utf8', \my $f; my $guard = Guard::guard { close STDERR };
  4         27  
  4         29  
66 4         12 $sub->();
67 4         27149 undef $guard;
68 4 50       27 utf8::decode($f) unless utf8::is_utf8($f);
69 4         36 $f
70             }
71              
72             # Ловушка для STDOUT
73             sub trappout(&) {
74 5     5 1 180156 my $sub = shift;
75 5         43 local *STDOUT;
76 5     5   65 open STDOUT, '>:utf8', \my $f; my $guard = Guard::guard { close STDOUT };
  5         33  
  5         357  
77 5         15 $sub->();
78 4         36 undef $guard;
79 4 50       28 utf8::decode($f) unless utf8::is_utf8($f);
80 4         33 $f
81             }
82              
83             #@category Цвет
84              
85             # Колоризирует текст escape-последовательностями: coloring("#{BOLD RED}ya#{}100!#RESET"), а затем - заменяет формат sprintf-ом
86             sub coloring(@) {
87 6     6 1 6253 my $s = shift;
88 6         95 $s =~ s!#\{(?[\w \t]*)\}|#(?\w+)!
89 12         340 my $x = $+{x};
90 12 100       99 $x = "RESET" if $x ~~ [qw/r R/];
91 12         56 Term::ANSIColor::color($x)
92             !nge;
93 6         232 sprintf $s, @_
94             }
95              
96             # Печатает в STDOUT вывод coloring
97             sub printcolor(@) {
98 1     1 1 9 print coloring @_
99             }
100              
101             # Печатает в STDERR вывод coloring
102             sub warncolor(@) {
103 1     1 1 36 print STDERR coloring @_
104             }
105              
106             # Для крона: Пишет в STDOUT
107             sub accesslog(@) {
108 1     1 1 140 print "[", POSIX::strftime("%F %T", localtime), "] ", coloring @_;
109             }
110              
111             # Для крона: Пишет в STDIN
112             sub errorlog(@) {
113 1     1 1 51 print STDERR "[", POSIX::strftime("%F %T", localtime), "] ", coloring @_;
114             }
115              
116              
117             #@category Преобразования
118              
119             # Проводит соответствия
120             #
121             # replace "...", qr/.../ => sub {...}, ...
122             #
123 1     1 1 2574 sub matches($@) { goto &replace }
124             sub replace($@) {
125 6     6 1 3648 my $s = shift;
126 6         8 my $i = 0;
127 6 100       9 my $re = join "\n| ", map { $i++ % 2 == 0? "(? $_ )": () } @_;
  100         166  
128 6         14 my $arg = \@_;
129             my $fn = sub {
130 716     716   1608 for my $k (keys %+) {
131 735 100       620 return $arg->[$k]->() if do { $k =~ /^I(\d+)\z/ and $k = $1 }
  735 100       1993  
132             }
133 6         13 };
134              
135 6         196 $s =~ s/$re/$fn->()/gex;
  716         746  
136              
137 6         107 $s
138             }
139              
140             #@category Транслитерация
141              
142             # Транслитетрирует русский текст (x, w, q)
143             our %TRANS = qw/
144             а a и i п p ц c э eh
145             б b й y р r ч ch ю ju
146             в v к k с s ш sh я ja
147             г g л l т t щ sch
148             д d м m у u ъ qh
149             е e н n ф f ы y
150             ё jo о o х kh ь q
151             ж zh
152             з z
153             /;
154             sub transliterate($) {
155 2     2 1 2573 my ($s) = @_;
156 2 100       13 $s =~ s/[а-яё]/lc($&) eq $&? $TRANS{$&}: ucfirst $TRANS{lc $&}/gier;
  26         92  
157             }
158              
159             # Транслитетрирует текст, оставляя только латинские буквы и тире
160             sub trans($) {
161 1     1 1 2790 my ($s) = @_;
162 1         4 $s = transliterate $s;
163 1         8 $s =~ s{[-\s_]+}{-}g;
164 1         4 $s =~ s![^a-z-]!!gi;
165 1         9 $s =~ s!^-*(.*?)-*\z!$1!;
166 1         4 lc $s
167             }
168              
169             #@category Строки
170              
171             # Преобразует в строку perl
172             sub to_str(;$) {
173 2 100   2 1 23227 my ($s) = @_ == 0? $_: @_;
174 2         15 $s =~ s/[\\']/\\$&/g;
175 2         10 $s =~ s/^(.*)\z/'$1'/s;
176 2         8 $s
177             }
178              
179             # Преобразует из строки perl
180             sub from_str(;$) {
181 2 100   2 1 3191 my ($s) = @_ == 0? $_: @_;
182 2         11 $s =~ s/^'(.*)'\z/$1/s;
183 2         8 $s =~ s/\\([\\'])/$1/g;
184 2         6 $s
185             }
186              
187             # Упрощённый язык регулярок
188             sub nous($) {
189 1     1 1 2549 my ($templates) = @_;
190             my $x = join "|", map {
191 1         2 replace $_,
192             # Срезаем все пробелы с конца:
193       8     qr!\s*$! => sub {},
194             # Срезаем все начальные строки:
195       4     qr!^([ \t]*\n)*! => sub {},
196             # С начала каждой строки 4 пробела или 0-3 пробела и табуляция:
197       15     qr!^( {4}| {0,3}\t)!m => sub {},
198             # Пробелы в конце строки и пробельные строки затем заменяем на \s*
199 11     11   28 qr!([ \t]*\n)+! => sub { "\\s*" },
200             # Заменяем все переменные {{}}:
201             qr!\{\{(?[>:])?\s*(?[a-z_]\w*)\s*\}\}!i => sub {
202             "(?<$+{name}>" . (
203             $+{type} eq ">"? "[^<>]*?":
204 13 100   13   105 $+{type} eq ":"? "[^\n]*":
    100          
205             ".*?"
206             ) . ")"
207             },
208             # Заменяем управляющие последовательности:
209 1     1   4 qr!\[\[! => sub { "(" },
210 1     1   3 qr!\]\]! => sub { ")?" },
211 1     1   4 qr!\(\(! => sub { "(" },
212 1     1   4 qr!\)\)! => sub { ")" },
213 1     1   4 qr!\|\|! => sub { "|" },
214             # Остальное - эскейпим:
215 654     654   1828 qr!.*?! => sub { quotemeta $& },
216 4         76 } @$templates;
217            
218 1         244 qr/$x/xsmn
219             }
220              
221             # формирует человекочитабельный интервал
222             sub sinterval($) {
223 5     5 1 4117 my ($interval) = @_;
224              
225 5 100       15 if(0 == int $interval) {
226 3 100       15 return sprintf "%.6f mks", $interval*1000_000 if 0 == int($interval*1000_000);
227 2 100       11 return sprintf "%.7f ms", $interval*1000 if 0 == int($interval*1000);
228 1         7 return sprintf "%.8f s", $interval;
229             }
230              
231 2         4 my $hours = int($interval / (60*60));
232 2         5 my $minutes = int(($interval - $hours*60*60) / 60);
233 2         3 my $seconds = int($interval - $hours*60*60 - $minutes*60);
234 2         16 my $last = sprintf "%.3f", $interval - $hours*60*60 - $minutes*60 - $seconds;
235 2         14 sprintf "%02i:%02i:%02i.%s", $hours, $minutes, $seconds, $last =~ s!^0\.!!r
236             }
237              
238             #@category Цифры
239              
240             # переводит в римскую систему счисления
241             # N - ноль
242             # Через каждую 1000 ставится пробел (разделитель разрядов)
243             our @RIM_CIF = (
244             [ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' ],
245             [ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC' ],
246             [ '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM' ]
247             );
248             sub rim($) {
249 9     9 1 6001 my ($a) = @_;
250 1     1   669 use bigint; $a+=0;
  1         4701  
  1         3  
  9         29  
251 9         1804 my $s;
252 9         20 for ( ; $a != 0 ; $a = int( $a / 1000 ) ) {
253 13         1204 my $v = $a % 1000;
254 13 100       1125 if ( $v == 0 ) {
255 3         74 $s = "M $s";
256             }
257             else {
258 10         238 my $d;
259 10         20 for ( my $i = 0, $d = "" ; $v != 0 ; $v = int( $v / 10 ), $i++ ) {
260 19         2958 my $x = $v % 10;
261 19         1535 $d = $RIM_CIF[$i][$x] . $d;
262             }
263 10         2758 $s = "$d $s";
264             }
265             }
266              
267 9   100     1562 $s //= "N";
268 9         33 $s =~ s/ \z//;
269 9         32 $s
270             }
271              
272             # Использованы символы из кодировки cp1251, что нужно для корректной записи в таблицы
273             our $CIF = join "", "0".."9", "A".."Z", "a".."z", "_-", # 64 символа для 64-ричной системы счисления
274             (map chr, ord "А" .. ord "Я"), "ЁЂЃЉЊЌЋЏЎЈҐЄЇІЅ",
275             (map chr, ord "а" .. ord "я"), "ёђѓљњќћџўјґєїіѕ",
276 1     1   57799 "‚„…†‡€‰‹‘’“”•–—™›¤¦§©«¬­®°±µ¶·№»", do { no utf8; chr 0xa0 }, # небуквенные символы из cp1251
  1         215  
  1         5  
277             "!\"#\$%&'()*+,./:;<=>?\@[\\]^`{|}~", # символы пунктуации ASCII
278             " ", # пробел
279             (map chr, 0 .. 0x1F, 0x7F), # управляющие символы ASCII
280             # символ 152 (0x98) в cp1251 отсутствует.
281             ;
282             # Переводит натуральное число в заданную систему счисления
283             sub to_radix($;$) {
284 1     1   109 use bigint;
  1         1  
  1         4  
285 4     4 1 4850 my ($n, $radix) = @_;
286 4   100     14 $radix //= 64;
287 4 100       14 die "to_radix: The number system $radix is too large. Use NS before " . (1 + length $CIF) if $radix > length $CIF;
288 3         153 $n+=0; $radix+=0;
  3         591  
289 3         405 my $x = "";
290 3         4 for(;;) {
291 8         1018 my $cif_idx = $n % $radix;
292 8         677 my $cif = substr $CIF, $cif_idx, 1;
293 8         354 $x =~ s/^/$cif/;
294 8 100       14 last unless $n = int($n / $radix);
295             }
296 3         532 return $x;
297             }
298              
299             # Парсит натуральное число в указанной системе счисления
300             sub from_radix(@) {
301 1     1   849 use bigint;
  1         2  
  1         3  
302 4     4 1 4135 my ($s, $radix) = @_;
303 4   100     14 $radix //= 64;
304 4         55 $radix+=0;
305 4 100       809 die "from_radix: The number system $radix is too large. Use NS before " . (1 + length $CIF) if $radix > length $CIF;
306 3         417 my $x = 0;
307 3         10 for my $ch (split "", $s) {
308 9         1447 $x = $x*$radix + index $CIF, $ch;
309             }
310 3         720 return $x;
311             }
312              
313             # Округляет до указанного разряда числа
314             sub round($;$) {
315 14     14 1 3383 my ($x, $dec) = @_;
316 14   100     33 $dec //= 0;
317 14         20 my $prec = 10**$dec;
318 14         87 int($x*$prec + 0.5) / $prec
319             }
320              
321              
322             #@category Меры (measure)
323              
324             # добавляет разделители между разрядами числа
325             sub num($) {
326 9     9 1 4075 my ($s) = @_;
327              
328 9         12 my $sep = " "; # Неразрывный пробел
329 9         9 my $dec = ".";
330              
331 9 100       22 ($s, $sep, $dec) = @$s == 2? @$s: (@$s, $dec) if ref $s;
    100          
332              
333 9         48 my ($x, $y) = split /\./, $s;
334 9 100       33 $y = "$dec$y" if defined $y;
335              
336 9         16 $x = reverse $x;
337 9         43 $x =~ s!\d{3}!$&$sep!g;
338 9         130 $x =~ s!$sep([+-]?)$!$1!;
339 9         41 reverse($x) . $y;
340             }
341              
342             # Добавляет разряды чисел и добавляет единицу измерения
343             sub kb_size($) {
344 5     5 1 4937 my ($n) = @_;
345              
346 5 100       15 return num(round($n / 1024 / 1024 / 1024)) . "G" if $n >= 1024 * 1024 * 1024;
347 4 100       10 return num(round($n / 1024 / 1024)) . "M" if $n >= 1024 * 1024;
348 3 100       7 return num(round($n / 1024)) . "k" if $n >= 1024;
349              
350 2         4 return num(round($n)) . "b";
351             }
352              
353             # Оставляет $n цифр до и после точки: 10.11 = 10, 0.00012 = 0.00012, 1.2345 = 1.2, если $n = 2
354             sub sround($;$) {
355 7     7 1 4968 my ($number, $digits) = @_;
356 7   100     23 $digits //= 2;
357 7         56 my $num = sprintf("%.100f", $number);
358 7         33 $num =~ /^-?0?(\d*)\.(0*)[1-9]/;
359 7 100       23 return "" . round($num, $digits + length $2) if length($1) == 0;
360 6         10 my $k = $digits - length $1;
361 6 100       15 return "" . round($num, $k < 0? 0: $k);
362             }
363              
364             # Кибибайт
365             sub KiB() { 2**10 }
366              
367             # Мебибайт
368             sub MiB() { 2**20 }
369              
370             # Гибибайт
371             sub GiB() { 2**30 }
372              
373             # Тебибайт
374             sub TiB() { 2**40 }
375              
376             # Максимум в данных TinyText Марии
377             sub xxS() { 255 }
378              
379             # Максимум в данных Text Марии
380             sub xxR() { 64*KiB-1 }
381              
382             # Максимум в данных MediumText Марии
383             sub xxM() { 16*MiB-1 }
384              
385             # Максимум в данных LongText Марии
386             sub xxL() { 4*GiB-1 }
387              
388             #@category Конверторы
389              
390             # Маппинг индекса Флеша для человеков
391             my %FLESCH_INDEX_NAMES = (
392             100 => "для младшеклассников",
393             90 => "для 11 лет (уровень 5-го класса)",
394             80 => "для 12 лет (6-й класс)",
395             70 => "для 13 лет (7-й класс)",
396             60 => "для 8-х и 9-х классов",
397             50 => "для 10-х, 12-х классов",
398             40 => "для студентов",
399             30 => "для бакалавров",
400             20 => "для магистров",
401             10 => "для профессионалов",
402             0 => "для академиков",
403             );
404              
405             sub flesch_index_human($) {
406 8     8 1 13290 my ($flesch_index) = @_;
407 8   100     48 $FLESCH_INDEX_NAMES{int($flesch_index / 10) * 10} // "несвязный русский текст"
408             }
409              
410             1;
411              
412             __END__