File Coverage

blib/lib/XAS/Utils.pm
Criterion Covered Total %
statement 27 242 11.1
branch 0 56 0.0
condition 0 9 0.0
subroutine 9 49 18.3
pod 26 33 78.7
total 62 389 15.9


line stmt bran cond sub pod time code
1             package XAS::Utils;
2              
3             our $VERSION = '0.06';
4              
5 1     1   1580 use DateTime;
  1         84176  
  1         30  
6 1     1   7 use Try::Tiny;
  1         0  
  1         47  
7 1     1   4 use XAS::Exception;
  1         1  
  1         7  
8 1     1   524 use DateTime::Format::Pg;
  1         24572  
  1         8  
9 1     1   30 use Digest::MD5 'md5_hex';
  1         2  
  1         42  
10 1     1   11 use Params::Validate ':all';
  1         1  
  1         143  
11 1     1   4 use DateTime::Format::Strptime;
  1         2  
  1         3  
12 1     1   37 use POSIX qw(:sys_wait_h setsid);
  1         1  
  1         5  
13              
14             use XAS::Class
15 1         16 debug => 0,
16             version => $VERSION,
17             base => 'Badger::Utils',
18             constants => 'HASH ARRAY LOG_LEVELS',
19             filesystem => 'Dir File',
20             exports => {
21             all => 'db2dt dt2db trim ltrim rtrim daemonize hash_walk
22             load_module bool compress exitcode _do_fork glob2regex dir_walk
23             env_store env_restore env_create env_parse env_dump env_clear
24             left right mid instr is_truthy is_falsey run_cmd
25             validate_params validation_exception level2syslog
26             stat2text bash_escape create_argv de_camel_case',
27             any => 'db2dt dt2db trim ltrim rtrim daemonize hash_walk
28             load_module bool compress exitcode _do_fork glob2regex dir_walk
29             env_store env_restore env_create env_parse env_dump env_clear
30             left right mid instr is_truthy is_falsey run_cmd
31             validate_params validation_exception level2syslog
32             stat2text bash_escape create_argv de_camel_case',
33             tags => {
34             dates => 'db2dt dt2db',
35             env => 'env_store env_restore env_create env_parse env_dump env_clear',
36             modules => 'load_module',
37             strings => 'trim ltrim rtrim compress left right mid instr',
38             process => 'daemonize exitcode run_cmd _do_fork bash_escape create_argv',
39             boolean => 'is_truthy is_falsey bool',
40             validation => 'validate_params validation_exception',
41             }
42             }
43 1     1   149 ;
  1         1  
