File Coverage

blib/lib/Pinto/Util.pm
Criterion Covered Total %
statement 155 189 82.0
branch 43 78 55.1
condition 7 12 58.3
subroutine 48 53 90.5
pod 32 35 91.4
total 285 367 77.6


line stmt bran cond sub pod time code
1             # ABSTRACT: Static utility functions for Pinto
2              
3             package Pinto::Util;
4              
5 66     66   84583 use strict;
  66         135  
  66         1965  
6 66     66   340 use warnings;
  66         122  
  66         1650  
7 66     66   8824 use version;
  66         54065  
  66         401  
8 66     66   5264 use base qw(Exporter);
  66         147  
  66         5232  
9              
10 66     66   13221 use URI;
  66         155655  
  66         3329  
11 66     66   21691 use URI::file;
  66         172927  
  66         2217  
12 66     66   556 use Carp;
  66         175  
  66         4623  
13 66     66   38337 use DateTime;
  66         25376961  
  66         3186  
14 66     66   20122 use File::Temp;
  66         468677  
  66         5174  
15 66     66   11541 use Path::Class;
  66         443343  
  66         3331  
16 66     66   477 use Digest::MD5;
  66         131  
  66         2372  
17 66     66   20589 use Digest::SHA;
  66         111074  
  66         3171  
18 66     66   506 use Scalar::Util;
  66         146  
  66         2274  
19 66     66   21593 use UUID::Tiny;
  66         539020  
  66         6438  
20 66     66   11586 use Readonly;
  66         111398  
  66         3240  
21              
22 66     66   18905 use Pinto::Globals;
  66         261  
  66         2361  
23 66     66   11587 use Pinto::Constants qw(:all);
  66         230  
  66         2630  
24 66     66   44101 use Pinto::Types qw(DiffStyle);
  66         284  
  66         575  
