File Coverage

blib/lib/Astro/App/Satpass2/Utils.pm
Criterion Covered Total %
statement 190 255 74.5
branch 78 128 60.9
condition 14 35 40.0
subroutine 35 42 83.3
pod 9 9 100.0
total 326 469 69.5


line stmt bran cond sub pod time code
1             package Astro::App::Satpass2::Utils;
2              
3 22     22   436 use 5.008;
  22         145  
4              
5 22     22   129 use strict;
  22         52  
  22         615  
6 22     22   98 use warnings;
  22         76  
  22         1171  
7              
8 22     22   6016 use parent qw{ Exporter };
  22         4056  
  22         155  
9              
10 22     22   1682 use Cwd ();
  22         79  
  22         502  
11 22     22   12337 use File::HomeDir;
  22         147561  
  22         1634  
12 22     22   234 use File::Spec;
  22         53  
  22         734  
13 22     22   8950 use Getopt::Long 2.33;
  22         191821  
  22         608  
14 22     22   4228 use Scalar::Util 1.26 qw{ blessed looks_like_number };
  22         477  
  22         1653  
15 22     22   11841 use Text::ParseWords ();
  22         38843  
  22         5226  
16              
17             our $VERSION = '0.057_01';
18              
19             our @CARP_NOT = qw{
20             Astro::App::Satpass2
21             Astro::App::Satpass2::Copier
22             Astro::App::Satpass2::Format
23             Astro::App::Satpass2::Format::Dump
24             Astro::App::Satpass2::Format::Template
25             Astro::App::Satpass2::Format::Template::Provider
26             Astro::App::Satpass2::FormatTime
27             Astro::App::Satpass2::FormatTime::Cldr
28             Astro::App::Satpass2::FormatTime::DateTime
29             Astro::App::Satpass2::FormatTime::DateTime::Cldr
30             Astro::App::Satpass2::FormatTime::DateTime::Strftime
31             Astro::App::Satpass2::FormatTime::POSIX::Strftime
32             Astro::App::Satpass2::FormatTime::Strftime
33             Astro::App::Satpass2::FormatValue
34             Astro::App::Satpass2::FormatValue::Formatter
35             Astro::App::Satpass2::Geocode
36             Astro::App::Satpass2::Geocode::OSM
37             Astro::App::Satpass2::Locale
38             Astro::App::Satpass2::Locale::C
39             Astro::App::Satpass2::Macro
40             Astro::App::Satpass2::Macro::Code
41             Astro::App::Satpass2::Macro::Command
42             Astro::App::Satpass2::ParseTime
43             Astro::App::Satpass2::ParseTime::Code
44             Astro::App::Satpass2::ParseTime::Date::Manip
45             Astro::App::Satpass2::ParseTime::Date::Manip::v5
46             Astro::App::Satpass2::ParseTime::Date::Manip::v6
47             Astro::App::Satpass2::ParseTime::ISO8601
48             Astro::App::Satpass2::Utils
49             Astro::App::Satpass2::Warner
50             Astro::App::Satpass2::Wrap::Array
51             };
52              
53             our @EXPORT_OK = qw{
54             __arguments
55             back_end
56             __back_end_class_name_of_record
57             expand_tilde find_package_pod
58             has_method instance load_package merge_hashes my_dist_config quoter
59             __date_manip_backend
60             __legal_options
61             __parse_class_and_args
62             ARRAY_REF CODE_REF HASH_REF REGEXP_REF SCALAR_REF
63             HAVE_DATETIME
64             OS_IS_WINDOWS
65             @CARP_NOT
66             };
67              
68             our %EXPORT_TAGS = (
69             os => [ grep { m/ \A OS_ /smx } @EXPORT_OK ],
70             ref => [ grep { m/ _REF \z /smx } @EXPORT_OK ],
71             );
72              
73 22     22   175 use constant ARRAY_REF => ref [];
  22         40  
  22         1879  
74 22     22   137 use constant CODE_REF => ref sub {};
  22         68  
  22         1464  