44              
45             #use Data::Dumper;
46              
47             # ----------------------------------------------------------------------
48             # Public Methods
49             # ----------------------------------------------------------------------
50              
51             sub validation_exception {
52 0     0 1   my $param = shift;
53 0           my $class = shift;
54              
55 0           my $format = 'invalid parameters passed, reason: %s';
56 0           my $method = Badger::Utils::dotid($class) . '.invparams';
57              
58 0           $param = trim(lcfirst($param));
59              
60 0           my $ex = XAS::Exception->new(
61             type => $method,
62             info => Badger::Utils::xprintf($format, $param),
63             );
64              
65 0           $ex->throw();
66              
67             }
68              
69             sub validate_params {
70 0     0 1   my $params = shift;
71 0           my $specs = shift;
72 0           my $class = shift;
73              
74 0 0         unless (defined($class)) {
75              
76 0           $class = (caller(1))[3];
77              
78             }
79              
80             my $results = validate_with(
81             params => $params,
82             called => $class,
83             spec => $specs,
84             normalize_keys => sub {
85 0     0     my $key = shift;
86 0           $key =~ s/^-//;
87 0           return lc $key;
88             },
89             on_fail => sub {
90 0     0     my $param = shift;
91 0           validation_exception($param, $class);
92             },
93 0           );
94              
95 0 0         return wantarray ? @$results : $results;
96              
97             }
98              
99             # recursively walk a HOH
100             sub hash_walk {
101 0     0 1   my $p = validate_params(\@_, {
102             -hash => { type => HASHREF },
103             -keys => { type => ARRAYREF },
104             -callback => { type => CODEREF },
105             });
106              
107 0           my $hash = $p->{'hash'};
108 0           my $key_list = $p->{'keys'};
109 0           my $callback = $p->{'callback'};
110              
111 0           while (my ($k, $v) = each %$hash) {
112              
113             # Keep track of the hierarchy of keys, in case
114             # our callback needs it.
115              
116 0           push(@$key_list, $k);
117              
118 0 0         if (ref($v) eq 'HASH') {
119              
120             # Recurse.
121              
122 0           hash_walk(-hash => $v, -keys => $key_list, -callback => $callback);
123              
124             } else {
125             # Otherwise, invoke our callback, passing it
126             # the current key and value, along with the
127             # full parentage of that key.
128              
129 0           $callback->($k, $v, $key_list);
130              
131             }
132              
133 0           pop(@$key_list);
134              
135             }
136              
137             }
138              
139             # recursively walk a directory structure
140             sub dir_walk {
141             my $p = validate_params(\@_, {
142             -directory => { isa => 'Badger::Filesystem::Directory' },
143             -callback => { type => CODEREF },
144             -filter => { optional => 1, default => qr/.*/, callbacks => {
145             'must be a compiled regex' => sub {
146 0 0   0     return (ref shift() eq 'Regexp') ? 1 : 0;
147             }
148             }},
149 0     0 1   });
150              
151 0           my $folder = $p->{'directory'};
152 0           my $filter = $p->{'filter'};
153 0           my $callback = $p->{'callback'};
154              
155 0           my @files = grep ( $_->path =~ /$filter/, $folder->files() );
156 0           my @folders = $folder->dirs;
157              
158 0           foreach my $file (@files) {
159              
160 0           $callback->($file);
161              
162             }
163              
164 0           foreach my $folder (@folders) {
165              
166 0           dir_walk(-directory => $folder, -filter => $filter, -callback => $callback);
167              
168             }
169              
170             }
171              
172             # Perl trim function to remove whitespace from the start and end of the string
173             sub trim {
174 0     0 1   my ($string) = validate_params(\@_, [1]);
175              
176 0           $string =~ s/^\s+//;
177 0           $string =~ s/\s+$//;
178              
179 0           return $string;
180              
181             }
182              
183             # Left trim function to remove leading whitespace
184             sub ltrim {
185 0     0 1   my ($string) = validate_params(\@_, [1]);
186              
187 0           $string =~ s/^\s+//;
188              
189 0           return $string;
190              
191             }
192              
193             # Right trim function to remove trailing whitespace
194             sub rtrim {
195 0     0 1   my ($string) = validate_params(\@_, [1]);
196              
197 0           $string =~ s/\s+$//;
198              
199 0           return $string;
200              
201             }
202              
203             # replace multiple whitspace with a single space
204             sub compress {
205 0     0 1   my ($string) = validate_params(\@_, [1]);
206              
207 0           $string =~ s/\s+/ /gms;
208              
209 0           return $string;
210              
211             }
212              
213             # emulate Basics string function left()
214             sub left {
215 0     0 1   my ($string, $offset) = validate_params(\@_, [1,1]);
216              
217 0           return substr($string, 0, $offset);
218              
219             }
220              
221             # emulate Basics string function right()
222             sub right {
223 0     0 1   my ($string, $offset) = validate_params(\@_, [1,1]);
224              
225 0           return substr($string, -($offset));
226              
227             }
228              
229             # emulate Basics string function mid()
230             sub mid {
231 0     0 1   my ($string, $start, $length) = validate_params(\@_, [1,1,1]);
232              
233 0           return substr($string, $start - 1, $length);
234              
235             }
236              
237             # emulate Basics string function instr()
238             sub instr {
239 0     0 1   my ($start, $string, $compare) = validate_params(\@_, [1,1,1]);
240              
241 0 0         if ($start =~ /^[0-9\-]+/) {
242              
243 0           $start++;
244              
245             } else {
246              
247 0           $compare = $string;
248 0           $string = $start;
249 0           $start = 0;
250              
251             }
252              
253 0           return index($string, $compare, $start) + 1;
254              
255             }
256              
257             sub de_camel_case {
258 0     0 1   my ($s) = validate_params(\@_, [1]);
259              
260 0           my $o;
261 0           my @a = split('', $s);
262 0           my $z = scalar(@a);
263              
264 0           for (my $x = 0; $x < $z; $x++) {
265              
266 0 0         if ($a[$x] =~ /[A-Z]/) {
267              
268 0 0         if ($x == 0) {
269              
270 0           $o .= lc($a[$x]);
271              
272             } else {
273              
274 0           $o .= '_' . lc($a[$x]);
275              
276             }
277              
278             } else {
279              
280 0           $o .= $a[$x];
281              
282             }
283              
284             }
285              
286 0           return $o;
287              
288             }
289              
290             # Checks to see if the parameter is the string 't', 'true', 'yes', '0E0'
291             # or the number 1.
292             #
293             sub is_truthy {
294 0     0 0   my ($parm) = validate_params(\@_, [1]);
295              
296 0           my @truth = qw(yes true t 1 0e0);
297              
298 0           return scalar(grep {lc($parm) eq $_} @truth);
  0            
299              
300             }
301              
302             # Checks to see if the parameter is the string 'f', 'false', 'no' or
303             # the number 0.
304             #
305             sub is_falsey {
306 0     0 0   my ($parm) = validate_params(\@_, [1]);
307              
308 0           my @truth = qw(no false f 0);
309              
310 0           return scalar(grep {lc($parm) eq $_} @truth);
  0            
311              
312             }
313              
314             sub bool {
315 0     0 0   my ($item) = validate_params(\@_, [1]);
316              
317 0           my @truth = qw(yes true 1 0e0 no false f 0);
318 0           return grep {lc($item) eq $_} @truth;
  0            
319              
320             }
321              
322             sub exitcode {
323              
324 0     0 1   my $ex = $?;
325 0           my $rc = $ex >> 8; # return code of process
326 0           my $sig = $ex & 127; # signal it was killed with
327 0           my $cored = $ex & 128; # wither the process cored
328              
329 0           return $rc, $sig, $cored;
330              
331             }
332              
333             sub _do_fork {
334              
335 0     0     my $child = fork();
336              
337 0 0         unless (defined($child)) {
338              
339 0           my $ex = XAS::Exception->new(
340             type => 'xas.utils.daemonize',
341             info => "unable to fork, reason: $!"
342             );
343              
344 0           $ex->throw;
345              
346             }
347              
348 0 0         exit(0) if ($child);
349              
350             }
351              
352             sub daemonize {
353              
354 0     0 1   _do_fork(); # initial fork
355 0           setsid(); # become session leader
356 0           _do_fork(); # second fork to prevent aquiring a controlling terminal
357              
358             # change directory to a netural place and set the umask
359              
360 0           chdir('/');
361 0           umask(0);
362              
363             # redirect our standard file handles
364              
365 0           open(STDIN, '<', '/dev/null');
366 0           open(STDOUT, '>', '/dev/null');
367 0           open(STDERR, '>', '/dev/null');
368              
369             }
370              
371             sub db2dt {
372 0     0 1   my ($p) = validate_params(\@_, [
373             { regex => qr/\d{4}-\d{2}-\d{2}.\d{2}:\d{2}:\d{2}/ }
374             ]);
375              
376             my $parser = DateTime::Format::Strptime->new(
377             pattern => '%Y-%m-%d %H:%M:%S',
378             time_zone => 'local',
379             on_error => sub {
380 0     0     my ($obj, $err) = @_;
381 0           my $ex = XAS::Exception->new(
382             type => 'xas.utils.db2dt',
383             info => $err
384             );
385 0           $ex->throw;
386             }
387 0           );
388              
389 0           return $parser->parse_datetime($p);
390              
391             }
392              
393             sub dt2db {
394 0     0 1   my ($dt) = validate_params(\@_, [
395             { isa => 'DateTime' }
396             ]);
397              
398 0           return $dt->strftime('%Y-%m-%d %H:%M:%S');
399              
400             }
401              
402             sub run_cmd {
403 0     0 1   my ($command) = validate_params(\@_, [1]);
404              
405 0           my @output = `$command 2>&1`;
406 0           my ($rc, $sig, $cored) = exitcode();
407              
408 0           return \@output, $rc, $sig;
409              
410             }
411              
412             sub load_module {
413 0     0 1   my ($module) = validate_params(\@_, [1]);
414              
415 0           my @parts;
416             my $filename;
417              
418 0           @parts = split("::", $module);
419 0           $filename = File(@parts);
420              
421             try {
422              
423 0     0     require $filename . '.pm';
424 0           $module->import();
425              
426             } catch {
427              
428 0     0     my $x = $_;
429 0           my $ex = XAS::Exception->new(
430             type => 'xas.utils.load_module',
431             info => $x
432             );
433              
434 0           $ex->throw;
435              
436 0           };
437              
438             }
439              
440             sub glob2regex {
441 0     0 0   my ($globstr) = validate_params(\@_, [1]);
442              
443 0           my %patmap = (
444             '*' => '.*',
445             '?' => '.',
446             '[' => '[',
447             ']' => ']',
448             );
449              
450 0 0         $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
  0            
451              
452 0           return '^' . $globstr . '$';
453              
454             }
455              
456             sub stat2text {
457 0     0 1   my ($stat) = validate_params(\@_, [1]);
458            
459 0           my $status = 'unknown';
460              
461 0 0         $status = 'suspended ready' if ($stat == 6);
462 0 0         $status = 'suspended blocked' if ($stat == 5);
463 0 0         $status = 'blocked' if ($stat == 4);
464 0 0         $status = 'running' if ($stat == 3);
465 0 0         $status = 'ready' if ($stat == 2);
466 0 0         $status = 'other' if ($stat == 1);
467              
468 0           return $status;
469              
470             }
471              
472             sub level2syslog {
473 0     0 1   my ($level) = validate_params(\@_, [
474             { regex => LOG_LEVELS },
475             ]);
476              
477 0           my $translate = {
478             info => 'info',
479             error => 'err',
480             warn => 'warning',
481             fatal => 'alert',
482             trace => 'notice',
483             debug => 'debug'
484             };
485              
486 0           return $translate->{lc($level)};
487              
488             }
489              
490             # **********************************************************************
491             # The Bourne shell treats some characters in a command's argument list as
492             # having a special meaning. This could result in the shell executing
493             # unwanted commands. This code escapes the special characters by
494             # prefixing them with the \ character.
495             #
496             # taken from: https://www.slac.stanford.edu/slac/www/resource/how-to-use/cgi-rexx/cgi-esc.html
497             #
498             # **********************************************************************
499              
500             sub bash_escape {
501 0     0 0   my $arg = shift;
502              
503 0           $arg =~ s/([;<>\*\|&\$!#\(\)\[\]\{\}:'"])/\\$1/g;
504              
505 0           return $arg;
506              
507             }
508              
509             #
510             # Extracted form Parse::CommandLine.
511             #
512              
513             sub create_argv {
514 0     0 0   my ($str) = validate_params(\@_, [1]);
515              
516 0           $str =~ s/\A\s+//ms;
517 0           $str =~ s/\s+\z//ms;
518              
519 0           my @argv;
520             my $buf;
521 0           my $escaped;
522 0           my $double_quoted;
523 0           my $single_quoted;
524              
525 0           for my $char (split //, $str) {
526              
527 0 0         if ($escaped) {
528              
529 0           $buf .= $char;
530 0           $escaped = undef;
531 0           next;
532              
533             }
534              
535 0 0         if ($char eq '\\') {
536              
537 0 0         if ($single_quoted) {
538              
539 0           $buf .= $char;
540              
541             } else {
542              
543 0           $escaped = 1;
544              
545             }
546 0           next;
547              
548             }
549              
550 0 0         if ($char =~ /\s/) {
551              
552 0 0 0       if ($single_quoted || $double_quoted) {
553              
554 0           $buf .= $char;
555              
556             } else {
557              
558 0 0         push @argv, $buf if defined $buf;
559 0           undef $buf;
560              
561             }
562 0           next;
563              
564             }
565              
566 0 0         if ($char eq '"') {
567              
568 0 0         if ($single_quoted) {
569              
570 0           $buf .= $char;
571 0           next;
572              
573             }
574              
575 0           $double_quoted = !$double_quoted;
576 0           next;
577              
578             }
579              
580 0 0         if ($char eq "'") {
581              
582 0 0         if ($double_quoted) {
583              
584 0           $buf .= $char;
585 0           next;
586              
587             }
588              
589 0           $single_quoted = !$single_quoted;
590 0           next;
591              
592             }
593              
594 0           $buf .= $char;
595              
596             }
597              
598 0 0         push @argv, $buf if defined $buf;
599              
600 0 0 0       if ($escaped || $single_quoted || $double_quoted) {
      0        
601              
602 0           my $ex = XAS::Exception->new(
603             type => 'xas.utils.create_argv',
604             info => 'invalid command line string',
605             );
606              
607 0           $ex->throw;
608              
609             }
610              
611 0           return @argv;
612              
613             }
614              
615             sub env_store {
616              
617 0     0 1   my $env;
618              
619 0           while (my ($key, $value) = each(%ENV)) {
620              
621 0           $env->{$key} = $value;
622              
623             }
624              
625 0           return $env;
626              
627             }
628              
629             sub env_clear {
630            
631 0     0 0   while (my ($key, $value) = each(%ENV)) {
632            
633 0           delete $ENV{$key};
634            
635             }
636            
637             }
638              
639             sub env_restore {
640 0     0 1   my ($env) = validate_params(\@_, [
641             { type => HASHREF },
642             ]);
643              
644 0           env_clear();
645 0           env_create($env);
646              
647             }
648              
649             sub env_create {
650 0     0 1   my ($env) = validate_params(\@_, [
651             { type => HASHREF },
652             ]);
653              
654 0           while (my ($key, $value) = each(%$env)) {
655              
656 0           $ENV{$key} = $value;
657              
658             }
659              
660             }
661              
662             sub env_parse {
663 0     0 1   my ($e) = validate_params(\@_, [
664             { type => SCALAR },
665             ]);
666              
667 0           my $env;
668 0           my @envs = split(';;', $e);
669              
670 0           foreach my $y (@envs) {
671              
672 0           my ($key, $value) = split('=', $y);
673 0           $env->{$key} = $value;
674              
675             }
676              
677 0           return $env;
678              
679             }
680              
681             sub env_dump {
682              
683 0     0 1   my $env;
684              
685 0           while (my ($key, $value) = each(%ENV)) {
686              
687 0           $env .= "$key=$value;;";
688              
689             }
690              
691             # remove the ;; at the end
692              
693 0           chop $env;
694 0           chop $env;
695              
696 0           return $env;
697              
698             }
699              
700             1;
701              
702             __END__