File Coverage

blib/lib/Catmandu/Util.pm
Criterion Covered Total %
statement 324 345 93.9
branch 144 190 75.7
condition 42 95 44.2
subroutine 70 73 95.8
pod 46 53 86.7
total 626 756 82.8


line stmt bran cond sub pod time code
1             package Catmandu::Util;
2              
3 177     177   651928 use Catmandu::Sane;
  177         441  
  177         7544  
4              
5             our $VERSION = '1.2020';
6              
7 177     177   1265 use Exporter qw(import);
  177         430  
  177         5300  
8 177     177   83983 use Sub::Quote ();
  177         803585  
  177         3861  
9 177     177   1265 use Scalar::Util ();
  177         458  
  177         2509  
10 177     177   978 use List::Util ();
  177         417  
  177         2235  
11 177     177   84371 use Data::Util ();
  177         142828  
  177         4696  
12 177     177   84308 use Data::Compare ();
  177         2458735  
  177         4824  
13 177     177   1655 use IO::File;
  177         441  
  177         27368  
14 177     177   92772 use IO::Handle::Util ();
  177         2948052  
  177         6305  
15 177     177   1351 use File::Spec;
  177         384  
  177         3966  
16 177     177   82855 use YAML::XS ();
  177         495259  
  177         3916  
17 177     177   162029 use Cpanel::JSON::XS ();
  177         604489  
  177         4445  
18 177     177   81527 use Hash::Merge::Simple ();
  177         88087  
  177         4377  
19 177     177   79681 use MIME::Types;
  177         736799  
  177         8775  
20 177     177   5023 use POSIX ();
  177         46139  
  177         2997  
21 177     177   4594 use Time::HiRes ();
  177         9935  
  177         811953  