25              
26             #-------------------------------------------------------------------------------
27              
28             our $VERSION = '0.13'; # VERSION
29              
30             #-------------------------------------------------------------------------------
31              
32             Readonly our @EXPORT_OK => qw(
33             author_dir
34             body_text
35             current_author_id
36             current_utc_time
37             current_time_offset
38             current_username
39             debug
40             decamelize
41             default_diff_style
42             indent_text
43             interpolate
44             is_blank
45             is_not_blank
46             is_interactive
47             is_remote_repo
48             is_system_prop
49             isa_perl
50             itis
51             make_uri
52             md5
53             mksymlink
54             mtime
55             parse_dist_path
56             mask_uri_passwords
57             sha256
58             tempdir
59             title_text
60             throw
61             trim_text
62             truncate_text
63             user_palette
64             uuid
65             whine
66             );
67              
68             Readonly our %EXPORT_TAGS => ( all => \@EXPORT_OK );
69              
70             #-------------------------------------------------------------------------------
71              
72              
73             sub throw {
74 109     109 1 23047376 my ($error) = @_;
75              
76             # Rethrowing...
77 109 100       659 $error->throw if itis( $error, 'Pinto::Exception' );
78              
79 67         758 require Pinto::Exception;
80 67         1957 Pinto::Exception->throw( message => "$error" );
81              
82 0         0 return; # Should never get here
83             }
84              
85             #-------------------------------------------------------------------------------
86              
87              
88             sub debug {
89 6958     6958 1 226833 my ($it) = @_;
90              
91             # TODO: Use Carp instead?
92              
93 6958 50       35685 return 1 if not $ENV{PINTO_DEBUG};
94              
95 0 0       0 $it = $it->() if ref $it eq 'CODE';
96 0         0 my ( $file, $line ) = (caller)[ 1, 2 ];
97 0         0 print {*STDERR} "$it in $file at line $line\n";
  0         0  
98              
99 0         0 return 1;
100             }
101              
102             #-------------------------------------------------------------------------------
103              
104              
105             sub whine {
106 10     10 1 110 my ($message) = @_;
107              
108 10 50       55 if ( $ENV{DEBUG} ) {
109 0         0 Carp::cluck($message);
110 0         0 return 1;
111             }
112              
113 10         40 chomp $message;
114 10         190 warn $message . "\n";
115              
116 10         374 return 1;
117             }
118              
119             #-------------------------------------------------------------------------------
120              
121              
122             sub author_dir { ## no critic (ArgUnpacking)
123 132     132 1 156249 my $author = uc pop;
124 132         483 my @base = @_;
125              
126 132         1205 return dir( @base, substr( $author, 0, 1 ), substr( $author, 0, 2 ), $author );
127             }
128              
129             #-------------------------------------------------------------------------------
130              
131              
132             sub itis {
133 2138     2138 1 8397 my ( $var, $class ) = @_;
134              
135 2138   100     24518 return ref $var && Scalar::Util::blessed($var) && $var->isa($class);
136             }
137              
138             #-------------------------------------------------------------------------------
139              
140              
141             sub parse_dist_path {
142 103     103 1 1375 my ($path) = @_;
143              
144             # eg: /yadda/authors/id/A/AU/AUTHOR/subdir1/subdir2/Foo-1.0.tar.gz
145             # or: A/AU/AUTHOR/subdir/Foo-1.0.tar.gz
146              
147 103 50       1444 if ( $path =~ s{^ (?:.*/authors/id/)? (.*) $}{$1}mx ) {
148              
149             # $path = 'A/AU/AUTHOR/subdir/Foo-1.0.tar.gz'
150 103         603 my @path_parts = split m{ / }mx, $path;
151 103         278 my $author = $path_parts[2]; # AUTHOR
152 103         197 my $archive = $path_parts[-1]; # Foo-1.0.tar.gz
153 103         617 return ( $author, $archive );
154             }
155              
156 0         0 throw "Unable to parse path: $path";
157             }
158              
159             #-------------------------------------------------------------------------------
160              
161              
162             sub isa_perl {
163 0     0 1 0 my ($path_or_uri) = @_;
164              
165 0         0 return $path_or_uri =~ m{ / perl-[\d.]+ \.tar \.(?: gz|bz2 ) $ }mx;
166             }
167              
168             #-------------------------------------------------------------------------------
169              
170              
171             sub mtime {
172 161     161 1 1563 my ($file) = @_;
173              
174 161 50       857 throw 'Must supply a file' if not $file;
175 161 50       1533 throw "$file does not exist" if not -e $file;
176              
177 161         11003 return ( stat $file )[9];
178             }
179              
180             #-------------------------------------------------------------------------------
181              
182              
183             sub md5 {
184 161     161 1 6456 my ($file) = @_;
185              
186 161 50       534 throw 'Must supply a file' if not $file;
187 161 50       1245 throw "$file does not exist" if not -e $file;
188              
189 161         6317 my $fh = $file->openr();
190 161         31393 my $md5 = Digest::MD5->new->addfile($fh)->hexdigest();
191              
192 161         2389 return $md5;
193             }
194              
195             #-------------------------------------------------------------------------------
196              
197              
198             sub sha256 {
199 443     443 1 62913 my ($file) = @_;
200              
201 443 50       2725 throw 'Must supply a file' if not $file;
202 443 50       4561 throw "$file does not exist" if not -e $file;
203              
204 443         32267 my $fh = $file->openr();
205 443         83393 my $sha256 = Digest::SHA->new(256)->addfile($fh)->hexdigest();
206              
207 443         39666 return $sha256;
208             }
209              
210             #-------------------------------------------------------------------------------
211              
212              
213             sub validate_property_name {
214 269     269 1 622 my ($prop_name) = @_;
215              
216 269 100       1713 throw "Invalid property name $prop_name" if $prop_name !~ $PINTO_PROPERTY_NAME_REGEX;
217              
218 268         3389 return $prop_name;
219             }
220              
221             #-------------------------------------------------------------------------------
222              
223              
224             sub validate_stack_name {
225 0     0 1 0 my ($stack_name) = @_;
226              
227 0 0       0 throw "Invalid stack name $stack_name" if $stack_name !~ $PINTO_STACK_NAME_REGEX;
228              
229 0         0 return $stack_name;
230             }
231              
232             #-------------------------------------------------------------------------------
233              
234              
235             sub current_utc_time {
236              
237             ## no critic qw(PackageVars)
238 519 100   519 1 2066 return $Pinto::Globals::current_utc_time
239             if defined $Pinto::Globals::current_utc_time;
240              
241 515         2121 return time;
242             }
243              
244             #-------------------------------------------------------------------------------
245              
246              
247             sub current_time_offset {
248              
249             ## no critic qw(PackageVars)
250 76 100   76 1 320 return $Pinto::Globals::current_time_offset
251             if defined $Pinto::Globals::current_time_offset;
252              
253 75         349 my $now = current_utc_time;
254 75         1185 my $time = DateTime->from_epoch( epoch => $now, time_zone => 'local' );
255              
256 75         120395 return $time->offset;
257             }
258              
259             #-------------------------------------------------------------------------------
260              
261              
262             sub current_username {
263              
264             ## no critic qw(PackageVars)
265 85 100   85 1 3712 return $Pinto::Globals::current_username
266             if defined $Pinto::Globals::current_username;
267              
268 9   33     202 my $username = $ENV{PINTO_USERNAME} || $ENV{USER} || $ENV{LOGIN} || $ENV{USERNAME} || $ENV{LOGNAME};
269              
270 9 50       63 throw "Unable to determine your username. Set PINTO_USERNAME." if not $username;
271              
272 0         0 return $username;
273             }
274              
275             #-------------------------------------------------------------------------------
276              
277              
278             sub current_author_id {
279              
280             ## no critic qw(PackageVars)
281 20 50   20 1 507 return $Pinto::Globals::current_author_id
282             if defined $Pinto::Globals::current_author_id;
283              
284 0         0 my $author_id = $ENV{PINTO_AUTHOR_ID};
285 0 0       0 return uc $author_id if $author_id;
286              
287 0         0 my $username = current_username;
288 0         0 $username =~ s/[^a-zA-Z0-9]//g;
289              
290 0         0 return uc $username;
291             }
292              
293             #-------------------------------------------------------------------------------
294              
295              
296             sub is_interactive {
297              
298             ## no critic qw(PackageVars)
299 78 50   78 1 640 return $Pinto::Globals::is_interactive
300             if defined $Pinto::Globals::is_interactive;
301              
302 0         0 return -t STDOUT;
303             }
304              
305             #-------------------------------------------------------------------------------
306              
307              
308             sub interpolate {
309 0     0 1 0 my $string = shift;
310              
311 0         0 return eval qq{"$string"}; ## no critic qw(Eval)
312             }
313              
314             #-------------------------------------------------------------------------------
315              
316              
317             sub trim_text {
318 119     119 1 322 my $string = shift;
319              
320 119         344 $string =~ s/^ \s+ //x;
321 119         341 $string =~ s/ \s+ $//x;
322              
323 119         1234 return $string;
324             }
325              
326             #-------------------------------------------------------------------------------
327              
328              
329             sub title_text {
330 14     14 1 270 my $string = shift;
331              
332 14         62 my $nl = index $string, "\n";
333 14 100       110 return $nl < 0 ? $string : substr $string, 0, $nl;
334             }
335              
336             #-------------------------------------------------------------------------------
337              
338              
339             sub body_text {
340 5     5 1 110 my $string = shift;
341              
342 5         21 my $nl = index $string, "\n";
343 5 100 66     49 return '' if $nl < 0 or $nl == length $string;
344 2         19 return substr $string, $nl + 1;
345             }
346              
347             #-------------------------------------------------------------------------------
348              
349              
350             sub truncate_text {
351 2     2 1 4 my ( $string, $max_length, $elipses ) = @_;
352              
353 2 50       5 return $string if not $max_length;
354 2 50       11 return $string if length $string <= $max_length;
355              
356 0 0       0 $elipses = '...' if not defined $elipses;
357              
358 0         0 my $truncated = substr $string, 0, $max_length;
359              
360 0         0 return $truncated . $elipses;
361             }
362              
363             #-------------------------------------------------------------------------------
364              
365              
366             sub decamelize {
367 378     378 1 669 my $string = shift;
368              
369 378 50       926 return if not defined $string;
370              
371 378         1175 $string =~ s/ ([a-z]) ([A-Z]) /$1_$2/xg;
372              
373 378         1110 return lc $string;
374             }
375              
376             #-------------------------------------------------------------------------------
377              
378              
379             sub indent_text {
380 3     3 1 9 my ( $string, $spaces ) = @_;
381              
382 3 50       15 return $string if not $spaces;
383 3 50       9 return $string if not $string;
384              
385 3         12 my $indent = ' ' x $spaces;
386 3         16 $string =~ s/^ /$indent/xmg;
387              
388 3         13 return $string;
389             }
390              
391             #-------------------------------------------------------------------------------
392              
393              
394             sub mksymlink {
395 253     253 1 20142 my ( $from, $to ) = @_;
396              
397             # TODO: Try to add Win32 support here, somehow.
398 253         1174 debug "Linking $to to $from";
399 253 50       961 symlink $to, $from or throw "symlink to $to from $from failed: $!";
400              
401 253         17752 return 1;
402             }
403              
404             #-------------------------------------------------------------------------------
405              
406              
407             sub is_system_prop {
408 0     0 1 0 my $string = shift;
409              
410 0 0       0 return 0 if not $string;
411 0         0 return $string =~ m/^ pinto- /x;
412             }
413              
414             #-------------------------------------------------------------------------------
415              
416              
417             sub uuid {
418 183     183 1 1897 return UUID::Tiny::create_uuid_as_string(UUID::Tiny::UUID_V4);
419             }
420              
421             #-------------------------------------------------------------------------------
422              
423              
424             sub user_palette {
425             my $palette = $ENV{PINTO_PALETTE}
426 6   33 6 1 212 || $ENV{PINTO_COLORS} || $ENV{PINTO_COLOURS}; # For backcompat
427              
428 6 100       94 return $PINTO_DEFAULT_PALETTE if not $palette;
429              
430 1         29 return [ split m/\s* , \s*/x, $palette ];
431             }
432              
433             #-------------------------------------------------------------------------------
434              
435              
436             sub is_blank {
437 74     74 1 232 my ($string) = @_;
438              
439 74 50       272 return 1 if not $string;
440 74 100       2512 return 0 if $string =~ m/ \S /x;
441 2         16 return 1;
442             }
443              
444             #-------------------------------------------------------------------------------
445              
446              
447             sub is_not_blank {
448 73     73 1 255 my ($string) = @_;
449              
450 73         417 return !is_blank($string);
451             }
452              
453             #-------------------------------------------------------------------------------
454              
455              
456             sub mask_uri_passwords {
457 48     48 1 82 my ($uri) = @_;
458              
459 48         95 $uri =~ s{ (https?://[^:/@]+ :) [^@/]+@}{$1*password*@}gx;
460              
461 48         109 return $uri;
462             }
463              
464             #-------------------------------------------------------------------------------
465              
466              
467             sub is_remote_repo {
468 0     0 1 0 my ($uri) = @_;
469              
470 0 0       0 return if not $uri;
471 0         0 return $uri =~ m{^https?://}x;
472             }
473              
474             #-------------------------------------------------------------------------------
475              
476             sub tempdir {
477              
478 124     124 0 4999 return Path::Class::dir(File::Temp::tempdir(CLEANUP => 1));
479             }
480              
481              
482             #-------------------------------------------------------------------------------
483              
484              
485             sub default_diff_style {
486              
487 20 100   20 0 106 if (my $style = $ENV{PINTO_DIFF_STYLE}) {
488              
489 11 50       59 throw "PINTO_DIFF_STYLE ($style) is invalid. Must be one of (@PINTO_DIFF_STYLES)"
490             unless DiffStyle->check($style);
491              
492 11         9201 return $style;
493             }
494              
495 9         104 return $PINTO_DIFF_STYLE_CONCISE;
496             }
497              
498             #-------------------------------------------------------------------------------
499              
500             sub make_uri {
501 196     196 0 725 my ($it) = @_;
502              
503 196 100       1623 return $it
504             if itis( $it, 'URI' );
505              
506 161 50       707 return URI::file->new( $it->absolute )
507             if itis( $it, 'Path::Class::File' );
508              
509 0 0         return URI::file->new( file($it)->absolute )
510             if -e $it;
511              
512 0           return URI->new($it);
513             }
514              
515             #-------------------------------------------------------------------------------
516             1;
517              
518             __END__
519              
520             =pod
521              
522             =encoding UTF-8
523              
524             =for :stopwords Jeffrey Ryan Thalhammer
525              
526             =head1 NAME
527              
528             Pinto::Util - Static utility functions for Pinto
529              
530             =head1 VERSION
531              
532             version 0.13
533              
534             =head1 DESCRIPTION
535              
536             This is a private module for internal use only. There is nothing for
537             you to see here (yet). All API documentation is purely for my own
538             reference.
539              
540             =head1 FUNCTIONS
541              
542             =head2 throw($message)
543              
544             =head2 throw($exception_object)
545              
546             Throws a L<Pinto::Exception> with the given message. If given a reference
547             to a L<Pinto::Exception> object, then it just throws it again.
548              
549             =head2 debug( $message )
550              
551             =head2 debug( sub {...} )
552              
553             Writes the message on STDERR if the C<PINTO_DEBUG> environment variable is true.
554             If the argument is a subroutine, it will be invoked and its output will be
555             written instead. Always returns true.
556              
557             =head2 whine( $message )
558              
559             Just calls warn(), but always appends the newline so that line numbers are
560             suppressed.
561              
562             =head2 author_dir( @base, $author )
563              
564             Given the name of an C<$author>, returns the directory where the
565             distributions for that author belong (as a L<Path::Class::Dir>). The
566             optional C<@base> can be a series of L<Path::Class:Dir> or path parts
567             (as strings). If C<@base> is given, it will be prepended to the
568             directory that is returned.
569              
570             =head2 itis( $var, $class )
571              
572             Asserts whether var is a blessed reference and is an instance of the
573             C<$class>.
574              
575             =head2 parse_dist_path( $path )
576              
577             Parses a path like the ones you would see in a full URI to a
578             distribution in a CPAN repository, or the URI fragment you would see
579             in a CPAN index. Returns the author and file name of the
580             distribution. Subdirectories between the author name and the file
581             name are discarded.
582              
583             =head2 isa_perl( $path_or_uri )
584              
585             Return true if C<$path_or_uri> appears to point to a release of perl
586             itself. This is based on some file naming patterns that I've seen in
587             the wild. It may not be completely accurate.
588              
589             =head2 mtime( $file )
590              
591             Returns the last modification time (in epoch seconds) for the C<file>.
592             The argument is required and the file must exist or an exception will
593             be thrown.
594              
595             =head2 md5( $file )
596              
597             Returns the C<MD-5> digest (as a hex string) for the C<$file>. The
598             argument is required and the file must exist on an exception will be
599             thrown.
600              
601             =head2 sha256( $file )
602              
603             Returns the C<SHA-256> digest (as a hex string) for the C<$file>. The
604             argument is required and the file must exist on an exception will be
605             thrown.
606              
607             =head2 validate_property_name( $prop_name )
608              
609             Throws an exception if the property name is invalid. Currently,
610             property names must be alphanumeric plus any underscores or hyphens.
611              
612             =head2 validate_stack_name( $stack_name )
613              
614             Throws an exception if the stack name is invalid. Currently, stack
615             names must be alphanumeric plus underscores or hyphens.
616              
617             =head2 current_utc_time()
618              
619             Returns the current time (in epoch seconds) unless the current time has been
620             overridden by C<$Pinto::Globals::current_utc_time>.
621              
622             =head2 current_time_offset()
623              
624             Returns the offset between current UTC time and the local time in
625             seconds, unless overridden by C<$Pinto::Globals::current_time_offset>.
626             The C<current_time> function is used to determine the current UTC
627             time.
628              
629             =head2 current_username()
630              
631             Returns the username of the current user unless it has been overridden by
632             C<$Pinto::Globals::current_username>. The username can be defined through
633             a number of environment variables. Throws an exception if no username
634             can be determined.
635              
636             =head2 current_author_id()
637              
638             Returns the author id of the current user unless it has been overridden by
639             C<$Pinto::Globals::current_author_id>. The author id can be defined through
640             environment variables. Otherwise it defaults to the upper-case form of the
641             C<current_username>. And since PAUSE only allows letters and numbers in the
642             author id, then we remove all of those from the C<current_username> too.
643              
644             =head2 is_interactive()
645              
646             Returns true if the process is connected to an interactive terminal
647             (i.e. a keyboard & screen) unless it has been overridden by
648             C<$Pinto::Globals::is_interactive>.
649              
650             =head2 interpolate($string)
651              
652             Performs interpolation on a literal string. The string should not
653             include anything that looks like a variable. Only metacharacters
654             (like \n) will be interpolated correctly.
655              
656             =head2 trim_text($string)
657              
658             Returns the string with all leading and trailing whitespace removed.
659              
660             =head2 title_text($string)
661              
662             Returns all the characters in C<$string> before the first newline. If
663             there is no newline, returns the entire C<$string>.
664              
665             =head2 body_text($string)
666              
667             Returns all the characters in C<$string> after the first newline. If
668             there is no newline, returns an empty string.
669              
670             =head2 truncate_text($string, $length, $elipses)
671              
672             Truncates the C<$string> and appends C<$elipses> if the C<$string> is
673             longer than C<$length> characters. C<$elipses> defaults to '...' if
674             not specified.
675              
676             =head2 decamelize($string)
677              
678             Returns the string forced to lower case and words separated by underscores.
679             For example C<FooBar> becomes C<foo_bar>.
680              
681             =head2 indent_text($string, $n)
682              
683             Returns a copy of C<$string> with each line indented by C<$n> spaces.
684             In other words, it puts C<4n> spaces immediately after each newline
685             in C<$string>. The original C<$string> is not modified.
686              
687             =head2 mksymlink($from => $to)
688              
689             Creates a symlink between the two files. No checks are performed to see
690             if either path is valid or already exists. Throws an exception if the
691             operation fails or is not supported.
692              
693             =head2 is_system_prop($string)
694              
695             Returns true if C<$string> is the name of a system property.
696              
697             =head2 uuid()
698              
699             Returns a UUID as a string. Currently, the UUID is derived from
700             random numbers.
701              
702             =head2 user_palette()
703              
704             Returns a reference to an array containing the names of the colors pinto
705             can use. This can be influenced by setting the C<PINTO_PALETTE> environment
706             variable.
707              
708             =head2 is_blank($string)
709              
710             Returns true if the string is undefined, empty, or contains only whitespace.
711              
712             =head2 is_not_blank($string)
713              
714             Returns true if the string contains any non-whitespace characters.
715              
716             =head2 mask_uri_passwords($string)
717              
718             Masks the parts the string that look like a password embedded in an http or
719             https URI. For example, C<http://joe:secret@foo.com> would return
720             C<http://joe:*password*@foo.com>
721              
722             =head2 is_remote_repo {
723              
724             Returns true if the argument looks like a URI to a remote repository
725              
726             =head1 AUTHOR
727              
728             Jeffrey Ryan Thalhammer <jeff@stratopan.com>
729              
730             =head1 COPYRIGHT AND LICENSE
731              
732             This software is copyright (c) 2015 by Jeffrey Ryan Thalhammer.
733              
734             This is free software; you can redistribute it and/or modify it under
735             the same terms as the Perl 5 programming language system itself.
736              
737             =cut