File Coverage

lib/Mojolicious/Plugin/Human.pm
Criterion Covered Total %
statement 193 193 100.0
branch 59 80 73.7
condition 40 79 50.6
subroutine 31 31 100.0
pod 1 1 100.0
total 324 384 84.3


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Human;
2              
3 9     9   3048827 use strict;
  9         32  
  9         297  
4 9     9   52 use warnings;
  9         24  
  9         274  
5 9     9   50 use utf8;
  9         19  
  9         76  
6 9     9   253 use 5.10.0;
  9         33  
7              
8 9     9   427 use Mojo::Base 'Mojolicious::Plugin';
  9         8215  
  9         56  
9 9     9   7518 use Carp;
  9         23  
  9         634  
10 9     9   453 use POSIX qw(strftime);
  9         5497  
  9         72  
11 9     9   7997 use DateTime;
  9         4154771  
  9         370  
12 9     9   4692 use DateTime::Format::DateParse;
  9         45958  
  9         296  
13 9     9   133 use DateTime::TimeZone;
  9         19  
  9         195  
14              
15 9     9   623 use Mojo::Util qw(url_unescape);
  9         79946  
  9         446  
16 9     9   497 use Mojo::ByteStream;
  9         2708  
  9         15843  
17              
18             our $VERSION = '1.01';
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 118880 my ($self, $app, $conf) = @_;
235              
236             # Configuration
237 26   50     116 $conf ||= {};
238              
239 26   50     220 $conf->{money_format} //= '%.2f';
240 26   50     181 $conf->{money_delim} //= '.';
241 26   50     171 $conf->{money_digit} //= ',';
242              
243 26   50     172 $conf->{datefull} //= '%F %T';
244 26   50     163 $conf->{datetime} //= '%F %H:%M';
245 26   50     160 $conf->{time} //= '%H:%M:%S';
246 26   50     173 $conf->{date} //= '%F';
247 26   66     931 $conf->{tz} //= strftime '%z', localtime;
248 26   50     162 $conf->{tz_force} //= undef;
249 26   50     160 $conf->{tz_cookie} //= 'tz';
250 26   50     152 $conf->{interval_format} //= '%0.2d:%0.2d:%0.2d';
251              
252 26   50     156 $conf->{phone_country} //= 7;
253 26   100     146 $conf->{phone_add} //= '.';
254              
255 26   50     146 $conf->{suffix_one} //= '';
256 26   50     245 $conf->{suffix_two} //= 'a';
257 26   50     188 $conf->{suffix_many} //= 'ов';
258              
259 26   50     142 $conf->{cut_length} //= 8;
260              
261             # Get timezone from cookies
262             $app->hook(before_dispatch => sub {
263 92     92   258794 my ($self) = @_;
264              
265 92         419 my $tz = $self->cookie( $conf->{tz_cookie} );
266 92 100       6774 return unless defined $tz;
267 77 50       183 return unless length $tz;
268              
269 77         213 $tz = url_unescape $tz;
270 77 100       640 return unless DateTime::TimeZone->is_valid_name( $tz );
271              
272 53         15019 $self->stash('-human-cookie-tz' => $tz);
273 26         245 });
274              
275             # Datetime
276              
277             $app->helper(str2datetime => sub {
278 118     118   3718 my ($self, $str, $tz) = @_;
279 118 50       376 return unless $str;
280              
281 118         268 my $dt = eval {
282 118 100       854 if( ref $str ) {
    100          
283 10         40 $str->clone;
284             } elsif( $str =~ m{$REGEXP_TIMESTAMP} ) {
285 42         224 DateTime->from_epoch( epoch => $str );
286             } else {
287 66         365 DateTime::Format::DateParse->parse_datetime( $str );
288             }
289             };
290 118 50 33     77312 return if ( !$dt or $@ );
291              
292             # time zone: set or force or cookie or default
293             $tz ||= $conf->{tz_force} ||
294             $self->stash('-human-force-tz') ||
295             $self->stash('-human-cookie-tz') ||
296 118   66     1555 $conf->{tz};
      33        
297             # make time zone
298 118         3714 $dt->set_time_zone( $tz );
299              
300 118         46659 return $dt;
301 26         602 });
302              
303             $app->helper(str2time => sub {
304 22     22   18048 my ($self, $str, $tz) = @_;
305 22         115 my $datetime = $self->str2datetime($str => $tz);
306 22 50       76 return $str unless $datetime;
307 22         183 return Mojo::ByteStream->new( $datetime->epoch );
308 26         847 });
309              
310             $app->helper(strftime => sub {
311 12     12   14503 my ($self, $format, $str, $tz) = @_;
312 12 50       51 return unless defined $str;
313 12         71 my $datetime = $self->str2datetime($str => $tz);
314 12 50       55 return $str unless $datetime;
315 12         122 return Mojo::ByteStream->new( $datetime->strftime( $format ) );
316 26         606 });
317              
318             $app->helper(human_datefull => sub {
319 21     21   28801 my ($self, $str, $tz) = @_;
320 21         133 my $datetime = $self->str2datetime($str => $tz);
321 21 50       85 return $str unless $datetime;
322 21         202 return Mojo::ByteStream->new( $datetime->strftime($conf->{datefull}) );
323 26         575 });
324              
325             $app->helper(human_datetime => sub {
326 21     21   25517 my ($self, $str, $tz) = @_;
327 21         123 my $datetime = $self->str2datetime($str => $tz);
328 21 50       88 return $str unless $datetime;
329 21         199 return Mojo::ByteStream->new( $datetime->strftime($conf->{datetime}) );
330 26         600 });
331              
332             $app->helper(human_time => sub {
333 21     21   24065 my ($self, $str, $tz) = @_;
334 21         128 my $datetime = $self->str2datetime($str => $tz);
335 21 50       84 return $str unless $datetime;
336 21         192 return Mojo::ByteStream->new( $datetime->strftime($conf->{time}) );
337 26         609 });
338              
339             $app->helper(human_date => sub {
340 21     21   22609 my ($self, $str, $tz) = @_;
341 21         115 my $datetime = $self->str2datetime($str => $tz);
342 21 50       72 return $str unless $datetime;
343 21         171 return Mojo::ByteStream->new( $datetime->strftime($conf->{date}) );
344 26         592 });
345              
346             $app->helper(human_interval => sub {
347 4     4   1700 my ($self, $sec) = @_;
348              
349 4 100       22 return undef unless defined $sec;
350              
351 3         5 my $epoch = abs $sec;
352              
353 3         6 my $seconds = $epoch % 60;
354 3         8 my $minutes = int($epoch / 60) % 60;
355 3         4 my $hours = int($epoch / 3600) % 24;
356 3         5 my $days = int($epoch / 86400);
357              
358 3         6 my $time = '';
359 3         14 $time .= sprintf $conf->{interval_format}, $hours, $minutes, $seconds;
360 3 100       16 $time = sprintf '%d %s', $days, $time if $days;
361 3 50       10 $time = ($sec < 0 ? '-' : '') . $time;
362              
363 3         14 return $time;
364 26         574 });
365              
366             # Money
367              
368             $app->helper(human_money => sub {
369 8     8   3850 my $self = shift;
370 8         14 my $str = pop;
371 8   66     40 my $format = shift // $conf->{money_format};
372              
373 8 100       34 return undef unless defined $str;
374 6 100       31 return undef unless length $str;
375              
376 4         12 my $delim = $conf->{money_delim};
377 4         11 my $digit = $conf->{money_digit};
378 4         33 $str = sprintf $format, $str;
379 4         28 $str =~ s{$REGEXP_FRACTIONAL_DELIMITER}{$delim};
380 4         76 1 while $str =~ s{$REGEXP_DIGIT}{$1$digit$2};
381              
382 4         25 return Mojo::ByteStream->new($str);
383 26         595 });
384              
385             $app->helper(human_money_short => sub {
386 4     4   2578 my $self = shift;
387              
388 4         24 my $stream = $self->human_money(@_);
389 4 100       31 return undef unless defined $stream;
390              
391 2         7 my $str = "$stream";
392 2         18 s{\D00$}{} for $str;
393 2         6 return Mojo::ByteStream->new($str);
394 26         561 });
395              
396             # Phones
397              
398             $app->helper(flat_phone => sub {
399 31     31   11866 my ($self, $phone, $country) = @_;
400 31 50       107 return undef unless $phone;
401              
402             # clear
403 31         168 s/$REGEXP_PHONE_SYMBOL//ig for $phone;
404 31 50       93 return undef unless 10 <= length $phone;
405              
406 31   66     152 $country //= $conf->{phone_country};
407             # make full
408 31 100       115 $phone = '+' . $country . $phone unless $phone =~ m{^\+};
409              
410 31         152 return Mojo::ByteStream->new($phone);
411 26         524 });
412              
413             $app->helper(human_phone => sub {
414 24     24   11008 my ($self, $phone, $country, $add) = @_;
415 24 50       55 return unless $phone;
416              
417             # make clean
418 24         91 $phone = $self->flat_phone( $phone, $country );
419 24 50       194 return $phone unless $phone;
420              
421             # make awesome
422 24   66     168 $add //= $conf->{phone_add};
423             s{$REGEXP_PHONE_AWESOME}{$1-$2-$3-$4},
424             s{$REGEXP_PHONE_COMMAND}{$add}ig
425 24         118 for $phone;
426              
427 24         434 return Mojo::ByteStream->new($phone);
428 26         559 });
429              
430             $app->helper(human_phones => sub {
431 8     8   7651 my ($self, $str, $country, $add) = @_;
432 8 50       22 return '' unless $str;
433              
434 8         54 my @phones = split m{$REGEXP_SEPARATOR}, $str;
435 12         74 my $phones = join ', ' => grep { $_ } map {
436 8         19 $self->human_phone( $_, $country, $add )
  12         69  
437             } @phones;
438              
439 8         68 return Mojo::ByteStream->new($phones);
440 26         550 });
441              
442             # Text
443              
444             # DEPRICATED
445             $app->helper(human_suffix => sub {
446 6     6   4680 my ($self, $str, $count, $one, $two, $many) = @_;
447              
448 6         391 warn 'human_suffix DEPRICATED!';
449              
450 6 50       36 return unless defined $str;
451 6 50       22 return $str unless defined $count;
452              
453             # Last digit
454 6         14 my $tail = abs( $count ) % 10;
455              
456             # Default suffix
457 6   33     15 $one //= $str . $conf->{suffix_one};
458 6   33     13 $two //= $str . $conf->{suffix_two};
459 6   33     12 $many //= $str . $conf->{suffix_many};
460              
461             # Get right suffix
462 6 100 66     40 my $result =
    100          
    100          
463             ( $tail == 0 ) ?$many :
464             ( $tail == 1 ) ?$one :
465             ( $tail >= 2 and $tail < 5 ) ?$two :$many;
466              
467             # For 10 - 20 get special suffix
468 6         16 $tail = abs( $count ) % 100;
469 6 50 33     19 $result =
470             ( $tail >= 10 and $tail < 21 ) ?$many :$result;
471              
472 6         37 return Mojo::ByteStream->new($result);
473 26         661 });
474              
475             $app->helper(human_suffix_ru => sub {
476 6     6   6296 my ($self, $count, $one, $two, $many) = @_;
477              
478 6 50       31 return unless defined $count;
479              
480             # Last digit
481 6         18 my $tail = abs( $count ) % 10;
482              
483             # Get right suffix
484 6 100 66     50 my $result =
    100          
    100          
485             ( $tail == 0 ) ?$many :
486             ( $tail == 1 ) ?$one :
487             ( $tail >= 2 and $tail < 5 ) ?$two :$many;
488              
489             # For 10 - 20 get special suffix
490 6         14 $tail = abs( $count ) % 100;
491 6 50 33     25 $result =
492             ( $tail >= 10 and $tail < 21 ) ?$many :$result;
493              
494 6         38 return Mojo::ByteStream->new($result);
495 26         648 });
496              
497             $app->helper(human_cut => sub {
498 4     4   3696 my ($self, $str, $length) = @_;
499              
500 4 100       30 return undef unless defined $str;
501 3 100       28 return undef unless length $str;
502              
503 2   33     20 $length //= $conf->{cut_length};
504 2 100       21 return Mojo::ByteStream->new(
505             $length < length $str
506             ? substr($str, 0 => $length) . '…'
507             : $str
508             );
509 26         578 });
510              
511             # Distance
512              
513             $app->helper(human_distance => sub {
514 7     7   6742 my ($self, $dist) = @_;
515 7         60 $dist = sprintf '%3.2f', $dist;
516 7         44 $dist =~ s{$REGEXP_FRACTIONAL}{};
517 7         30 return Mojo::ByteStream->new($dist);
518 26         537 });
519             }
520              
521             1;
522              
523             =head1 AUTHORS
524              
525             Dmitry E. Oboukhov <unera@debian.org>,
526             Roman V. Nikolaev <rshadow@rambler.ru>
527              
528             =head1 COPYRIGHT
529              
530             Copyright (C) 2011 Dmitry E. Oboukhov <unera@debian.org>
531             Copyright (C) 2011 Roman V. Nikolaev <rshadow@rambler.ru>
532              
533             This library is free software; you can redistribute it and/or modify
534             it under the same terms as Perl itself, either Perl version 5.8.8 or,
535             at your option, any later version of Perl 5 you may have available.
536              
537             =cut