22              
23             our %EXPORT_TAGS = (
24             io => [
25             qw(io read_file read_io write_file read_yaml read_json join_path
26             normalize_path segmented_path content_type)
27             ],
28             data => [qw(parse_data_path get_data set_data delete_data data_at)],
29             array => [
30             qw(array_exists array_group_by array_pluck array_to_sentence
31             array_sum array_includes array_any array_rest array_uniq array_split)
32             ],
33             hash => [qw(hash_merge)],
34             string => [qw(as_utf8 trim capitalize)],
35             is => [qw(is_same is_different)],
36             check => [qw(check_same check_different)],
37             human => [qw(human_number human_content_type human_byte_size)],
38             xml => [qw(xml_declaration xml_escape)],
39             misc => [qw(require_package use_lib pod_section)],
40             date => [qw(now)],
41             );
42              
43             our @EXPORT_OK = map {@$_} values %EXPORT_TAGS;
44              
45             $EXPORT_TAGS{all} = \@EXPORT_OK;
46              
47             my $HUMAN_CONTENT_TYPES = {
48              
49             # txt
50             'text/plain' => 'Text',
51             'application/txt' => 'Text',
52              
53             # pdf
54             'application/pdf' => 'PDF',
55             'application/x-pdf' => 'PDF',
56             'application/acrobat' => 'PDF',
57             'applications/vnd.pdf' => 'PDF',
58             'text/pdf' => 'PDF',
59             'text/x-pdf' => 'PDF',
60              
61             # doc
62             'application/doc' => 'Word',
63             'application/vnd.msword' => 'Word',
64             'application/vnd.ms-word' => 'Word',
65             'application/winword' => 'Word',
66             'application/word' => 'Word',
67             'application/x-msw6' => 'Word',
68             'application/x-msword' => 'Word',
69              
70             # docx
71             'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
72             => 'Word',
73              
74             # xls
75             'application/vnd.ms-excel' => 'Excel',
76             'application/msexcel' => 'Excel',
77             'application/x-msexcel' => 'Excel',
78             'application/x-ms-excel' => 'Excel',
79             'application/vnd.ms-excel' => 'Excel',
80             'application/x-excel' => 'Excel',
81             'application/x-dos_ms_excel' => 'Excel',
82             'application/xls' => 'Excel',
83              
84             # xlsx
85             'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' =>
86             'Excel',
87              
88             # ppt
89             'application/vnd.ms-powerpoint' => 'PowerPoint',
90             'application/mspowerpoint' => 'PowerPoint',
91             'application/ms-powerpoint' => 'PowerPoint',
92             'application/mspowerpnt' => 'PowerPoint',
93             'application/vnd-mspowerpoint' => 'PowerPoint',
94             'application/powerpoint' => 'PowerPoint',
95             'application/x-powerpoint' => 'PowerPoint',
96              
97             # pptx
98             'application/vnd.openxmlformats-officedocument.presentationml.presentation'
99             => 'PowerPoint',
100              
101             # csv
102             'text/comma-separated-values' => 'CSV',
103             'text/csv' => 'CSV',
104             'application/csv' => 'CSV',
105              
106             # zip
107             'application/zip' => 'ZIP archive',
108             };
109              
110             my $XML_DECLARATION = qq(<?xml version="1.0" encoding="UTF-8"?>\n);
111              
112       0     sub TIESCALAR { }
113              
114             sub io {
115 113     113 1 50332 my ($arg, %opts) = @_;
116 113   50     493 my $binmode = $opts{binmode} || $opts{encoding} || ':encoding(UTF-8)';
117 113   100     351 my $mode = $opts{mode} || 'r';
118 113         197 my $io;
119              
120 113 100 66     745 if (is_scalar_ref($arg)) {
    100 100        
    100 66        
    100          
    100          
    50          
121 71         314 $io = IO::Handle::Util::io_from_scalar_ref($arg);
122 71 50       60230 defined($io) && binmode $io, $binmode;
123             }
124             elsif (is_glob_ref(\$arg) || is_glob_ref($arg)) {
125 17   66     160 $io = IO::Handle->new_from_fd($arg, $mode) // $arg;
126 17 50       2191 defined($io) && binmode $io, $binmode;
127             }
128             elsif (is_string($arg)) {
129 22         177 $io = IO::File->new($arg, $mode);
130 22 50       2884 defined($io) && binmode $io, $binmode;
131             }
132             elsif (is_code_ref($arg) && $mode eq 'r') {
133 1         6 $io = IO::Handle::Util::io_from_getline($arg);
134             }
135             elsif (is_code_ref($arg) && $mode eq 'w') {
136 1         5 $io = IO::Handle::Util::io_from_write_cb($arg);
137             }
138             elsif (is_instance($arg, 'IO::Handle')) {
139 1         2 $io = $arg;
140 1 50       43 defined($io) && binmode $io, $binmode;
141             }
142             else {
143 0         0 Catmandu::BadArg->throw("can't make io from argument");
144             }
145              
146 113         28008 $io;
147             }
148              
149             # Deprecated use tools like File::Slurp::Tiny
150             sub read_file {
151 3     3 1 464 my ($path) = @_;
152 3         15 local $/;
153 3 50       198 open my $fh, "<:encoding(UTF-8)", $path
154             or Catmandu::Error->throw(qq(can't open "$path" for reading));
155 3         1596 my $str = <$fh>;
156 3         150 close $fh;
157 3         37 $str;
158             }
159              
160             sub read_io {
161 3     3 1 783 my ($io) = @_;
162 3 100       44 $io->binmode("encoding(UTF-8)") if ($io->can('binmode'));
163 3         156 my @lines = ();
164 3         80 while (<$io>) {
165 15         135 push @lines, $_;
166             }
167 3         26 $io->close();
168 3         83 join "", @lines;
169             }
170              
171             # Deprecated use tools like File::Slurp::Tiny
172             sub write_file {
173 1     1 1 726 my ($path, $str) = @_;
174 1 50       105 open my $fh, ">:encoding(UTF-8)", $path
175             or Catmandu::Error->throw(qq(can't open "$path" for writing));
176 1         83 print $fh $str;
177 1         91 close $fh;
178 1         14 $path;
179             }
180              
181             sub read_yaml {
182              
183             # dies on error
184 1     1 1 6 YAML::XS::LoadFile($_[0]);
185             }
186              
187             sub read_json {
188 1     1 1 1771 my $text = read_file($_[0]);
189              
190             # dies on error
191 1         46 Cpanel::JSON::XS->new->decode($text);
192             }
193              
194             ##
195             # Split a path on . or /, but not on \/ or \.
196             sub split_path {
197 22     22 0 52 my ($path) = @_;
198 22         56 $path = trim($path);
199 22         56 $path =~ s/^\$[\.\/]//;
200 22         100 return [map {s/\\(?=[\.\/])//g; $_} split /(?<!\\)[\.\/]/, $path];
  35         73  
  35         139  
201             }
202              
203             sub join_path {
204 1     1 1 905 my $path = File::Spec->catfile(@_);
205 1         7 $path =~ s!/\./!/!g;
206 1         26 while ($path =~ s![^/]*/\.\./!!) { }
207 1         9 $path;
208             }
209              
210             sub normalize_path { # taken from Dancer::FileUtils
211 1     1 1 4 my ($path) = @_;
212 1         3 $path =~ s!/\./!/!g;
213 1         26 while ($path =~ s![^/]*/\.\./!!) { }
214 1         12 File::Spec->catfile($path);
215             }
216              
217             sub segmented_path {
218 1     1 1 6 my ($id, %opts) = @_;
219 1   50     10 my $segment_size = $opts{segment_size} || 3;
220 1         3 my $base_path = $opts{base_path};
221 1         4 $id =~ s/[^0-9a-zA-Z]+//g;
222 1         12 my @path = unpack "(A$segment_size)*", $id;
223 1 50       22 defined $base_path
224             ? File::Spec->catdir($base_path, @path)
225             : File::Spec->catdir(@path);
226             }
227              
228             my $MIME_TYPES;
229              
230             sub content_type {
231 0     0 1 0 my ($filename) = @_;
232              
233 0   0     0 $MIME_TYPES ||= MIME::Types->new(only_complete => 1);
234              
235 0 0       0 return undef unless $filename;
236              
237 0         0 my ($ext) = $filename =~ /\.(.+?)$/;
238              
239 0         0 my $type = 'application/octet-stream';
240              
241 0         0 my $mime = $MIME_TYPES->mimeTypeOf($ext);
242              
243             # Require explicit stringification!
244 0 0       0 $type = sprintf "%s", $mime->type if $mime;
245              
246 0         0 $type;
247             }
248              
249             sub parse_data_path {
250 1     1 0 4 my ($path) = @_;
251 1         6 check_string($path);
252 1         740 $path = split_path($path);
253 1         10 my $key = pop @$path;
254 1         9 return $path, $key;
255             }
256              
257             sub get_data {
258 7     7 0 34 my ($data, $key) = @_;
259 7 100       31 if (is_array_ref($data)) {
260 5 50       25 if ($key eq '$first') {return unless @$data; $key = 0}
  1 100       5  
  1 100       2  
    100          
261 1 50       4 elsif ($key eq '$last') {return unless @$data; $key = @$data - 1}
  1         4  
262 1         8 elsif ($key eq '*') {return @$data}
263 4 100       15 if (array_exists($data, $key)) {
264 3         19 return $data->[$key];
265             }
266 1         7 return;
267             }
268 2 100 66     18 if (is_hash_ref($data) && exists $data->{$key}) {
269 1         5 return $data->{$key};
270             }
271 1         5 return;
272             }
273              
274             sub set_data {
275 15     15 0 7722 my ($data, $key, @vals) = @_;
276 15 50       43 return unless @vals;
277 15 100       47 if (is_array_ref($data)) {
278 12 50       52 if ($key eq '$first') {return unless @$data; $key = 0}
  2 100       7  
  2 100       4  
    100          
    100          
    100          
279 2 50       7 elsif ($key eq '$last') {return unless @$data; $key = @$data - 1}
  2         5  
280             elsif ($key eq '$prepend') {
281 2         9 unshift @$data, $vals[0];
282 2         5 return $vals[0];
283             }
284 2         6 elsif ($key eq '$append') {push @$data, $vals[0]; return $vals[0]}
  2         6  
285 1         7 elsif ($key eq '*') {return splice @$data, 0, @$data, @vals}
286 7 100       15 return $data->[$key] = $vals[0] if is_natural($key);
287 1         4 return;
288             }
289 3 100       9 if (is_hash_ref($data)) {
290 2         5 return $data->{$key} = $vals[0];
291             }
292 1         6 return;
293             }
294              
295             sub delete_data {
296 7     7 0 3924 my ($data, $key) = @_;
297 7 100       28 if (is_array_ref($data)) {
298 5 50       25 if ($key eq '$first') {return unless @$data; $key = 0}
  1 100       5  
  1 100       69  
    100          
299 1 50       5 elsif ($key eq '$last') {return unless @$data; $key = @$data - 1}
  1         2  
300 1         4 elsif ($key eq '*') {return splice @$data, 0, @$data}
301 4 100       11 if (array_exists($data, $key)) {
302 3         12 return splice @$data, $key, 1;
303             }
304 1         3 return;
305             }
306 2 50 66     10 if (is_hash_ref($data) && exists $data->{$key}) {
307 1         4 return delete $data->{$key};
308             }
309              
310 1         3 return;
311             }
312              
313             sub data_at {
314 13     13 0 1224 my ($path, $data, %opts) = @_;
315 13 50       41 if (ref $path) {
316 0         0 $path = [map {split_path($_)} @$path];
  0         0  
317             }
318             else {
319 13         43 $path = split_path($path);
320             }
321 13         34 my $create = $opts{create};
322 13   33     64 my $_key = $opts{_key} // $opts{key};
323 13 0 33     44 if (defined $opts{key} && $create && @$path) {
      33        
324 0         0 push @$path, $_key;
325             }
326 13         20 my $key;
327 13         46 while (defined(my $key = shift @$path)) {
328 18 50       46 is_ref($data) || return;
329 18 100 33     84 if (is_array_ref($data)) {
    50          
330 5 50       11 if ($key eq '*') {
331             return
332 0         0 map {data_at($path, $_, create => $create, _key => $_key)}
  0         0  
333             @$data;
334             }
335             else {
336 5 100       24 if ($key eq '$first') {$key = 0}
  1 100       4  
    100          
    100          
337 1         3 elsif ($key eq '$last') {$key = -1}
338 1         3 elsif ($key eq '$prepend') {unshift @$data, undef; $key = 0}
  1         2  
339 1         4 elsif ($key eq '$append') {push @$data, undef; $key = @$data}
  1         2  
340 5 50       10 is_integer($key) || return;
341 5 50 33     15 if ($create && @$path) {
342 0 0 0     0 $data = $data->[$key] ||= is_integer($path->[0])
      0        
343             || ord($path->[0]) == ord('$') ? [] : {};
344             }
345             else {
346 5         11 $data = $data->[$key];
347             }
348             }
349             }
350             elsif ($create && @$path) {
351 0 0 0     0 $data = $data->{$key} ||= is_integer($path->[0])
      0        
352             || ord($path->[0]) == ord('$') ? [] : {};
353             }
354             else {
355 13         27 $data = $data->{$key};
356             }
357 18 50 33     72 if ($create && @$path == 1) {
358 0         0 last;
359             }
360             }
361 13         180 $data;
362             }
363              
364             sub array_exists {
365 10     10 1 731 my ($arr, $i) = @_;
366 10 50       27 is_natural($i) && $i < @$arr;
367             }
368              
369             sub array_group_by {
370 1     1 1 3 my ($arr, $key) = @_;
371             List::Util::reduce {
372 4     4   8 my $k = $b->{$key};
373 4 100 50     10 push @{$a->{$k} ||= []}, $b if defined $k;
  3         31  
374 4         18 $a
375             }
376 1         15 {}, @$arr;
377             }
378              
379             sub array_pluck {
380 1     1 1 4 my ($arr, $key) = @_;
381 1         4 my @vals = map {$_->{$key}} @$arr;
  3         7  
382 1         5 \@vals;
383             }
384              
385             sub array_to_sentence {
386 7     7 1 20 my ($arr, $join, $join_last) = @_;
387 7   50     36 $join //= ', ';
388 7   50     34 $join_last //= ' and ';
389 7         11 my $size = scalar @$arr;
390 7 100       76 $size > 2
391             ? join($join_last, join($join, @$arr[0 .. $size - 2]), $arr->[-1])
392             : join($join_last, @$arr);
393             }
394              
395             sub array_sum {
396 1     1 1 2 List::Util::sum(0, @{$_[0]});
  1         10  
397             }
398              
399             sub array_includes {
400 2     2 1 5 my ($arr, $val) = @_;
401 2   100     12 is_same($val, $_) && return 1 for @$arr;
402 1         205 0;
403             }
404              
405             sub array_any {
406 2     2 1 5 my ($arr, $sub) = @_;
407 2   100     7 $sub->($_) && return 1 for @$arr;
408 1         13 0;
409             }
410              
411             sub array_rest {
412 1     1 1 4 my ($arr) = @_;
413 1 50       11 @$arr < 2 ? [] : [@$arr[1 .. (@$arr - 1)]];
414             }
415              
416             sub array_uniq {
417 1     1 1 3 my ($arr) = @_;
418 1         3 my %seen = ();
419 1         3 my @vals = grep {not $seen{$_}++} @$arr;
  10         27  
420 1         6 \@vals;
421             }
422              
423             sub array_split {
424 20     20 1 381 my ($arr) = @_;
425 20 100       395 is_array_ref($arr) ? $arr : [split ',', $arr];
426             }
427              
428             sub as_utf8 {
429 31     31 1 842 my $str = $_[0];
430 31         94 utf8::upgrade($str);
431 31         562 $str;
432             }
433              
434             sub trim {
435 658     658 1 1188 my $str = $_[0];
436 658 100       1499 if ($str) {
437 651         1817 $str =~ s/^[\h\v]+//s;
438 651         1611 $str =~ s/[\h\v]+$//s;
439             }
440 658         1751 $str;
441             }
442              
443             sub capitalize {
444 1     1 1 2 my $str = $_[0];
445 1         5 utf8::upgrade($str);
446 1         10 ucfirst lc $str;
447             }
448              
449             sub is_different {
450 1     1 1 586 !is_same(@_);
451             }
452              
453             sub check_same {
454 2 100   2 1 587 is_same(@_) || Catmandu::BadVal->throw('should be same');
455 1         238 $_[0];
456             }
457              
458             sub check_different {
459 2 100   2 1 89 is_same(@_) && Catmandu::BadVal->throw('should be different');
460 1         230 $_[0];
461             }
462              
463             sub is_bool {
464 22     22 0 35 my $obj = $_[0];
465 22 100       455 Scalar::Util::blessed($obj) || return 0;
466 8   50     19 eval {
467 8 50 33     280 $obj->isa('boolean')
      33        
      33        
468             || $obj->isa('Types::Serialiser::Boolean')
469             || $obj->isa('JSON::XS::Boolean')
470             || $obj->isa('Cpanel::JSON::XS::Boolean')
471             || $obj->isa('JSON::PP::Boolean');
472             } // 0;
473             }
474              
475             sub is_integer {
476 882 100   882 1 79214 Data::Util::is_integer($_[0]) && $_[0] !~ /^0[0-9]/;
477             }
478              
479             sub is_natural {
480 855 100   855 1 10271 is_integer($_[0]) && $_[0] >= 0;
481             }
482              
483             sub is_positive {
484 21 100   21 1 8486 is_integer($_[0]) && $_[0] >= 1;
485             }
486              
487             sub is_float {
488 0 0 0 0 1 0 is_value($_[0])
489             && $_[0] =~ /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/
490             && $_[0] !~ /^0[0-9]/;
491             }
492              
493             sub is_ref {
494 981 100   981 1 18055 ref $_[0] ? 1 : 0;
495             }
496              
497             sub is_able {
498 57     57 1 14353 my $obj = shift;
499 57 100       305 is_invocant($obj) || return 0;
500 43   100     366 $obj->can($_) || return 0 for @_;
501 40         423 1;
502             }
503              
504             sub check_able {
505 24     24 1 123 my $obj = shift;
506 24 100       89 return $obj if is_able($obj, @_);
507 2         19 Catmandu::BadVal->throw('should be able to ' . array_to_sentence(\@_));
508             }
509              
510             sub check_maybe_able {
511 2     2 1 51 my $obj = shift;
512 2 100       9 return $obj if is_maybe_able($obj, @_);
513 1         4 Catmandu::BadVal->throw(
514             'should be undef or able to ' . array_to_sentence(\@_));
515             }
516              
517             sub is_instance {
518 186     186 1 7860 my $obj = shift;
519 186 100       1179 Scalar::Util::blessed($obj) || return 0;
520 41         144 for my $pkg (@_) {
521 30 100       72 eval {$obj->isa($pkg)} || return 0;
  30         249  
522             }
523 36         327 1;
524             }
525              
526             sub check_instance {
527 2     2 1 53 my $obj = shift;
528 2 100       7 return $obj if is_instance($obj, @_);
529 1         5 Catmandu::BadVal->throw(
530             'should be instance of ' . array_to_sentence(\@_));
531             }
532              
533             sub check_maybe_instance {
534 2     2 1 50 my $obj = shift;
535 2 100       11 return $obj if is_maybe_instance($obj, @_);
536 1         5 Catmandu::BadVal->throw(
537             'should be undef or instance of ' . array_to_sentence(\@_));
538             }
539              
540             Data::Util::install_subroutine(__PACKAGE__,
541             hash_merge => \&Hash::Merge::Simple::merge,
542             is_same => \&Data::Compare::Compare,
543             is_invocant => \&Data::Util::is_invocant,
544             is_scalar_ref => \&Data::Util::is_scalar_ref,
545             is_array_ref => \&Data::Util::is_array_ref,
546             is_hash_ref => \&Data::Util::is_hash_ref,
547             is_code_ref => \&Data::Util::is_code_ref,
548             is_regex_ref => \&Data::Util::is_rx,
549             is_glob_ref => \&Data::Util::is_glob_ref,
550             is_value => \&Data::Util::is_value,
551             is_string => \&Data::Util::is_string,
552             is_number => \&Data::Util::is_number,
553             );
554              
555             for my $sym (
556             qw(able instance invocant ref
557             scalar_ref array_ref hash_ref code_ref regex_ref glob_ref
558             bool value string number integer natural positive float)
559             )
560             {
561             my $err_name = $sym;
562             $err_name =~ s/_/ /;
563              
564             push @EXPORT_OK, "is_$sym", "is_maybe_$sym", "check_$sym",
565             "check_maybe_$sym";
566             push @{$EXPORT_TAGS{is}}, "is_$sym", "is_maybe_$sym";
567             push @{$EXPORT_TAGS{check}}, "check_$sym", "check_maybe_$sym";
568              
569             unless (Data::Util::get_code_ref(__PACKAGE__, "is_maybe_$sym")) {
570             my $sub
571             = Sub::Quote::quote_sub("!defined(\$_[0]) || is_$sym(\$_[0])");
572             Data::Util::install_subroutine(__PACKAGE__, "is_maybe_$sym" => $sub);
573             }
574              
575             unless (Data::Util::get_code_ref(__PACKAGE__, "check_$sym")) {
576             my $sub
577             = Sub::Quote::quote_sub(
578             "is_$sym(\$_[0]) || Catmandu::BadVal->throw('should be $err_name'); \$_[0]"
579             );
580             Data::Util::install_subroutine(__PACKAGE__, "check_$sym" => $sub);
581             }
582              
583             unless (Data::Util::get_code_ref(__PACKAGE__, "check_maybe_$sym")) {
584             my $sub
585             = Sub::Quote::quote_sub(
586             "is_maybe_$sym(\$_[0]) || Catmandu::BadVal->throw('should be undef or $err_name'); \$_[0]"
587             );
588             Data::Util::install_subroutine(__PACKAGE__,
589             "check_maybe_$sym" => $sub);
590             }
591             }
592              
593             sub human_number { # taken from Number::Format
594 1     1 1 2 my $num = $_[0];
595              
596             # add leading 0's so length($num) is divisible by 3
597 1         6 $num = '0' x (3 - (length($num) % 3)) . $num;
598              
599             # split $num into groups of 3 characters and insert commas
600 1         9 $num = join ',', grep {$_ ne ''} split /(...)/, $num;
  4         11  
601              
602             # strip off leading zeroes and/or comma
603 1         5 $num =~ s/^0+,?//;
604 1 50       8 length $num ? $num : '0';
605             }
606              
607             sub human_byte_size {
608 4     4 1 10 my ($size) = @_;
609 4 100       18 if ($size > 1000000000) {
    100          
    100          
610 1         14 return sprintf("%.2f GB", $size / 1000000000);
611             }
612             elsif ($size > 1000000) {
613 1         14 return sprintf("%.2f MB", $size / 1000000);
614             }
615             elsif ($size > 1000) {
616 1         17 return sprintf("%.2f KB", $size / 1000);
617             }
618 1         7 "$size bytes";
619             }
620              
621             sub human_content_type {
622 1     1 1 4 my ($content_type, $default) = @_;
623 1         9 my ($key) = $content_type =~ /^([^;]+)/;
624 1   33     10 $HUMAN_CONTENT_TYPES->{$key} // $default // $content_type;
      33        
625             }
626              
627             sub xml_declaration {
628 1     1 1 6 $XML_DECLARATION;
629             }
630              
631             sub xml_escape {
632 1     1 1 5 my ($str) = @_;
633 1         4 utf8::upgrade($str);
634              
635 1         8 $str =~ s/&/&amp;/go;
636 1         9 $str =~ s/</&lt;/go;
637 1         7 $str =~ s/>/&gt;/go;
638 1         4 $str =~ s/"/&quot;/go;
639 1         6 $str =~ s/'/&apos;/go;
640              
641             # remove control chars
642 1         4 $str
643             =~ s/[^\x09\x0A\x0D\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]//go;
644              
645 1         5 $str;
646             }
647              
648             sub use_lib {
649 2     2 1 7 my (@dirs) = @_;
650              
651 177     177   83775 use lib;
  177         115583  
  177         1752  
652 2         5 local $@;
653 2         18 lib->import(@dirs);
654 2 50       281 Catmandu::Error->throw($@) if $@;
655              
656 2         7 1;
657             }
658              
659             sub pod_section {
660 962 50   962 1 2503 my $class = is_ref($_[0]) ? ref(shift) : shift;
661 962         2023 my $section = uc(shift);
662              
663 962 50       16281 unless (-r $class) {
664 0         0 $class =~ s!::!/!g;
665 0         0 $class .= '.pm';
666 0 0       0 $class = $INC{$class} or return '';
667             }
668              
669 962         2362 my $text = "";
670 962 50       33177 open my $input, "<", $class or return '';
671 962     3   10885 open my $output, ">", \$text;
  3         29  
  3         9  
  3         24  
672              
673 962         9385 require Pod::Usage; # lazy load only if needed
674 962         85780 Pod::Usage::pod2usage(
675             -input => $input,
676             -output => $output,
677             -sections => $section,
678             -exit => "NOEXIT",
679             -verbose => 99,
680             -indent => 0,
681             -utf8 => 1,
682             @_
683             );
684 962         6522152 $section = ucfirst(lc($section));
685 962         5669 $text =~ s/$section:\n//m;
686 962         2443 chomp $text;
687              
688 962         20404 $text;
689             }
690              
691             sub require_package {
692 884     884 1 2361 my ($pkg, $ns) = @_;
693              
694 884 100       2389 if ($ns) {
695 812 50 33     13056 unless ($pkg =~ s/^\+// || $pkg =~ /^$ns/) {
696 812         3189 $pkg = "${ns}::$pkg";
697             }
698             }
699              
700 884 100       7575 return $pkg if is_invocant($pkg);
701              
702 270 100       20906 eval "require $pkg;1;"
703             or Catmandu::NoSuchPackage->throw(
704             message => "No such package: $pkg",
705             package_name => $pkg
706             );
707              
708 264         1959 $pkg;
709             }
710              
711             sub now {
712 7     7 1 55 my $format = $_[0];
713 7         15 my $now;
714              
715 7 100 100     48 if (!defined $format || $format eq 'iso_date_time') {
    100          
716 5         480 $now = POSIX::strftime('%Y-%m-%dT%H:%M:%SZ', gmtime(time));
717             }
718             elsif ($format eq 'iso_date_time_millis') {
719 1         9 my $t = Time::HiRes::time;
720 1         44 $now = POSIX::strftime('%Y-%m-%dT%H:%M:%S', gmtime($t));
721 1         13 $now .= sprintf('.%03d', ($t - int($t)) * 1000);
722 1         7 $now .= 'Z';
723             }
724             else {
725 1         44 $now = POSIX::strftime($format, gmtime(time));
726             }
727             }
728              
729             1;
730              
731             __END__
732              
733             =pod
734              
735             =head1 NAME
736              
737             Catmandu::Util - A collection of utility functions
738              
739             =head1 SYNOPSIS
740              
741             use Catmandu::Util qw(:string);
742              
743             $str = trim($str);
744              
745             =head1 FUNCTIONS
746              
747             =head2 IO functions
748              
749             use Catmandu::Util qw(:io);
750              
751             =over 4
752              
753             =item io($io, %opts)
754              
755             Takes a file path, glob, glob reference, scalar reference or L<IO::Handle>
756             object and returns an opened L<IO::Handle> object.
757              
758             my $fh = io '/path/to/file';
759              
760             my $fh = io *STDIN;
761              
762             my $fh = io \*STDOUT, mode => 'w', binmode => ':crlf';
763              
764             my $write_cb = sub { my $str = $_[0]; ... };
765              
766             my $fh = io $write_cb, mode => 'w';
767              
768             my $scalar = "";
769             my $fh = io \$scalar, mode => 'w';
770             $fh->print("some text");
771              
772             Options are:
773              
774             =over 12
775              
776             =item mode
777              
778             Default is C<"r">.
779              
780             =item binmode
781              
782             Default is C<":encoding(UTF-8)">.
783              
784             =item encoding
785              
786             Alias for C<binmode>.
787              
788             =back
789              
790             =item read_file($path);
791              
792             [deprecated]: use tools like Path::Tiny instead.
793              
794             Reads the file at C<$path> into a string.
795              
796             my $str = read_file('/path/to/file.txt');
797              
798             Throws a Catmandu::Error on failure.
799              
800             =item read_io($io)
801              
802             Reads an IO::Handle into a string.
803              
804             my $str = read_file($fh);
805              
806             =item write_file($path, $str);
807              
808             [deprecated]: use tools like use tools like File::Slurp::Tiny instead.
809              
810             Writes the string C<$str> to a file at C<$path>.
811              
812             write_file('/path/to/file.txt', "contents");
813              
814             Throws a Catmandu::Error on failure.
815              
816             =item read_yaml($path);
817              
818             Reads the YAML file at C<$path> into a Perl hash.
819              
820             my $cfg = read_yaml($path);
821              
822             Dies on failure reading the file or parsing the YAML.
823              
824             =item read_json($path);
825              
826             Reads the JSON file at C<$path> into a Perl hash.
827              
828             my $cfg = read_json($path);
829              
830             Dies on failure reading the file or parsing the JSON.
831              
832             =item join_path(@path);
833              
834             Joins relative paths into an absolute path.
835              
836             join_path('/path/..', './to', 'file.txt');
837             # => "/to/file.txt"
838              
839             =item normalize_path($path);
840              
841             Normalizes a relative path to an absolute path.
842              
843             normalize_path('/path/../to/./file.txt');
844             # => "/to/file.txt"
845              
846             =item segmented_path($path);
847              
848             my $id = "FB41144C-F0ED-11E1-A9DE-61C894A0A6B4";
849             segmented_path($id, segment_size => 4);
850             # => "FB41/144C/F0ED/11E1/A9DE/61C8/94A0/A6B4"
851             segmented_path($id, segment_size => 2, base_path => "/files");
852             # => "/files/FB/41/14/4C/F0/ED/11/E1/A9/DE/61/C8/94/A0/A6/B4"
853              
854             =item content_type($filename);
855              
856             Guess the content type of a file name.
857              
858             content_type("book.pdf");
859             # => "application/pdf"
860              
861             =back
862              
863             =head2 Hash functions
864              
865             use Catmandu::Util qw(:hash);
866              
867             A collection of functions that operate on hash references.
868              
869             =over 4
870              
871             =item hash_merge($hash1, $hash2, ... , $hashN)
872              
873             Merge <hash1> through <hashN>, with the nth-most (rightmost) hash taking precedence.
874             Returns a new hash reference representing the merge.
875              
876             hash_merge({a => 1}, {b => 2}, {a => 3});
877             # => { a => 3 , b => 2}
878              
879             =back
880              
881             =head2 Array functions
882              
883             use Catmandu::Util qw(:array);
884              
885             A collection of functions that operate on array references.
886              
887             =over 4
888              
889             =item array_exists($array, $index)
890              
891             Returns C<1> if C<$index> is in the bounds of C<$array>
892              
893             array_exists(["a", "b"], 2);
894             # => 0
895             array_exists(["a", "b"], 1);
896             # => 1
897              
898             =item array_group_by($array, $key)
899              
900             my $list = [{color => 'black', id => 1},
901             {color => 'white', id => 2},
902             {id => 3},
903             {color => 'black', id => 4}];
904             array_group_by($list, 'color');
905             # => {black => [{color => 'black', id => 1}, {color => 'black', id => 4}],
906             # white => [{color => 'white', id => 2}]}
907              
908             =item array_pluck($array, $key)
909              
910             my $list = [{id => 1}, {}, {id => 3}];
911             array_pluck($list, 'id');
912             # => [1, undef, 3]
913              
914             =item array_to_sentence($array)
915              
916             =item array_to_sentence($array, $join)
917              
918             =item array_to_sentence($array, $join, $join_last)
919              
920             array_to_sentence([1,2,3]);
921             # => "1, 2 and 3"
922             array_to_sentence([1,2,3], ",");
923             # => "1,2 and 3"
924             array_to_sentence([1,2,3], ",", " & ");
925             # => "1,2 & 3"
926              
927             =item array_sum($array)
928              
929             array_sum([1,2,3]);
930             # => 6
931              
932             =item array_includes($array, $val)
933              
934             Returns 1 if C<$array> includes a value that is deeply equal to C<$val>, 0
935             otherwise. Comparison is done with C<is_same()>.
936              
937             array_includes([{color => 'black'}], {color => 'white'});
938             # => 0
939             array_includes([{color => 'black'}], {color => 'black'});
940             # => 1
941              
942             =item array_any($array, \&sub)
943              
944             array_any(["green", "blue"], sub { my $color = $_[0]; $color eq "blue" });
945             # => 1
946              
947             =item array_rest($array)
948              
949             Returns a copy of C<$array> without the head.
950              
951             array_rest([1,2,3,4]);
952             # => [2,3,4]
953             array_rest([1]);
954             # => []
955              
956             =item array_uniq($array)
957              
958             Returns a copy of C<$array> with all duplicates removed.
959              
960             =item array_split($array | $string)
961              
962             Returns C<$array> or a new array by splitting C<$string> at commas.
963              
964             =back
965              
966             =head2 String functions
967              
968             use Catmandu::Util qw(:string);
969              
970             =over 4
971              
972             =item as_utf8($str)
973              
974             Returns a copy of C<$str> flagged as UTF-8.
975              
976             =item trim($str)
977              
978             Returns a copy of C<$str> with leading and trailing whitespace removed.
979              
980             =item capitalize($str)
981              
982             Equivalent to C<< ucfirst lc as_utf8 $str >>.
983              
984             =back
985              
986             =head2 Is functions
987              
988             use Catmandu::Util qw(:is);
989              
990             is_number(42) ? "it's numeric" : "it's not numeric";
991              
992             is_maybe_hash_ref({});
993             # => 1
994             is_maybe_hash_ref(undef);
995             # => 1
996             is_maybe_hash_ref([]);
997             # => 0
998              
999             A collection of predicate functions that test the type or value of argument
1000             C<$val>. Each function (except C<is_same()> and C<is_different>) also has a
1001             I<maybe> variant that also tests true if C<$val> is undefined.
1002             Returns C<1> or C<0>.
1003              
1004             =over 4
1005              
1006             =item is_invocant($val)
1007              
1008             =item is_maybe_invocant($val)
1009              
1010             Tests if C<$val> is callable (is an existing package or blessed object).
1011              
1012             =item is_able($val, @method_names)
1013              
1014             =item is_maybe_able($val, @method_names)
1015              
1016             Tests if C<$val> is callable and has all methods in C<@method_names>.
1017              
1018             =item is_instance($val, @class_names)
1019              
1020             =item is_maybe_instance($val, @class_names)
1021              
1022             Tests if C<$val> is a blessed object and an instance of all the classes
1023             in C<@class_names>.
1024              
1025             =item is_ref($val)
1026              
1027             =item is_maybe_ref($val)
1028              
1029             Tests if C<$val> is a reference. Equivalent to C<< ref $val ? 1 : 0 >>.
1030              
1031             =item is_scalar_ref($val)
1032              
1033             =item is_maybe_scalar_ref($val)
1034              
1035             Tests if C<$val> is a scalar reference.
1036              
1037             =item is_array_ref($val)
1038              
1039             =item is_maybe_array_ref($val)
1040              
1041             Tests if C<$val> is an array reference.
1042              
1043             =item is_hash_ref($val)
1044              
1045             =item is_maybe_hash_ref($val)
1046              
1047             Tests if C<$val> is a hash reference.
1048              
1049             =item is_code_ref($val)
1050              
1051             =item is_maybe_code_ref($val)
1052              
1053             Tests if C<$val> is a subroutine reference.
1054              
1055             =item is_regex_ref($val)
1056              
1057             =item is_maybe_regex_ref($val)
1058              
1059             Tests if C<$val> is a regular expression reference generated by the C<qr//>
1060             operator.
1061              
1062             =item is_glob_ref($val)
1063              
1064             =item is_maybe_glob_ref($val)
1065              
1066             Tests if C<$val> is a glob reference.
1067              
1068             =item is_value($val)
1069              
1070             =item is_maybe_value($val)
1071              
1072             Tests if C<$val> is a real value (defined, not a reference and not a
1073             glob.
1074              
1075             =item is_string($val)
1076              
1077             =item is_maybe_string($val)
1078              
1079             Tests if C<$val> is a non-empty string.
1080             Equivalent to C<< is_value($val) && length($val) > 0 >>.
1081              
1082             =item is_number($val)
1083              
1084             =item is_maybe_number($val)
1085              
1086             Tests if C<$val> is a number.
1087              
1088             =item is_integer($val)
1089              
1090             =item is_maybe_integer($val)
1091              
1092             Tests if C<$val> is an integer.
1093              
1094             =item is_natural($val)
1095              
1096             =item is_maybe_natural($val)
1097              
1098             Tests if C<$val> is a non-negative integer.
1099             Equivalent to C<< is_integer($val) && $val >= 0 >>.
1100              
1101             =item is_positive($val)
1102              
1103             =item is_maybe_positive($val)
1104              
1105             Tests if C<$val> is a positive integer.
1106             Equivalent to C<< is_integer($val) && $val >= 1 >>.
1107              
1108             =item is_float($val)
1109              
1110             =item is_maybe_float($val)
1111              
1112             Tests if C<$val> is a floating point number.
1113              
1114             =item is_same($val, $other_val)
1115              
1116             Tests if C<$val> is deeply equal to C<$other_val>.
1117              
1118             =item is_different($val, $other_val)
1119              
1120             The opposite of C<is_same()>.
1121              
1122             =back
1123              
1124             =head2 Check functions
1125              
1126             use Catmandu::Util qw(:check);
1127              
1128             check_hash_ref({color => 'red'});
1129             # => {color => 'red'}
1130             check_hash_ref([]);
1131             # dies
1132              
1133             A group of assert functions similar to the C<:is> group, but instead of
1134             returning true or false they return their argument or die.
1135              
1136             =over 4
1137              
1138             =item check_invocant($val)
1139              
1140             =item check_maybe_invocant($val)
1141              
1142             =item check_able($val, @method_names)
1143              
1144             =item check_maybe_able($val, @method_names)
1145              
1146             =item check_instance($val, @class_names)
1147              
1148             =item check_maybe_instance($val, @class_names)
1149              
1150             =item check_ref($val)
1151              
1152             =item check_maybe_ref($val)
1153              
1154             =item check_scalar_ref($val)
1155              
1156             =item check_maybe_scalar_ref($val)
1157              
1158             =item check_array_ref($val)
1159              
1160             =item check_maybe_array_ref($val)
1161              
1162             =item check_hash_ref($val)
1163              
1164             =item check_maybe_hash_ref($val)
1165              
1166             =item check_code_ref($val)
1167              
1168             =item check_maybe_code_ref($val)
1169              
1170             =item check_regex_ref($val)
1171              
1172             =item check_maybe_regex_ref($val)
1173              
1174             =item check_glob_ref($val)
1175              
1176             =item check_maybe_glob_ref($val)
1177              
1178             =item check_value($val)
1179              
1180             =item check_maybe_value($val)
1181              
1182             =item check_string($val)
1183              
1184             =item check_maybe_string($val)
1185              
1186             =item check_number($val)
1187              
1188             =item check_maybe_number($val)
1189              
1190             =item check_integer($val)
1191              
1192             =item check_maybe_integer($val)
1193              
1194             =item check_natural($val)
1195              
1196             =item check_maybe_natural($val)
1197              
1198             =item check_positive($val)
1199              
1200             =item check_maybe_positive($val)
1201              
1202             =item check_float($val)
1203              
1204             =item check_maybe_float($val)
1205              
1206             =item check_same($val, $other_val)
1207              
1208             =item check_different($val, $other_val)
1209              
1210             =back
1211              
1212             =head2 Human output functions
1213              
1214             use Catmandu::Util qw(:human);
1215              
1216             =over 4
1217              
1218             =item human_number($num)
1219              
1220             Insert a comma a 3-digit intervals to make C<$num> more readable. Only works
1221             with I<integers> for now.
1222              
1223             human_number(64354);
1224             # => "64,354"
1225              
1226             =item human_byte_size($size)
1227              
1228             human_byte_size(64);
1229             # => "64 bytes"
1230             human_byte_size(10005000);
1231             # => "10.01 MB"
1232              
1233             =item human_content_type($content_type)
1234              
1235             =item human_content_type($content_type, $default)
1236              
1237             human_content_type('application/x-dos_ms_excel');
1238             # => "Excel"
1239             human_content_type('application/zip');
1240             # => "ZIP archive"
1241             human_content_type('foo/x-unknown');
1242             # => "foo/x-unknown"
1243             human_content_type('foo/x-unknown', 'Unknown');
1244             # => "Unknown"
1245              
1246             =back
1247              
1248             =head2 XML functions
1249              
1250             use Catmandu::Util qw(:xml);
1251              
1252             =over 4
1253              
1254             =item xml_declaration()
1255              
1256             Returns C<< qq(<?xml version="1.0" encoding="UTF-8"?>\n) >>.
1257              
1258             =item xml_escape($str)
1259              
1260             Returns an XML escaped copy of C<$str>.
1261              
1262             =back
1263              
1264             =head2 Miscellaneous functions
1265              
1266             =over 4
1267              
1268             =item require_package($pkg)
1269              
1270             =item require_package($pkg, $namespace)
1271              
1272             Load package C<$pkg> at runtime with C<require> and return it's full name.
1273              
1274             my $pkg = require_package('File::Spec');
1275             my $dir = $pkg->tmpdir();
1276              
1277             require_package('Util', 'Catmandu');
1278             # => "Catmandu::Util"
1279             require_package('Catmandu::Util', 'Catmandu');
1280             # => "Catmandu::Util"
1281              
1282             Throws a Catmandu::Error on failure.
1283              
1284             =item use_lib(@dirs)
1285              
1286             Add directories to C<@INC> at runtime.
1287              
1288             Throws a Catmandu::Error on failure.
1289              
1290             =item pod_section($package_or_file, $section [, @options] )
1291              
1292             Get documentation of a package for a selected section. Additional options are
1293             passed to L<Pod::Usage>.
1294              
1295             =item now($format)
1296              
1297             Returns the current datetime as a string. C<$format>can be any
1298             C<strftime> format. There are also 2 builtin formats, C<iso_date_time>
1299             and C<iso_date_time_millis>. C<iso_date_time> is equivalent to
1300             C<%Y-%m-%dT%H:%M:%SZ>. C<iso_date_time_millis> is the same, but with
1301             added milliseconds.
1302              
1303             now('%Y/%m/%d');
1304             now('iso_date_time_millis');
1305              
1306             The default format is C<iso_date_time>;
1307              
1308             =back
1309              
1310             =cut