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   390 use 5.008;
  22         130  
4              
5 22     22   131 use strict;
  22         38  
  22         643  
6 22     22   97 use warnings;
  22         62  
  22         1537  
7              
8 22     22   6632 use parent qw{ Exporter };
  22         4373  
  22         146  
9              
10 22     22   1722 use Cwd ();
  22         44  
  22         523  
11 22     22   12254 use File::HomeDir;
  22         152129  
  22         1757  
12 22     22   221 use File::Spec;
  22         107  
  22         738  
13 22     22   8257 use Getopt::Long 2.33;
  22         165252  
  22         661  
14 22     22   4525 use Scalar::Util 1.26 qw{ blessed looks_like_number };
  22         538  
  22         1689  
15 22     22   11760 use Text::ParseWords ();
  22         39890  
  22         28856  
16              
17             our $VERSION = '0.057';
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   193 use constant ARRAY_REF => ref [];
  22         46  
  22         2252  
74 22     22   143 use constant CODE_REF => ref sub {};
  22         47  
  22         1581  
75 22     22   139 use constant HASH_REF => ref {};
  22         52  
  22         1750  
76 22     22   134 use constant REGEXP_REF => ref qr{};
  22         61  
  22         1869  
77 22     22   133 use constant SCALAR_REF => ref \1;
  22         42  
  22         2354  
78              
79             {
80             local $@ = undef;
81              
82 22   50     53 use constant HAVE_DATETIME => eval {
83             require DateTime;
84             require DateTime::TimeZone;
85             1;
86 22     22   149 } || 0;
  22         42  
87             }
88              
89             use constant OS_IS_WINDOWS => {
90             dos => 1,
91             MSWin32 => 1,
92 22   50 22   207 }->{$^O} || 0;
  22         50  
  22         74859  
