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   345 use 5.008;
  22         66  
4              
5 22     22   94 use strict;
  22         42  
  22         430  
6 22     22   87 use warnings;
  22         42  
  22         969  
7              
8 22     22   4641 use parent qw{ Exporter };
  22         3262  
  22         121  
9              
10 22     22   1124 use Cwd ();
  22         32  
  22         345  
11 22     22   9289 use File::HomeDir;
  22         105181  
  22         1281  
12 22     22   160 use File::Spec;
  22         60  
  22         519  
13 22     22   7404 use Getopt::Long 2.33;
  22         129287  
  22         445  
14 22     22   3237 use Scalar::Util 1.26 qw{ blessed looks_like_number };
  22         371  
  22         1131  
15 22     22   8553 use Text::ParseWords ();
  22         28928  
  22         3937  
16              
17             our $VERSION = '0.058';
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   184 use constant ARRAY_REF => ref [];
  22         31  
  22         1566  
74 22     22   92 use constant CODE_REF => ref sub {};
  22         38  
  22         1054  
75 22     22   83 use constant HASH_REF => ref {};
  22         42  
  22         1202  
76 22     22   94 use constant REGEXP_REF => ref qr{};
  22         49  
  22         1229  
77 22     22   103 use constant SCALAR_REF => ref \1;
  22         27  
  22         1791  
78              
79             {
80             local $@ = undef;
81              
82 22   50     35 use constant HAVE_DATETIME => eval {
83             require DateTime;
84             require DateTime::TimeZone;
85             1;
86 22     22   96 } || 0;
  22         30  
87             }
88              
89             use constant OS_IS_WINDOWS => {
90             dos => 1,
91             MSWin32 => 1,
92 22   50 22   109 }->{$^O} || 0;
  22         34  
  22         56912  