75 22     22   137 use constant HASH_REF => ref {};
  22         44  
  22         1685  
76 22     22   129 use constant REGEXP_REF => ref qr{};
  22         58  
  22         1554  
77 22     22   118 use constant SCALAR_REF => ref \1;
  22         47  
  22         2241  
78              
79             {
80             local $@ = undef;
81              
82 22   50     43 use constant HAVE_DATETIME => eval {
83             require DateTime;
84             require DateTime::TimeZone;
85             1;
86 22     22   131 } || 0;
  22         60  
87             }
88              
89             use constant OS_IS_WINDOWS => {
90             dos => 1,
91             MSWin32 => 1,
92 22   50 22   141 }->{$^O} || 0;
  22         45  
  22         76241  
93              
94             # Documented in POD
95              
96             {
97              
98             my @default_config = qw{default pass_through};
99              
100             sub __arguments {
101 350     350   1175 my ( $self, @args ) = @_;
102              
103 350 100       1158 has_method( $self, '__parse_time_reset' )
104             and $self->__parse_time_reset();
105              
106             @args = map {
107 350 100       904 has_method( $_, 'dereference' ) ? $_->dereference() : $_
  1181         1894  
108             } @args;
109              
110 350         642 my $code = \&{ ( caller 1 )[3] };
  350         4106  
111              
112 350 100       1750 if ( HASH_REF eq ref $args[0] ) {
113 6         15 my $opt = shift @args;
114 6         11 my @orig_keys = sort keys %{ $opt };
  6         25  
115 6         24 my $lgl = $self->__legal_options( $code, $opt );
116 6         21 my %opt_name = (
117             level1 => 1,
118             );
119 6         10 my $name;
120 6         22 foreach my $inx ( 0 .. $#$lgl ) {
121 42 100       80 if ( CODE_REF eq ref $lgl->[$inx] ) {
122 6 50       17 defined $name
123             or die "Bug - \$name undefined. Inx $inx; lgl @$lgl";
124 6 100       16 if ( exists $opt->{$name} ) {
125 2         10 $lgl->[$inx]->( $name, $opt->{$name} );
126             }
127             } else {
128 36         118 ( $name = $lgl->[ $inx ] ) =~ s/ \W .* //smx;
129 36         83 $opt_name{$name} = 1;
130             }
131             }
132 6         14 foreach my $key ( @orig_keys ) {
133 9 50       27 $opt_name{$key}
134             or __error_out( $self, wail => "Illegal option '$key'" );
135             }
136 6         23 _apply_default( $self, $opt, \@args );
137 6         54 return( $self, $opt, @args );
138             }
139              
140             =begin comment
141              
142             my @data = caller(1);
143             my $code = \&{$data[3]};
144              
145             my ( $err, %opt );
146             my $lgl = $self->__get_attr($code, 'Verb') || [];
147             if ( @{ $lgl } && ':compute' eq $lgl->[0] ) {
148             my $method = $lgl->[1];
149             unless ( defined $method ) {
150             ( $method = $data[3] ) =~ s/ .* :: //smx;
151             $method = "__${method}_options";
152             }
153             $lgl = $self->$method( \%opt, $lgl );
154             }
155              
156             =end comment
157              
158             =cut
159              
160              
161 344         710 my ( $err, %opt );
162 344         1386 my $lgl = $self->__legal_options( $code, \%opt );
163              
164 344     0   3065 local $SIG{__WARN__} = sub {$err = $_[0]};
  0         0  
165 344   50     1092 my $config =
166             $self->__get_attr($code, 'Configure') || \@default_config;
167 344         2990 my $go = Getopt::Long::Parser->new(config => $config);
168 344 50       42140 if ( ! $go->getoptionsfromarray(
169             \@args, \%opt, 'default=s', @$lgl) ) {
170 0         0 __error_out( $self, wail => $err );
171             }
172              
173 344         153831 _apply_default( $self, \%opt, \@args );
174              
175 344         4967 return ( $self, \%opt, @args );
176             }
177             }
178              
179             sub __legal_options {
180 350     350   907 my ( $self, $code, $opt ) = @_;
181 350   50     973 $code ||= \&{ ( caller 1 )[3] };
  0         0  
182 350 50       1020 CODE_REF eq ref $code
183             or __error_out( $self, weep => "$code not a CODE ref" );
184 350   50     738 $opt ||= {};
185 350         1767 my $lgl = $self->__get_attr( $code, Verb => [] );
186 350 100 100     675 if ( @{ $lgl } && ':compute' eq $lgl->[0] ) {
  350         1668  
187 24 50       119 my $method = $lgl->[1]
188             or __error_out( $self, weep => ':compute did not specify method' );
189 24         116 $lgl = $self->$method( $opt, $lgl );
190             }
191 350         799 return $lgl;
192             }
193              
194             sub _apply_default {
195 350     350   936 my ( $self, $opt, $args ) = @_;
196              
197             my $dflt = delete $opt->{default}
198 350 100       1427 or return;
199              
200 2 50       13 if ( ARRAY_REF eq ref $dflt ) {
    50          
    50          
201             # Do nothing -- we already have what we want
202             } elsif ( ref $dflt ) {
203 0         0 __error_out( $self,
204             wail => "Invalid default specification $dflt" );
205             } elsif ( my $code = $self->can( '__tokenize' ) ) {
206 2         6 ( $dflt ) = $code->( $self, $dflt );
207             } else {
208 0         0 $dflt = [ Text::ParseWords::shellwords( $dflt ) ];
209             }
210              
211 2         8 foreach my $inx ( 0 .. $#$dflt ) {
212 14 100 66     30 defined $args->[$inx]
213             and '' ne $args->[$inx]
214             or $args->[$inx] = $dflt->[$inx];
215             }
216              
217 2         4 return;
218             }
219              
220             sub back_end {
221 0     0 1 0 my ( $self, @arg ) = @_;
222 0 0       0 if ( @arg ) {
223 0         0 my ( $pkg, @cls_arg ) = ( $self->__parse_class_and_args(
224             $self->__back_end_default( $arg[0] ) ), @arg[ 1 .. $#arg ] );
225 0         0 my $cls = $self->load_package( { fatal => 1 }, $pkg,
226             'DateTime::Calendar' );
227 0         0 $self->__back_end_validate( $cls, @cls_arg );
228             $self->{_back_end} = {
229 0         0 arg => \@cls_arg,
230             class => $cls,
231             pkg => $pkg,
232             };
233 0         0 $self->{back_end} = shift @arg;
234 0         0 while ( @arg ) {
235 0         0 my ( $name, $value ) = splice @arg, 0, 2;
236 0         0 $self->{back_end} .= ",$name=$value";
237             }
238 0         0 return $self;
239             } else {
240             wantarray
241             and return ( $self->{_back_end}{pkg}, @{
242 0 0       0 $self->{_back_end}{arg} } );
  0         0  
243 0         0 return $self->{back_end};
244             }
245             }
246              
247             sub __back_end_class_name_of_record {
248 0     0   0 my ( $self, $name ) = @_;
249             defined( my $back_end = $self->{_back_end}{class} )
250 0 0       0 or return $name;
251 0 0       0 $back_end eq $self->__back_end_default()
252             and return $name;
253 0         0 $back_end =~ s/ \A DateTime::Calendar:: //smx;
254 0 0       0 @{ $self->{_back_end}{arg} }
  0         0  
255             or return "$name,back_end=$back_end";
256 0         0 my %dt_arg = @{ $self->{_back_end}{arg} };
  0         0  
257 0         0 foreach my $key ( sort keys %dt_arg ) {
258 0         0 $back_end .= ",$key=$dt_arg{$key}";
259             }
260 0         0 return "$name,back_end='$back_end'";
261             }
262              
263             # $backend = __date_manip_backend()
264             #
265             # This subroutine loads Date::Manip and returns the backend available,
266             # either 5 or 6. If Date::Manip can not be loaded it returns undef.
267             #
268             # The idea here is to return 6 if the O-O interface is available, and 5
269             # if it is not but Date::Manip is.
270              
271             sub __date_manip_backend {
272 10 50   10   34 load_package( 'Date::Manip' )
273             or return;
274 0 0       0 Date::Manip->isa( 'Date::Manip::DM6' )
275             and return 6;
276 0         0 return 5;
277             }
278              
279             {
280             my %method_to_sub = (
281             whinge => 'carp',
282             wail => 'croak',
283             weep => 'confess',
284             );
285              
286             # __error_out( $invocant, $method, @arg )
287             #
288             # $method must be 'carp', 'croak', or 'confess'.
289             #
290             # If the $invocant is a blessed reference having method $method,
291             # that method is called with @arg as arguments.
292             #
293             # Otherwise Carp is loaded, $method is mapped to the corresponding
294             # Carp subroutine, and that subroutine is called with @arg as
295             # arguments.
296             #
297             # If we have not thrown an exception as a result of all this, we
298             # just return.
299             sub __error_out {
300 11     11   47 my ( $obj, $method, @arg ) = @_;
301 11 50       63 $method_to_sub{$method}
302             or $method = 'weep';
303 11 100 66     91 if ( blessed( $obj ) && $obj->can( $method )
304             ) {
305 5         29 $obj->$method( @arg );
306             } else {
307 6         91 require Carp;
308 6 50       134 if ( my $code = Carp->can( $method_to_sub{ $method } ) ) {
309 6         11780 $code->( @arg );
310             } else {
311 0         0 Carp::confess( @arg );
312             }
313             }
314 0         0 return;
315             }
316             }
317              
318             sub expand_tilde {
319 39     39 1 158 my @args = @_;
320 39 100       170 my ( $self, $fn ) = @args > 1 ? @args : ( undef, @args );
321 39 50       321 defined $fn
322 21         113 and $fn =~ s{ \A ~ ( [^/]* ) }{ _user_home_dir( $self, $1 ) }smxe;
323 29         1684 return $fn;
324             }
325              
326             {
327             my %special = (
328             '+' => sub { return Cwd::cwd() },
329             '~' => sub {
330             return my_dist_config();
331             },
332             '' => sub { return File::HomeDir->my_home() },
333             );
334             # $dir = $self->_user_home_dir( $user );
335             #
336             # Find the home directory for the given user, croaking if this can
337             # not be done. If $user is '' or undef, returns the home directory
338             # for the current user.
339              
340             sub _user_home_dir {
341 21     21   138 my ( $self, $user ) = @_;
342 21 50       87 defined $user
343             or $user = '';
344              
345 21 100       102 if ( my $code = $special{$user} ) {
346 17 100       74 defined( my $special_dir = $code->( $user ) )
347             or _wail( $self, "Unable to find ~$user" );
348 9         115 return $special_dir;
349             } else {
350 4 100       66 defined( my $home_dir = File::HomeDir->users_home( $user ) )
351             or _wail( $self, "Unable to find home for $user" );
352 2         36 return $home_dir;
353             }
354             }
355             }
356              
357             sub find_package_pod {
358 0     0 1 0 my ( $pkg ) = @_;
359 0         0 ( my $fn = $pkg ) =~ s{ :: }{/}smxg;
360 0         0 foreach my $dir ( @INC ) {
361 0 0 0     0 defined $dir
      0        
      0        
362             and not ref $dir
363             and -d $dir
364             and -x _
365             or next;
366 0         0 foreach my $sfx ( qw{ pod pm } ) {
367 0         0 my $path = "$dir/$fn.$sfx";
368 0 0       0 -r $path
369             or next;
370 0         0 return Cwd::abs_path( $path );
371             }
372             }
373 0         0 return;
374             }
375              
376             sub _wail {
377 10     10   1001 my ( $invocant, @msg ) = @_;
378 10         66 __error_out( $invocant, wail => @msg );
379 0         0 return; # We should never get here, but Perl::Critic does not
380             # know this.
381             }
382              
383             sub has_method {
384 2709     2709 1 5434 my ( $object, $method ) = @_;
385              
386 2709 100       8608 ref $object or return;
387 995 100       2618 blessed( $object ) or return;
388 970         5791 return $object->can( $method );
389             }
390              
391             sub instance {
392 1502     1502 1 3488 my ( $object, $class ) = @_;
393 1502 100       3653 ref $object or return;
394 1475 100       5658 blessed( $object ) or return;
395 981         5324 return $object->isa( $class );
396             }
397              
398             sub _get_my_lib {
399 226     226   700 my $my_lib = my_dist_config();
400 226 50       30567 if ( defined $my_lib ) {
401 0         0 $my_lib = File::Spec->catdir( $my_lib, 'lib' );
402 0 0       0 -d $my_lib
403             or $my_lib = undef;
404             }
405 226         572 return $my_lib;
406             }
407              
408             {
409             my %loaded;
410              
411             # CAVEAT:
412             #
413             # Unfortunately as things currently stand, the version needs to be
414             # maintained three places:
415             # - lib/Astro/App/Satpass2/Utils.pm
416             # - inc/My/Module/Recommend.pm
417             # - inc/My/Module/Test/App.pm
418             # These all need to stay the same. Sigh.
419             # Any such should be in xt/author/consistent_module_versions.t
420              
421             my %version = (
422             'DateTime::Calendar::Christian' => 0.06,
423             );
424              
425             # Expose the module version so we can test for consistent definition.
426             # IM(NS)HO the following annotation silences a false positive.
427             sub __module_version { ## no critic (RequireArgUnpacking)
428 0     0   0 my $module = $_[-1];
429 0         0 require Carp;
430 0 0       0 exists $version{$module}
431             or Carp::confess( "Bug - Module $module has no defined version" );
432 0         0 return $version{$module};
433             }
434              
435             # my %valid_complaint = map { $_ => 1 } qw{ whinge wail weep };
436              
437             sub load_package {
438             # my ( $module, @prefix ) = @_;
439 247     247 1 974 my @prefix = @_;
440 247         416 my $self;
441 247 100       850 blessed( $prefix[0] )
442             and $self = shift @prefix;
443 247 100       1067 my $opt = HASH_REF eq ref $prefix[0] ? shift @prefix : {};
444 247         535 my $module = shift @prefix;
445              
446 247         1934 local @INC = @INC;
447              
448 247 100       970 my $use_lib = exists $opt->{lib} ? $opt->{lib} : _get_my_lib();
449 247 100       778 if ( defined $use_lib ) {
450 1         6 require lib;
451 1         6 lib->import( $use_lib );
452             }
453              
454 247         789 foreach ( $module, @prefix ) {
455 311 50       807 '' eq $_
456             and next;
457 311 50       4469 m/ \A [[:alpha:]]\w* (?: :: [[:alpha:]]\w* )* \z /smx
458             and next;
459              
460 0   0     0 __error_out( $self, $opt->{complaint} || 'weep',
461             "Invalid package name '$_'",
462             );
463             }
464              
465 247         828 my $key = join ' ', $module, @prefix;
466             exists $loaded{$key}
467 247 100       2246 and return $loaded{$key};
468              
469 134         290 local $@ = undef;
470              
471 134         359 push @prefix, '';
472 134         318 foreach my $pfx ( @prefix ) {
473 180         449 my $package = join '::', grep { $_ ne '' } $pfx, $module;
  360         1105  
474 180 50       431 '' eq $package
475             and next;
476 180         952 ( my $fn = $package ) =~ s{ :: }{/}smxg;
477 180 100       459 eval {
478 180         36108 require "$fn.pm"; ## no critic (RequireBarewordIncludes)
479 81         170362 1;
480             } or next;
481              
482             not $version{$package}
483 81 50       320 or $package->VERSION( $version{$package} );
484              
485 81         864 return ( $loaded{$key} = $package );
486             }
487              
488 53 100       246 if ( $opt->{fatal} ) {
489 1         8 __error_out( $self, $opt->{fatal}, "Can not load $module: $@" );
490             }
491              
492 52         184 $loaded{$key} = undef;
493              
494 52         429 return;
495             }
496             }
497              
498             # The Perl::Critic annotation on the following line should not (strictly
499             # speaking) be necessary - but Subroutines::RequireArgUnpacking does not
500             # understand the unpacking to be subject to the configuration
501             # allow_arg_unpacking = grep
502             sub merge_hashes { ## no critic (RequireArgUnpacking)
503 0     0 1 0 my @args = grep { HASH_REF eq ref $_ } @_;
  0         0  
504 0 0       0 @args == 1
505             and return $args[0];
506 0         0 my %rslt;
507 0         0 foreach my $hash ( @args ) {
508 0         0 @rslt{ keys %{ $hash } } = values %{ $hash };
  0         0  
  0         0  
509             }
510 0         0 return \%rslt;
511             }
512              
513 22     22   195 use constant MY_PACKAGE_NAME => 'Astro-App-Satpass2';
  22         45  
  22         24295  
514              
515             sub my_dist_config {
516 239     239 1 608 my ( $opt ) = @_;
517              
518             defined $ENV{ASTRO_APP_SATPASS2_CONFIG_DIR}
519 239 100       1310 and return Cwd::abs_path( $ENV{ASTRO_APP_SATPASS2_CONFIG_DIR} );
520              
521 236   50     3830 my $code = __PACKAGE__->can( "_my_dist_config_$^O" ) || \&_my_dist_config_;
522 236         787 return $code->( $opt );
523             }
524              
525             sub _my_dist_config_ {
526 236     236   569 my ( $opt ) = @_;
527             return File::HomeDir->my_dist_config(
528             MY_PACKAGE_NAME,
529 236         2144 { create => $opt->{'create-directory'} },
530             );
531             }
532              
533             # Called dynamically by my_dist_config() if $^O is 'darwin'.
534             sub _my_dist_config_darwin { ## no critic (ProhibitUnusedPrivateSubroutines)
535             # my ( $opt ) = @_;
536 0 0   0   0 my $rslt = File::HomeDir->my_dist_data( MY_PACKAGE_NAME )
537             or goto &_my_dist_config_;
538 0         0 return $rslt;
539             }
540              
541             sub __parse_class_and_args {
542 43     43   72017 my ( $self, $arg, @rest ) = @_;
543 43         406 my ( $cls, @val ) =
544             Text::ParseWords::parse_line( qr{ , }smx, 0, $arg );
545 43 50 33     5479 unless ( defined $cls &&
546             $cls =~ m/ \A [_[:alpha:]] \w* (?: :: \w+ )* \z /smx ) {
547 0 0       0 $cls = defined $cls ? "'$cls'" : 'undef';
548 0 0       0 my $warner = $self->can( 'wail' ) ? $self : $self->warner();
549 0         0 $warner->wail( "Invalid class name $cls" );
550             }
551 43         131 foreach ( @val ) {
552 4 50       41 m/ = /smx
553             or $_ .= '=';
554             };
555 43         206 return ( $cls, ( map { split qr{ = }smx, $_, 2 } @val ), @rest );
  4         80  
556             }
557              
558             sub quoter {
559 50     50 1 346 my @args = @_;
560 50         121 my @rslt = map { _quoter( $_ ) } @args;
  100         248  
561 50 100       498 return wantarray ? @rslt : join ' ', @rslt;
562             }
563              
564             sub _quoter {
565 100     100   203 my ( $string ) = @_;
566 100 50       212 return 'undef' unless defined $string;
567 100 100       383 return $string if looks_like_number ($string);
568 84 50       167 return q{''} unless $string;
569 84 100       425 return $string unless $string =~ m/ [\s'"\$] /smx;
570 16         42 $string =~ s/ ( [\\'] ) /\\$1/smxg;
571 16         64 return qq{'$string'};
572             }
573              
574             1;
575              
576             __END__