93              
94             # Documented in POD
95              
96             {
97              
98             my @default_config = qw{default pass_through};
99              
100             sub __arguments {
101 350     350   1265 my ( $self, @args ) = @_;
102              
103 350 100       1177 has_method( $self, '__parse_time_reset' )
104             and $self->__parse_time_reset();
105              
106             @args = map {
107 350 100       985 has_method( $_, 'dereference' ) ? $_->dereference() : $_
  1181         2066  
108             } @args;
109              
110 350         779 my $code = \&{ ( caller 1 )[3] };
  350         4494  
111              
112 350 100       1323 if ( HASH_REF eq ref $args[0] ) {
113 6         11 my $opt = shift @args;
114 6         12 my @orig_keys = sort keys %{ $opt };
  6         27  
115 6         24 my $lgl = $self->__legal_options( $code, $opt );
116 6         19 my %opt_name = (
117             level1 => 1,
118             );
119 6         8 my $name;
120 6         22 foreach my $inx ( 0 .. $#$lgl ) {
121 38 100       68 if ( CODE_REF eq ref $lgl->[$inx] ) {
122 4 50       21 defined $name
123             or die "Bug - \$name undefined. Inx $inx; lgl @$lgl";
124 4 100       20 if ( exists $opt->{$name} ) {
125 2         18 $lgl->[$inx]->( $name, $opt->{$name} );
126             }
127             } else {
128 34         129 ( $name = $lgl->[ $inx ] ) =~ s/ \W .* //smx;
129 34         83 $opt_name{$name} = 1;
130             }
131             }
132 6         11 foreach my $key ( @orig_keys ) {
133 9 50       25 $opt_name{$key}
134             or __error_out( $self, wail => "Illegal option '$key'" );
135             }
136 6         21 _apply_default( $self, $opt, \@args );
137 6         43 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         700 my ( $err, %opt );
162 344         1508 my $lgl = $self->__legal_options( $code, \%opt );
163              
164 344     0   3339 local $SIG{__WARN__} = sub {$err = $_[0]};
  0         0  
165 344   50     1331 my $config =
166             $self->__get_attr($code, 'Configure') || \@default_config;
167 344         2673 my $go = Getopt::Long::Parser->new(config => $config);
168 344 50       46524 if ( ! $go->getoptionsfromarray(
169             \@args, \%opt, 'default=s', @$lgl) ) {
170 0         0 __error_out( $self, wail => $err );
171             }
172              
173 344         170445 _apply_default( $self, \%opt, \@args );
174              
175 344         5005 return ( $self, \%opt, @args );
176             }
177             }
178              
179             sub __legal_options {
180 350     350   950 my ( $self, $code, $opt ) = @_;
181 350   50     902 $code ||= \&{ ( caller 1 )[3] };
  0         0  
182 350 50       1332 CODE_REF eq ref $code
183             or __error_out( $self, weep => "$code not a CODE ref" );
184 350   50     921 $opt ||= {};
185 350         1567 my $lgl = $self->__get_attr( $code, Verb => [] );
186 350 100 100     745 if ( @{ $lgl } && ':compute' eq $lgl->[0] ) {
  350         1786  
187 24 50       121 my $method = $lgl->[1]
188             or __error_out( $self, weep => ':compute did not specify method' );
189 24         136 $lgl = $self->$method( $opt, $lgl );
190             }
191 350         806 return $lgl;
192             }
193              
194             sub _apply_default {
195 350     350   1126 my ( $self, $opt, $args ) = @_;
196              
197             my $dflt = delete $opt->{default}
198 350 100       1474 or return;
199              
200 2 50       19 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         8 ( $dflt ) = $code->( $self, $dflt );
207             } else {
208 0         0 $dflt = [ Text::ParseWords::shellwords( $dflt ) ];
209             }
210              
211 2         9 foreach my $inx ( 0 .. $#$dflt ) {
212 14 100 66     33 defined $args->[$inx]
213             and '' ne $args->[$inx]
214             or $args->[$inx] = $dflt->[$inx];
215             }
216              
217 2         5 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   31 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       89 $method_to_sub{$method}
302             or $method = 'weep';
303 11 100 66     108 if ( blessed( $obj ) && $obj->can( $method )
304             ) {
305 5         30 $obj->$method( @arg );
306             } else {
307 6         67 require Carp;
308 6 50       130 if ( my $code = Carp->can( $method_to_sub{ $method } ) ) {
309 6         11717 $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 171 my @args = @_;
320 39 100       213 my ( $self, $fn ) = @args > 1 ? @args : ( undef, @args );
321 39 50       344 defined $fn
322 21         98 and $fn =~ s{ \A ~ ( [^/]* ) }{ _user_home_dir( $self, $1 ) }smxe;
323 29         2581 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   120 my ( $self, $user ) = @_;
342 21 50       116 defined $user
343             or $user = '';
344              
345 21 100       142 if ( my $code = $special{$user} ) {
346 17 100       82 defined( my $special_dir = $code->( $user ) )
347             or _wail( $self, "Unable to find ~$user" );
348 9         231 return $special_dir;
349             } else {
350 4 100       42 defined( my $home_dir = File::HomeDir->users_home( $user ) )
351             or _wail( $self, "Unable to find home for $user" );
352 2         32 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   997 my ( $invocant, @msg ) = @_;
378 10         65 __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 6246 my ( $object, $method ) = @_;
385              
386 2709 100       9964 ref $object or return;
387 995 100       2719 blessed( $object ) or return;
388 970         7174 return $object->can( $method );
389             }
390              
391             sub instance {
392 1502     1502 1 3992 my ( $object, $class ) = @_;
393 1502 100       3829 ref $object or return;
394 1475 100       6153 blessed( $object ) or return;
395 981         6115 return $object->isa( $class );
396             }
397              
398             sub _get_my_lib {
399 164     164   498 my $my_lib = my_dist_config();
400 164 50       22064 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 164         404 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 185     185 1 706 my @prefix = @_;
440 185         332 my $self;
441 185 100       671 blessed( $prefix[0] )
442             and $self = shift @prefix;
443 185 100       809 my $opt = HASH_REF eq ref $prefix[0] ? shift @prefix : {};
444 185         435 my $module = shift @prefix;
445              
446 185         1277 local @INC = @INC;
447              
448 185 100       767 my $use_lib = exists $opt->{lib} ? $opt->{lib} : _get_my_lib();
449 185 100       630 if ( defined $use_lib ) {
450 1         27 require lib;
451 1         9 lib->import( $use_lib );
452             }
453              
454 185         781 foreach ( $module, @prefix ) {
455 249 50       714 '' eq $_
456             and next;
457 249 50       2024 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 185         654 my $key = join ' ', $module, @prefix;
466             exists $loaded{$key}
467 185 100       1454 and return $loaded{$key};
468              
469 131         304 local $@ = undef;
470              
471 131         335 push @prefix, '';
472 131         305 foreach my $pfx ( @prefix ) {
473 177         473 my $package = join '::', grep { $_ ne '' } $pfx, $module;
  354         1193  
474 177 50       503 '' eq $package
475             and next;
476 177         1011 ( my $fn = $package ) =~ s{ :: }{/}smxg;
477 177 100       404 eval {
478 177         36896 require "$fn.pm"; ## no critic (RequireBarewordIncludes)
479 80         203058 1;
480             } or next;
481              
482             not $version{$package}
483 80 50       368 or $package->VERSION( $version{$package} );
484              
485 80         920 return ( $loaded{$key} = $package );
486             }
487              
488 51 100       252 if ( $opt->{fatal} ) {
489 1         8 __error_out( $self, $opt->{fatal}, "Can not load $module: $@" );
490             }
491              
492 50         164 $loaded{$key} = undef;
493              
494 50         565 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   216 use constant MY_PACKAGE_NAME => 'Astro-App-Satpass2';
  22         47  
  22         26629  
514              
515             sub my_dist_config {
516 177     177 1 455 my ( $opt ) = @_;
517              
518             defined $ENV{ASTRO_APP_SATPASS2_CONFIG_DIR}
519 177 100       846 and return Cwd::abs_path( $ENV{ASTRO_APP_SATPASS2_CONFIG_DIR} );
520              
521 174   50     3053 my $code = __PACKAGE__->can( "_my_dist_config_$^O" ) || \&_my_dist_config_;
522 174         603 return $code->( $opt );
523             }
524              
525             sub _my_dist_config_ {
526 174     174   428 my ( $opt ) = @_;
527             return File::HomeDir->my_dist_config(
528             MY_PACKAGE_NAME,
529 174         1725 { 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   58326 my ( $self, $arg, @rest ) = @_;
543 43         396 my ( $cls, @val ) =
544             Text::ParseWords::parse_line( qr{ , }smx, 0, $arg );
545 43 50 33     6072 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         142 foreach ( @val ) {
552 4 50       19 m/ = /smx
553             or $_ .= '=';
554             };
555 43         253 return ( $cls, ( map { split qr{ = }smx, $_, 2 } @val ), @rest );
  4         86  
556             }
557              
558             sub quoter {
559 50     50 1 319 my @args = @_;
560 50         121 my @rslt = map { _quoter( $_ ) } @args;
  100         200  
561 50 100       576 return wantarray ? @rslt : join ' ', @rslt;
562             }
563              
564             sub _quoter {
565 100     100   190 my ( $string ) = @_;
566 100 50       241 return 'undef' unless defined $string;
567 100 100       408 return $string if looks_like_number ($string);
568 84 50       214 return q{''} unless $string;
569 84 100       437 return $string unless $string =~ m/ [\s'"\$] /smx;
570 16         59 $string =~ s/ ( [\\'] ) /\\$1/smxg;
571 16         97 return qq{'$string'};
572             }
573              
574             1;
575              
576             __END__