93              
94             # Documented in POD
95              
96             {
97              
98             my @default_config = qw{default pass_through};
99              
100             sub __arguments {
101 350     350   909 my ( $self, @args ) = @_;
102              
103 350 100       777 has_method( $self, '__parse_time_reset' )
104             and $self->__parse_time_reset();
105              
106             @args = map {
107 350 100       674 has_method( $_, 'dereference' ) ? $_->dereference() : $_
  1181         1498  
108             } @args;
109              
110 350         445 my $code = \&{ ( caller 1 )[3] };
  350         2874  
111              
112 350 100       818 if ( HASH_REF eq ref $args[0] ) {
113 6         16 my $opt = shift @args;
114 6         10 my @orig_keys = sort keys %{ $opt };
  6         27  
115 6         23 my $lgl = $self->__legal_options( $code, $opt );
116 6         20 my %opt_name = (
117             level1 => 1,
118             );
119 6         9 my $name;
120 6         24 foreach my $inx ( 0 .. $#$lgl ) {
121 42 100       62 if ( CODE_REF eq ref $lgl->[$inx] ) {
122 6 50       12 defined $name
123             or die "Bug - \$name undefined. Inx $inx; lgl @$lgl";
124 6 100       13 if ( exists $opt->{$name} ) {
125 2         7 $lgl->[$inx]->( $name, $opt->{$name} );
126             }
127             } else {
128 36         86 ( $name = $lgl->[ $inx ] ) =~ s/ \W .* //smx;
129 36         91 $opt_name{$name} = 1;
130             }
131             }
132 6         16 foreach my $key ( @orig_keys ) {
133 9 50       22 $opt_name{$key}
134             or __error_out( $self, wail => "Illegal option '$key'" );
135             }
136 6         21 _apply_default( $self, $opt, \@args );
137 6         48 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 344         444 my ( $err, %opt );
161 344         909 my $lgl = $self->__legal_options( $code, \%opt );
162              
163 344     0   2158 local $SIG{__WARN__} = sub {$err = $_[0]};
  0         0  
164 344   50     764 my $config =
165             $self->__get_attr($code, 'Configure') || \@default_config;
166 344         1740 my $go = Getopt::Long::Parser->new(config => $config);
167 344 50       29225 if ( ! $go->getoptionsfromarray(
168             \@args, \%opt, 'default=s', @$lgl) ) {
169 0         0 __error_out( $self, wail => $err );
170             }
171              
172 344         107301 _apply_default( $self, \%opt, \@args );
173              
174 344         3402 return ( $self, \%opt, @args );
175             }
176             }
177              
178             sub __legal_options {
179 350     350   605 my ( $self, $code, $opt ) = @_;
180 350   50     631 $code ||= \&{ ( caller 1 )[3] };
  0         0  
181 350 50       713 CODE_REF eq ref $code
182             or __error_out( $self, weep => "$code not a CODE ref" );
183 350   50     571 $opt ||= {};
184 350         844 my $lgl = $self->__get_attr( $code, Verb => [] );
185 350 100 100     575 if ( @{ $lgl } && ':compute' eq $lgl->[0] ) {
  350         1081  
186 24 50       102 my $method = $lgl->[1]
187             or __error_out( $self, weep => ':compute did not specify method' );
188 24         109 $lgl = $self->$method( $opt, $lgl );
189             }
190 350         551 return $lgl;
191             }
192              
193             sub _apply_default {
194 350     350   650 my ( $self, $opt, $args ) = @_;
195              
196             my $dflt = delete $opt->{default}
197 350 100       917 or return;
198              
199 2 50       18 if ( ARRAY_REF eq ref $dflt ) {
    50          
    50          
200             # Do nothing -- we already have what we want
201             } elsif ( ref $dflt ) {
202 0         0 __error_out( $self,
203             wail => "Invalid default specification $dflt" );
204             } elsif ( my $code = $self->can( '__tokenize' ) ) {
205 2         5 ( $dflt ) = $code->( $self, $dflt );
206             } else {
207 0         0 $dflt = [ Text::ParseWords::shellwords( $dflt ) ];
208             }
209              
210 2         7 foreach my $inx ( 0 .. $#$dflt ) {
211 14 100 66     40 defined $args->[$inx]
212             and '' ne $args->[$inx]
213             or $args->[$inx] = $dflt->[$inx];
214             }
215              
216 2         7 return;
217             }
218              
219             sub back_end {
220 0     0 1 0 my ( $self, @arg ) = @_;
221 0 0       0 if ( @arg ) {
222 0         0 my ( $pkg, @cls_arg ) = ( $self->__parse_class_and_args(
223             $self->__back_end_default( $arg[0] ) ), @arg[ 1 .. $#arg ] );
224 0         0 my $cls = $self->load_package( { fatal => 1 }, $pkg,
225             'DateTime::Calendar' );
226 0         0 $self->__back_end_validate( $cls, @cls_arg );
227             $self->{_back_end} = {
228 0         0 arg => \@cls_arg,
229             class => $cls,
230             pkg => $pkg,
231             };
232 0         0 $self->{back_end} = shift @arg;
233 0         0 while ( @arg ) {
234 0         0 my ( $name, $value ) = splice @arg, 0, 2;
235 0         0 $self->{back_end} .= ",$name=$value";
236             }
237 0         0 return $self;
238             } else {
239             wantarray
240             and return ( $self->{_back_end}{pkg}, @{
241 0 0       0 $self->{_back_end}{arg} } );
  0         0  
242 0         0 return $self->{back_end};
243             }
244             }
245              
246             sub __back_end_class_name_of_record {
247 0     0   0 my ( $self, $name ) = @_;
248             defined( my $back_end = $self->{_back_end}{class} )
249 0 0       0 or return $name;
250 0 0       0 $back_end eq $self->__back_end_default()
251             and return $name;
252 0         0 $back_end =~ s/ \A DateTime::Calendar:: //smx;
253 0 0       0 @{ $self->{_back_end}{arg} }
  0         0  
254             or return "$name,back_end=$back_end";
255 0         0 my %dt_arg = @{ $self->{_back_end}{arg} };
  0         0  
256 0         0 foreach my $key ( sort keys %dt_arg ) {
257 0         0 $back_end .= ",$key=$dt_arg{$key}";
258             }
259 0         0 return "$name,back_end='$back_end'";
260             }
261              
262             # $backend = __date_manip_backend()
263             #
264             # This subroutine loads Date::Manip and returns the backend available,
265             # either 5 or 6. If Date::Manip can not be loaded it returns undef.
266             #
267             # The idea here is to return 6 if the O-O interface is available, and 5
268             # if it is not but Date::Manip is.
269              
270             sub __date_manip_backend {
271 10 50   10   41 load_package( 'Date::Manip' )
272             or return;
273 0 0       0 Date::Manip->isa( 'Date::Manip::DM6' )
274             and return 6;
275 0         0 return 5;
276             }
277              
278             {
279             my %method_to_sub = (
280             whinge => 'carp',
281             wail => 'croak',
282             weep => 'confess',
283             );
284              
285             # __error_out( $invocant, $method, @arg )
286             #
287             # $method must be 'carp', 'croak', or 'confess'.
288             #
289             # If the $invocant is a blessed reference having method $method,
290             # that method is called with @arg as arguments.
291             #
292             # Otherwise Carp is loaded, $method is mapped to the corresponding
293             # Carp subroutine, and that subroutine is called with @arg as
294             # arguments.
295             #
296             # If we have not thrown an exception as a result of all this, we
297             # just return.
298             sub __error_out {
299 11     11   36 my ( $obj, $method, @arg ) = @_;
300 11 50       54 $method_to_sub{$method}
301             or $method = 'weep';
302 11 100 66     65 if ( blessed( $obj ) && $obj->can( $method )
303             ) {
304 5         18 $obj->$method( @arg );
305             } else {
306 6         52 require Carp;
307 6 50       76 if ( my $code = Carp->can( $method_to_sub{ $method } ) ) {
308 6         7703 $code->( @arg );
309             } else {
310 0         0 Carp::confess( @arg );
311             }
312             }
313 0         0 return;
314             }
315             }
316              
317             sub expand_tilde {
318 39     39 1 124 my @args = @_;
319 39 100       135 my ( $self, $fn ) = @args > 1 ? @args : ( undef, @args );
320 39 50       240 defined $fn
321 21         65 and $fn =~ s{ \A ~ ( [^/]* ) }{ _user_home_dir( $self, $1 ) }smxe;
322 29         936 return $fn;
323             }
324              
325             {
326             my %special = (
327             '+' => sub { return Cwd::cwd() },
328             '~' => sub {
329             return my_dist_config();
330             },
331             '' => sub { return File::HomeDir->my_home() },
332             );
333             # $dir = $self->_user_home_dir( $user );
334             #
335             # Find the home directory for the given user, croaking if this can
336             # not be done. If $user is '' or undef, returns the home directory
337             # for the current user.
338              
339             sub _user_home_dir {
340 21     21   79 my ( $self, $user ) = @_;
341 21 50       49 defined $user
342             or $user = '';
343              
344 21 100       69 if ( my $code = $special{$user} ) {
345 17 100       58 defined( my $special_dir = $code->( $user ) )
346             or _wail( $self, "Unable to find ~$user" );
347 9         110 return $special_dir;
348             } else {
349 4 100       21 defined( my $home_dir = File::HomeDir->users_home( $user ) )
350             or _wail( $self, "Unable to find home for $user" );
351 2         34 return $home_dir;
352             }
353             }
354             }
355              
356             sub find_package_pod {
357 0     0 1 0 my ( $pkg ) = @_;
358 0         0 ( my $fn = $pkg ) =~ s{ :: }{/}smxg;
359 0         0 foreach my $dir ( @INC ) {
360 0 0 0     0 defined $dir
      0        
      0        
361             and not ref $dir
362             and -d $dir
363             and -x _
364             or next;
365 0         0 foreach my $sfx ( qw{ pod pm } ) {
366 0         0 my $path = "$dir/$fn.$sfx";
367 0 0       0 -r $path
368             or next;
369 0         0 return Cwd::abs_path( $path );
370             }
371             }
372 0         0 return;
373             }
374              
375             sub _wail {
376 10     10   681 my ( $invocant, @msg ) = @_;
377 10         40 __error_out( $invocant, wail => @msg );
378 0         0 return; # We should never get here, but Perl::Critic does not
379             # know this.
380             }
381              
382             sub has_method {
383 2709     2709 1 4230 my ( $object, $method ) = @_;
384              
385 2709 100       5940 ref $object or return;
386 995 100       2046 blessed( $object ) or return;
387 970         4400 return $object->can( $method );
388             }
389              
390             sub instance {
391 1502     1502 1 2462 my ( $object, $class ) = @_;
392 1502 100       2584 ref $object or return;
393 1475 100       3863 blessed( $object ) or return;
394 981         4074 return $object->isa( $class );
395             }
396              
397             sub _get_my_lib {
398 226     226   591 my $my_lib = my_dist_config();
399 226 50       22235 if ( defined $my_lib ) {
400 0         0 $my_lib = File::Spec->catdir( $my_lib, 'lib' );
401 0 0       0 -d $my_lib
402             or $my_lib = undef;
403             }
404 226         474 return $my_lib;
405             }
406              
407             {
408             my %loaded;
409              
410             # CAVEAT:
411             #
412             # Unfortunately as things currently stand, the version needs to be
413             # maintained three places:
414             # - lib/Astro/App/Satpass2/Utils.pm
415             # - inc/My/Module/Recommend.pm
416             # - inc/My/Module/Test/App.pm
417             # These all need to stay the same. Sigh.
418             # Any such should be in xt/author/consistent_module_versions.t
419              
420             my %version = (
421             'DateTime::Calendar::Christian' => 0.06,
422             );
423              
424             # Expose the module version so we can test for consistent definition.
425             # IM(NS)HO the following annotation silences a false positive.
426             sub __module_version { ## no critic (RequireArgUnpacking)
427 0     0   0 my $module = $_[-1];
428 0         0 require Carp;
429 0 0       0 exists $version{$module}
430             or Carp::confess( "Bug - Module $module has no defined version" );
431 0         0 return $version{$module};
432             }
433              
434             # my %valid_complaint = map { $_ => 1 } qw{ whinge wail weep };
435              
436             sub load_package {
437             # my ( $module, @prefix ) = @_;
438 247     247 1 684 my @prefix = @_;
439 247         373 my $self;
440 247 100       652 blessed( $prefix[0] )
441             and $self = shift @prefix;
442 247 100       779 my $opt = HASH_REF eq ref $prefix[0] ? shift @prefix : {};
443 247         429 my $module = shift @prefix;
444              
445 247         1344 local @INC = @INC;
446              
447 247 100       744 my $use_lib = exists $opt->{lib} ? $opt->{lib} : _get_my_lib();
448 247 100       570 if ( defined $use_lib ) {
449 1         6 require lib;
450 1         6 lib->import( $use_lib );
451             }
452              
453 247         671 foreach ( $module, @prefix ) {
454 311 50       662 '' eq $_
455             and next;
456 311 50       1889 m/ \A [[:alpha:]]\w* (?: :: [[:alpha:]]\w* )* \z /smx
457             and next;
458              
459 0   0     0 __error_out( $self, $opt->{complaint} || 'weep',
460             "Invalid package name '$_'",
461             );
462             }
463              
464 247         643 my $key = join ' ', $module, @prefix;
465             exists $loaded{$key}
466 247 100       1524 and return $loaded{$key};
467              
468 134         216 local $@ = undef;
469              
470 134         251 push @prefix, '';
471 134         261 foreach my $pfx ( @prefix ) {
472 180         355 my $package = join '::', grep { $_ ne '' } $pfx, $module;
  360         944  
473 180 50       403 '' eq $package
474             and next;
475 180         740 ( my $fn = $package ) =~ s{ :: }{/}smxg;
476 180 100       308 eval {
477 180         30043 require "$fn.pm"; ## no critic (RequireBarewordIncludes)
478 81         158434 1;
479             } or next;
480              
481             not $version{$package}
482 81 50       248 or $package->VERSION( $version{$package} );
483              
484 81         671 return ( $loaded{$key} = $package );
485             }
486              
487 53 100       194 if ( $opt->{fatal} ) {
488 1         6 __error_out( $self, $opt->{fatal}, "Can not load $module: $@" );
489             }
490              
491 52         132 $loaded{$key} = undef;
492              
493 52         345 return;
494             }
495             }
496              
497             # The Perl::Critic annotation on the following line should not (strictly
498             # speaking) be necessary - but Subroutines::RequireArgUnpacking does not
499             # understand the unpacking to be subject to the configuration
500             # allow_arg_unpacking = grep
501             sub merge_hashes { ## no critic (RequireArgUnpacking)
502 0     0 1 0 my @args = grep { HASH_REF eq ref $_ } @_;
  0         0  
503 0 0       0 @args == 1
504             and return $args[0];
505 0         0 my %rslt;
506 0         0 foreach my $hash ( @args ) {
507 0         0 @rslt{ keys %{ $hash } } = values %{ $hash };
  0         0  
  0         0  
508             }
509 0         0 return \%rslt;
510             }
511              
512 22     22   170 use constant MY_PACKAGE_NAME => 'Astro-App-Satpass2';
  22         50  
  22         16145  
513              
514             sub my_dist_config {
515 239     239 1 499 my ( $opt ) = @_;
516              
517             defined $ENV{ASTRO_APP_SATPASS2_CONFIG_DIR}
518 239 100       733 and return Cwd::abs_path( $ENV{ASTRO_APP_SATPASS2_CONFIG_DIR} );
519              
520 236   50     2875 my $code = __PACKAGE__->can( "_my_dist_config_$^O" ) || \&_my_dist_config_;
521 236         1088 return $code->( $opt );
522             }
523              
524             sub _my_dist_config_ {
525 236     236   408 my ( $opt ) = @_;
526             return File::HomeDir->my_dist_config(
527             MY_PACKAGE_NAME,
528 236         1643 { create => $opt->{'create-directory'} },
529             );
530             }
531              
532             # Called dynamically by my_dist_config() if $^O is 'darwin'.
533             sub _my_dist_config_darwin { ## no critic (ProhibitUnusedPrivateSubroutines)
534             # my ( $opt ) = @_;
535 0 0   0   0 my $rslt = File::HomeDir->my_dist_data( MY_PACKAGE_NAME )
536             or goto &_my_dist_config_;
537 0         0 return $rslt;
538             }
539              
540             sub __parse_class_and_args {
541 43     43   32485 my ( $self, $arg, @rest ) = @_;
542 43         324 my ( $cls, @val ) =
543             Text::ParseWords::parse_line( qr{ , }smx, 0, $arg );
544 43 50 33     3946 unless ( defined $cls &&
545             $cls =~ m/ \A [_[:alpha:]] \w* (?: :: \w+ )* \z /smx ) {
546 0 0       0 $cls = defined $cls ? "'$cls'" : 'undef';
547 0 0       0 my $warner = $self->can( 'wail' ) ? $self : $self->warner();
548 0         0 $warner->wail( "Invalid class name $cls" );
549             }
550 43         100 foreach ( @val ) {
551 4 50       18 m/ = /smx
552             or $_ .= '=';
553             };
554 43         135 return ( $cls, ( map { split qr{ = }smx, $_, 2 } @val ), @rest );
  4         61  
555             }
556              
557             sub quoter {
558 50     50 1 198 my @args = @_;
559 50         78 my @rslt = map { _quoter( $_ ) } @args;
  100         175  
560 50 100       295 return wantarray ? @rslt : join ' ', @rslt;
561             }
562              
563             sub _quoter {
564 100     100   148 my ( $string ) = @_;
565 100 50       158 return 'undef' unless defined $string;
566 100 100       264 return $string if looks_like_number ($string);
567 84 50       123 return q{''} unless $string;
568 84 100       273 return $string unless $string =~ m/ [\s'"\$] /smx;
569 16         27 $string =~ s/ ( [\\'] ) /\\$1/smxg;
570 16         40 return qq{'$string'};
571             }
572              
573             1;
574              
575             __END__