File Coverage

lib/Mojolicious/Plugin/Human.pm
Criterion Covered Total %
statement 192 192 100.0
branch 57 78 73.0
condition 40 79 50.6
subroutine 31 31 100.0
pod 1 1 100.0
total 321 381 84.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Human;
2              
3 9     9   2755735 use strict;
  9         22  
  9         263  
4 9     9   50 use warnings;
  9         19  
  9         242  
5 9     9   42 use utf8;
  9         20  
  9         77  
6 9     9   278 use 5.10.0;
  9         29  
7              
8 9     9   476 use Mojo::Base 'Mojolicious::Plugin';
  9         7699  
  9         55  
9 9     9   6978 use Carp;
  9         20  
  9         409  
10 9     9   472 use POSIX qw(strftime);
  9         6247  
  9         67  
11 9     9   8121 use DateTime;
  9         3776231  
  9         371  
12 9     9   5020 use DateTime::Format::DateParse;
  9         42645  
  9         250  
13 9     9   132 use DateTime::TimeZone;
  9         18  
  9         175  
14              
15 9     9   570 use Mojo::Util qw(url_unescape);
  9         76597  
  9         486  
16 9     9   573 use Mojo::ByteStream;
  9         2855  
  9         16185  
17              
18             our $VERSION = '1.0';
19              
20             =encoding utf-8
21              
22             =head1 NAME
23              
24             Mojolicious::Plugin::Human - Helpers to print values as human readable form.
25              
26             =head1 SYNOPSIS
27              
28             $self->plugin('Human', {
29              
30             # Set money parameters if you need
31             money_delim => ",",
32             money_digit => " ",
33              
34             # Local format for date and time strings
35             datetime => '%d.%m.%Y %H:%M',
36             time => '%H:%M:%S',
37             date => '%d.%m.%Y',
38              
39             phone_country => 1,
40             });
41              
42             # Controllers
43              
44             $self->human_datetime( time );
45              
46             # Templates
47              
48             # return '2015-05-23 13:63'
49             %= human_datetime '2015-05-23 13:63:67 +0400'
50              
51             =head1 DESCRIPTION
52              
53             You can use this module in Mojo template engine to make you users happy.
54              
55             =head1 CONFIGURATION
56              
57             =over
58              
59             =item money_format
60              
61             Set printf like money format. Default B<%.2f>
62              
63             =item money_delim
64              
65             Set format for human readable delimiter of money. Default: B<.>
66              
67             =item money_digit
68              
69             Set format for human readable digits of money. Default: B<,>
70              
71             =item datefull
72              
73             Set full format for human readable date and time. Default: %F %T
74              
75             =item datetime
76              
77             Set format for human readable date and time. Default: %F %H:%M
78              
79             =item time
80              
81             Set format for human readable time. Default: %H:%M:%S
82              
83             =item date
84              
85             Set format for human readable date. Default: %F
86              
87             =item tz
88              
89             Set default time zone for DateTime. Default: local
90              
91             =item tz_force
92              
93             Force use time zone
94              
95             =item tz_cookie
96              
97             Set default cookie name for extract time zone from client. Default: tz
98              
99             =item interval_format
100              
101             Set default time format for intervals. Default : %0.2d:%0.2d:%0.2d
102              
103             =item phone_country
104              
105             Set country code for phones functions. Default: 7
106              
107             =item suffix_one
108              
109             Set default suffix for 1 value. DEPRICATED!
110              
111             =item suffix_two
112              
113             Set default suffix for value between 2 and 5. DEPRICATED!
114              
115             =item suffix_many
116              
117             Set default suffix for other values. DEPRICATED!
118              
119             =item cut_length
120              
121             Set default max length for I<human_cut>. Default: 8
122              
123             =back
124              
125             =head1 DATE AND TIME HELPERS
126              
127             =head2 str2datetime $str, $tz
128              
129             Get string or number, return DateTime object.
130             Optional get $tz timezone.
131              
132             =head2 str2time $str, $tz
133              
134             Get string, return timestamp.
135             Optional get $tz timezone.
136              
137             =head2 strftime $str, $tz
138              
139             Get string, return formatted string.
140             Optional get $tz timezone.
141              
142             =head2 human_datetime $str, $tz
143              
144             Get string, return date and time string in human readable form.
145             Optional get $tz timezone.
146              
147             =head2 human_time $str, $tz
148              
149             Get string, return time string in human readable form.
150             Optional get $tz timezone.
151              
152             =head2 human_date $str, $tz
153              
154             Get $str string, return date string in human readable form.
155             Optional get $tz timezone.
156              
157             =head2 human_interval $sec
158              
159             Get count of seconds and return interval human readable form.
160              
161             =head1 MONEY HELPERS
162              
163             =head2 human_money $str
164              
165             =head2 human_money $format, $str
166              
167             Get number, return money string in human readable form with levels.
168              
169             =head2 human_money_short $str
170              
171             =head2 human_money_short $format, $str
172              
173             Like I<human_money> but discard zeros.
174              
175             =head1 PHONE HELPERS
176              
177             =head2 flat_phone $str, $country
178              
179             Get srtring, return flat phone string.
180              
181             =head2 human_phone $str, $country, $add
182              
183             Get srtring, return phone string in human readable form.
184              
185             =head2 human_phones $str, $country, $add
186              
187             Get srtring, return phones (if many) string in human readable form.
188              
189             =head1 TEXT HELPERS
190              
191             =head2 human_suffix $str, $count, $one, $two, $many
192              
193             Get word base form and add some of suffix ($one, $two, $many) depends of $count
194             DEPRICATED!
195              
196             =head2 human_suffix_ru $count, $one, $two, $many
197              
198             Get word form for ($one, $two, $many) depends of $count
199              
200             =head2 human_cut $str, $length
201              
202             Return string cut off $length and ellipsis in the end.
203              
204             =head1 DISTANCE HELPERS
205              
206             =head2 human_distance $dist
207              
208             Return distance, without fractional part if possible.
209              
210             =cut
211              
212             # Placement level in the money functions
213             our $REGEXP_DIGIT = qr{^(-?\d+)(\d{3})};
214              
215             # Timestamp
216             our $REGEXP_TIMESTAMP = qr{^\d+$};
217              
218             # Fractional part of numbers
219             our $REGEXP_FRACTIONAL = qr{\.?0+$};
220             # Fractional delimeter of numbers
221             our $REGEXP_FRACTIONAL_DELIMITER = qr{\.};
222              
223             # Phones symbols
224             our $REGEXP_PHONE_SYMBOL = qr{[^0-9wp\+]+};
225             # Phones command
226             our $REGEXP_PHONE_COMMAND = qr{[wp]};
227             # Get parts of phone number to make it awesome
228             our $REGEXP_PHONE_AWESOME = qr{^(\+.)(...)(...)(.*)$};
229              
230             # Some values separators
231             our $REGEXP_SEPARATOR = qr{[\s,;:]+};
232              
233             sub register {
234 26     26 1 100373 my ($self, $app, $conf) = @_;
235              
236             # Configuration
237 26   50     105 $conf ||= {};
238              
239 26   50     211 $conf->{money_format} //= '%.2f';
240 26   50     153 $conf->{money_delim} //= '.';
241 26   50     149 $conf->{money_digit} //= ',';
242              
243 26   50     159 $conf->{datefull} //= '%F %T';
244 26   50     149 $conf->{datetime} //= '%F %H:%M';
245 26   50     153 $conf->{time} //= '%H:%M:%S';
246 26   50     163 $conf->{date} //= '%F';
247 26   66     935 $conf->{tz} //= strftime '%z', localtime;
248 26   50     149 $conf->{tz_force} //= undef;
249 26   50     126 $conf->{tz_cookie} //= 'tz';
250 26   50     134 $conf->{interval_format} //= '%0.2d:%0.2d:%0.2d';
251              
252 26   50     120 $conf->{phone_country} //= 7;
253 26   100     124 $conf->{phone_add} //= '.';
254              
255 26   50     142 $conf->{suffix_one} //= '';
256 26   50     208 $conf->{suffix_two} //= 'a';
257 26   50     139 $conf->{suffix_many} //= 'ов';
258              
259 26   50     115 $conf->{cut_length} //= 8;
260              
261             # Get timezone from cookies
262             $app->hook(before_dispatch => sub {
263 92     92   218784 my ($self) = @_;
264              
265 92         355 my $tz = $self->cookie( $conf->{tz_cookie} );
266 92 100       4475 return unless defined $tz;
267 77 50       171 return unless length $tz;
268              
269 77         180 $tz = url_unescape $tz;
270 77 100       565 return unless DateTime::TimeZone->is_valid_name( $tz );
271              
272 53         12401 $self->stash('-human-cookie-tz' => $tz);
273 26         214 });
274              
275             # Datetime
276              
277             $app->helper(str2datetime => sub {
278 108     108   2787 my ($self, $str, $tz) = @_;
279 108 50       275 return unless $str;
280              
281 108         156 my $dt = eval {
282 108 100       629 if( $str =~ m{$REGEXP_TIMESTAMP} ) {
283 42         151 DateTime->from_epoch( epoch => $str );
284             } else {
285 66         289 DateTime::Format::DateParse->parse_datetime( $str );
286             }
287             };
288 108 50 33     61913 return if ( !$dt or $@ );
289              
290             # time zone: set or force or cookie or default
291             $tz ||= $conf->{tz_force} ||
292             $self->stash('-human-force-tz') ||
293             $self->stash('-human-cookie-tz') ||
294 108   66     1180 $conf->{tz};
      33        
295             # make time zone
296 108         2681 $dt->set_time_zone( $tz );
297              
298 108         36870 return $dt;
299 26         501 });
300              
301             $app->helper(str2time => sub {
302 12     12   3851 my ($self, $str, $tz) = @_;
303 12         58 my $datetime = $self->str2datetime($str => $tz);
304 12 50       39 return $str unless $datetime;
305 12         104 return Mojo::ByteStream->new( $datetime->epoch );
306 26         693 });
307              
308             $app->helper(strftime => sub {
309 12     12   11388 my ($self, $format, $str, $tz) = @_;
310 12 50       39 return unless defined $str;
311 12         55 my $datetime = $self->str2datetime($str => $tz);
312 12 50       42 return $str unless $datetime;
313 12         94 return Mojo::ByteStream->new( $datetime->strftime( $format ) );
314 26         482 });
315              
316             $app->helper(human_datefull => sub {
317 21     21   21359 my ($self, $str, $tz) = @_;
318 21         95 my $datetime = $self->str2datetime($str => $tz);
319 21 50       75 return $str unless $datetime;
320 21         156 return Mojo::ByteStream->new( $datetime->strftime($conf->{datefull}) );
321 26         469 });
322              
323             $app->helper(human_datetime => sub {
324 21     21   18685 my ($self, $str, $tz) = @_;
325 21         96 my $datetime = $self->str2datetime($str => $tz);
326 21 50       73 return $str unless $datetime;
327 21         160 return Mojo::ByteStream->new( $datetime->strftime($conf->{datetime}) );
328 26         466 });
329              
330             $app->helper(human_time => sub {
331 21     21   17336 my ($self, $str, $tz) = @_;
332 21         92 my $datetime = $self->str2datetime($str => $tz);
333 21 50       72 return $str unless $datetime;
334 21         159 return Mojo::ByteStream->new( $datetime->strftime($conf->{time}) );
335 26         457 });
336              
337             $app->helper(human_date => sub {
338 21     21   17062 my ($self, $str, $tz) = @_;
339 21         86 my $datetime = $self->str2datetime($str => $tz);
340 21 50       72 return $str unless $datetime;
341 21         155 return Mojo::ByteStream->new( $datetime->strftime($conf->{date}) );
342 26         451 });
343              
344             $app->helper(human_interval => sub {
345 4     4   1526 my ($self, $sec) = @_;
346              
347 4 100       15 return undef unless defined $sec;
348              
349 3         5 my $epoch = abs $sec;
350              
351 3         7 my $seconds = $epoch % 60;
352 3         7 my $minutes = int($epoch / 60) % 60;
353 3         4 my $hours = int($epoch / 3600) % 24;
354 3         7 my $days = int($epoch / 86400);
355              
356 3         7 my $time = '';
357 3         16 $time .= sprintf $conf->{interval_format}, $hours, $minutes, $seconds;
358 3 100       15 $time = sprintf '%d %s', $days, $time if $days;
359 3 50       12 $time = ($sec < 0 ? '-' : '') . $time;
360              
361 3         16 return $time;
362 26         461 });
363              
364             # Money
365              
366             $app->helper(human_money => sub {
367 8     8   2719 my $self = shift;
368 8         16 my $str = pop;
369 8   66     37 my $format = shift // $conf->{money_format};
370              
371 8 100       31 return undef unless defined $str;
372 6 100       26 return undef unless length $str;
373              
374 4         9 my $delim = $conf->{money_delim};
375 4         7 my $digit = $conf->{money_digit};
376 4         39 $str = sprintf $format, $str;
377 4         29 $str =~ s{$REGEXP_FRACTIONAL_DELIMITER}{$delim};
378 4         67 1 while $str =~ s{$REGEXP_DIGIT}{$1$digit$2};
379              
380 4         27 return Mojo::ByteStream->new($str);
381 26         493 });
382              
383             $app->helper(human_money_short => sub {
384 4     4   2170 my $self = shift;
385              
386 4         24 my $stream = $self->human_money(@_);
387 4 100       31 return undef unless defined $stream;
388              
389 2         6 my $str = "$stream";
390 2         18 s{\D00$}{} for $str;
391 2         7 return Mojo::ByteStream->new($str);
392 26         496 });
393              
394             # Phones
395              
396             $app->helper(flat_phone => sub {
397 31     31   8356 my ($self, $phone, $country) = @_;
398 31 50       74 return undef unless $phone;
399              
400             # clear
401 31         155 s/$REGEXP_PHONE_SYMBOL//ig for $phone;
402 31 50       93 return undef unless 10 <= length $phone;
403              
404 31   66     147 $country //= $conf->{phone_country};
405             # make full
406 31 100       110 $phone = '+' . $country . $phone unless $phone =~ m{^\+};
407              
408 31         150 return Mojo::ByteStream->new($phone);
409 26         529 });
410              
411             $app->helper(human_phone => sub {
412 24     24   11955 my ($self, $phone, $country, $add) = @_;
413 24 50       59 return unless $phone;
414              
415             # make clean
416 24         100 $phone = $self->flat_phone( $phone, $country );
417 24 50       207 return $phone unless $phone;
418              
419             # make awesome
420 24   66     187 $add //= $conf->{phone_add};
421             s{$REGEXP_PHONE_AWESOME}{$1-$2-$3-$4},
422             s{$REGEXP_PHONE_COMMAND}{$add}ig
423 24         97 for $phone;
424              
425 24         485 return Mojo::ByteStream->new($phone);
426 26         485 });
427              
428             $app->helper(human_phones => sub {
429 8     8   8039 my ($self, $str, $country, $add) = @_;
430 8 50       24 return '' unless $str;
431              
432 8         47 my @phones = split m{$REGEXP_SEPARATOR}, $str;
433 12         85 my $phones = join ', ' => grep { $_ } map {
434 8         20 $self->human_phone( $_, $country, $add )
  12         79  
435             } @phones;
436              
437 8         74 return Mojo::ByteStream->new($phones);
438 26         549 });
439              
440             # Text
441              
442             # DEPRICATED
443             $app->helper(human_suffix => sub {
444 6     6   4122 my ($self, $str, $count, $one, $two, $many) = @_;
445              
446 6         372 warn 'human_suffix DEPRICATED!';
447              
448 6 50       31 return unless defined $str;
449 6 50       21 return $str unless defined $count;
450              
451             # Last digit
452 6         11 my $tail = abs( $count ) % 10;
453              
454             # Default suffix
455 6   33     18 $one //= $str . $conf->{suffix_one};
456 6   33     12 $two //= $str . $conf->{suffix_two};
457 6   33     12 $many //= $str . $conf->{suffix_many};
458              
459             # Get right suffix
460 6 100 66     35 my $result =
    100          
    100          
461             ( $tail == 0 ) ?$many :
462             ( $tail == 1 ) ?$one :
463             ( $tail >= 2 and $tail < 5 ) ?$two :$many;
464              
465             # For 10 - 20 get special suffix
466 6         10 $tail = abs( $count ) % 100;
467 6 50 33     18 $result =
468             ( $tail >= 10 and $tail < 21 ) ?$many :$result;
469              
470 6         33 return Mojo::ByteStream->new($result);
471 26         500 });
472              
473             $app->helper(human_suffix_ru => sub {
474 6     6   4283 my ($self, $count, $one, $two, $many) = @_;
475              
476 6 50       16 return unless defined $count;
477              
478             # Last digit
479 6         13 my $tail = abs( $count ) % 10;
480              
481             # Get right suffix
482 6 100 66     36 my $result =
    100          
    100          
483             ( $tail == 0 ) ?$many :
484             ( $tail == 1 ) ?$one :
485             ( $tail >= 2 and $tail < 5 ) ?$two :$many;
486              
487             # For 10 - 20 get special suffix
488 6         11 $tail = abs( $count ) % 100;
489 6 50 33     26 $result =
490             ( $tail >= 10 and $tail < 21 ) ?$many :$result;
491              
492 6         25 return Mojo::ByteStream->new($result);
493 26         534 });
494              
495             $app->helper(human_cut => sub {
496 4     4   2363 my ($self, $str, $length) = @_;
497              
498 4 100       14 return undef unless defined $str;
499 3 100       15 return undef unless length $str;
500              
501 2   33     12 $length //= $conf->{cut_length};
502 2 100       14 return Mojo::ByteStream->new(
503             $length < length $str
504             ? substr($str, 0 => $length) . '…'
505             : $str
506             );
507 26         469 });
508              
509             # Distance
510              
511             $app->helper(human_distance => sub {
512 7     7   5960 my ($self, $dist) = @_;
513 7         54 $dist = sprintf '%3.2f', $dist;
514 7         43 $dist =~ s{$REGEXP_FRACTIONAL}{};
515 7         35 return Mojo::ByteStream->new($dist);
516 26         445 });
517             }
518              
519             1;
520              
521             =head1 AUTHORS
522              
523             Dmitry E. Oboukhov <unera@debian.org>,
524             Roman V. Nikolaev <rshadow@rambler.ru>
525              
526             =head1 COPYRIGHT
527              
528             Copyright (C) 2011 Dmitry E. Oboukhov <unera@debian.org>
529             Copyright (C) 2011 Roman V. Nikolaev <rshadow@rambler.ru>
530              
531             This library is free software; you can redistribute it and/or modify
532             it under the same terms as Perl itself, either Perl version 5.8.8 or,
533             at your option, any later version of Perl 5 you may have available.
534              
535             =cut