File Coverage

blib/lib/Astro/App/Satpass2/Format/Template.pm
Criterion Covered Total %
statement 215 244 88.1
branch 61 102 59.8
condition 14 27 51.8
subroutine 43 46 93.4
pod 11 11 100.0
total 344 430 80.0


line stmt bran cond sub pod time code
1             package Astro::App::Satpass2::Format::Template;
2              
3 8     8   1690 use strict;
  8         29  
  8         304  
4 8     8   47 use warnings;
  8         19  
  8         256  
5              
6 8     8   64 use parent qw{ Astro::App::Satpass2::Format };
  8         20  
  8         67  
7              
8 8     8   410 use Astro::App::Satpass2::Locale qw{ __localize };
  8         28  
  8         424  
9             # use Astro::App::Satpass2::FormatValue;
10 8     8   3540 use Astro::App::Satpass2::FormatValue::Formatter;
  8         22  
  8         274  
11 8         832 use Astro::App::Satpass2::Utils qw{
12             instance
13             ARRAY_REF
14             HASH_REF
15             SCALAR_REF
16             @CARP_NOT
17 8     8   51 };
  8         20  
18 8     8   3482 use Astro::App::Satpass2::Wrap::Array;
  8         59  
  8         345  
19 8     8   54 use Astro::Coord::ECI::TLE 0.059 qw{ :constants };
  8         131  
  8         1509  
20 8         534 use Astro::Coord::ECI::Utils 0.059 qw{
21             deg2rad embodies julianday PI rad2deg TWOPI
22 8     8   72 };
  8         127  
23 8     8   52 use Clone qw{ };
  8         31  
  8         192  
24 8     8   42 use POSIX qw{ floor };
  8         20  
  8         59  
25 8     8   4706 use Template;
  8         154941  
  8         282  
26 8     8   64 use Template::Provider;
  8         21  
  8         151  
27 8     8   766 use Text::Abbrev;
  8         66  
  8         435  
28 8     8   52 use Text::Wrap qw{ wrap };
  8         35  
  8         586  
29              
30             our $VERSION = '0.051_01';
31              
32 8     8   60 use constant FORMAT_VALUE => 'Astro::App::Satpass2::FormatValue';
  8         26  
  8         18896  
33              
34             sub new {
35 9     9 1 4451 my ($class, @args) = @_;
36 9         67 my $self = $class->SUPER::new( @args );
37              
38             # As of 0.020_002 the template definitions are in the
39             # locale system. The attribute simply holds modifications.
40 9         25 $self->{canned_template} = {};
41              
42 9         40 $self->_new_tt( $self->permissive() );
43              
44 9         38 $self->{default} = {};
45 9         55 $self->{formatter_method} = {};
46              
47 9         68 return $self;
48             }
49              
50             sub _new_tt {
51 10     10   41 my ( $self, $permissive ) = @_;
52              
53 10 50       94 $self->{tt} = Template->new(
54             {
55             LOAD_TEMPLATES => [
56             Template::Provider->new(
57             ABSOLUTE => $permissive,
58             RELATIVE => $permissive,
59             ),
60             ],
61             }
62             ) or $self->weep(
63             "Failed to instantate tt: $Template::ERROR" );
64              
65 10         190695 return;
66             }
67              
68             sub add_formatter_method {
69             # TODO I want the arguments to be ( $self, $fmtr ), but for the
70             # moment I have to live with an unreleased version that passed the
71             # name as the first argument. I will go to the desired signature as
72             # soon as I get this version installed on my own machine.
73 1     1 1 5 my ( $self, @arg ) = @_;
74 1 50       6 my $fmtr = HASH_REF eq ref $arg[0] ? $arg[0] : $arg[1];
75 1 50       5 HASH_REF eq ref $fmtr
76             or $self->wail(
77             'Formatter definition must be a HASH reference' );
78             defined( my $fmtr_name = $fmtr->{name} )
79 1 50       5 or $self->wail(
80             'Formatter definition must have {name} defined' );
81             $self->{formatter_method}{$fmtr_name}
82             and $self->{warner}->wail(
83 1 50       5 "Formatter method $fmtr_name already exists" );
84             FORMAT_VALUE->can( $fmtr_name )
85             and $self->{warner}->wail(
86 1 50       17 "Formatter $fmtr_name can not override built-in formatter" );
87 1         13 $self->{formatter_method}{$fmtr_name} =
88             Astro::App::Satpass2::FormatValue::Formatter->new( $fmtr );
89 1         4 return $self;
90             }
91              
92             sub attribute_names {
93 17     17 1 55 my ( $self ) = @_;
94 17         110 return ( $self->SUPER::attribute_names(),
95             qw{ permissive },
96             );
97             }
98              
99             sub config {
100 0     0 1 0 my ( $self, %args ) = @_;
101 0         0 my @data = $self->SUPER::config( %args );
102              
103             # TODO support for the {default} key.
104              
105 0 0       0 foreach my $name (
106             sort $args{changes} ?
107 0         0 keys %{ $self->{canned_template} } :
108             $self->__list_templates()
109             ) {
110             push @data, [ template => $name,
111 0         0 $self->{canned_template}{$name} ];
112             }
113              
114 0 0       0 return wantarray ? @data : \@data;
115             }
116              
117             # Return the names of all known templates, in no particular order. No
118             # arguments other than the invocant.
119             sub __list_templates {
120 24     24   76 my ( $self ) = @_;
121 72         139 return ( _uniq( map { keys %{ $_ } } $self->{canned_template},
  72         484  
122 24         121 __localize(
123             text => '+template',
124             default => {},
125             ) ) );
126             }
127              
128             {
129             my %decoder = (
130             template => sub {
131             my ( $self, $method, @args ) = @_;
132              
133             =begin comment
134              
135             1 == @args
136             and return ( @args, $self->$method( @args ) );
137             $self->$method( @args );
138             return $self;
139              
140             =end comment
141              
142             =cut
143              
144             1 == @args
145             or return $self->$method( @args );
146             return ( @args, $self->$method( @args ) );
147             },
148             );
149              
150             sub decode {
151 7     7 1 18 my ( $self, $method, @args ) = @_;
152 7 50       37 my $dcdr = $decoder{$method}
153             or return $self->SUPER::decode( $method, @args );
154 0         0 goto $dcdr;
155             }
156             }
157              
158             sub __default {
159 302     302   627 my ( $self, @arg ) = @_;
160 302 50       1293 @arg or return $self->{default};
161 0         0 my $action = shift @arg;
162 0 0       0 @arg or return $self->{default}{$action};
163 0         0 my $attrib = shift @arg;
164             defined $attrib
165 0 0       0 or return delete $self->{default}{$action};
166 0 0       0 @arg or return $self->{default}{$action}{$attrib};
167 0         0 my $value = shift @arg;
168             defined $value
169 0 0       0 or return delete $self->{default}{$action}{$attrib};
170 0         0 $self->{default}{$action}{$attrib} = $value;
171 0         0 return $value;
172             }
173              
174             sub format : method { ## no critic (ProhibitBuiltInHomonyms)
175 62     62 1 4008 my ( $self, %data ) = @_;
176              
177             exists $data{data}
178             and $data{data} = $self->_wrap(
179             data => $data{data},
180             report => $data{template},
181 62 100       465 );
182              
183 62 100       275 _is_format() and return $data{data};
184              
185             my $tplt = delete $data{template}
186 61 50       326 or $self->wail( 'template argument is required' );
187              
188 61 100       253 my $tplt_name = SCALAR_REF eq ref $tplt ? ${ $tplt } : $tplt;
  1         4  
189              
190 61   33     368 $data{default} ||= $self->__default();
191              
192             $data{instantiate} = sub {
193 0     0   0 my @args = @_;
194 0         0 my $class = Astro::App::Satpass2::Utils::load_package( @args );
195 0         0 return $class->new();
196 61         482 };
197              
198 61   33     372 $data{provider} ||= $self->provider();
199              
200 61 50       260 if ( $data{time} ) {
201             ref $data{time}
202             or $data{time} = $self->_wrap(
203             data => { time => $data{time} },
204 0 0       0 report => $tplt_name,
205             );
206             } else {
207 61         369 $data{time} = $self->_wrap(
208             data => { time => time },
209             report => $tplt_name,
210             );
211             }
212              
213 61         261 my $value_formatter = $self->value_formatter();
214              
215             $data{title} = $self->_wrap(
216             default => $data{default},
217 61         278 report => $tplt_name,
218             );
219             $data{TITLE_GRAVITY_BOTTOM} =
220 61         374 $value_formatter->TITLE_GRAVITY_BOTTOM;
221             $data{TITLE_GRAVITY_TOP} =
222 61         255 $value_formatter->TITLE_GRAVITY_TOP;
223              
224             local $Template::Stash::LIST_OPS->{bodies} = sub {
225 0     0   0 my ( $list ) = @_;
226 0         0 return [ map { $_->body() } @{ $list } ];
  0         0  
  0         0  
227 61         463 };
228              
229             local $Template::Stash::LIST_OPS->{events} = sub {
230 3     3   66 my @args = @_;
231 3         13 $DB::single = 1;
232 3         18 return $self->_all_events( $args[0] );
233 61         384 };
234              
235             local $Template::Stash::LIST_OPS->{fixed_width} = sub {
236 2     2   9 my ( $list, $value ) = @_;
237 2         5 foreach my $item ( @{ $list } ) {
  2         6  
238 10 50       36 my $code = $item->can( 'fixed_width' )
239             or next;
240 10         28 $code->( $item, $value );
241             }
242 2         12 return;
243 61         392 };
244              
245             local $Template::Stash::LIST_OPS->{first_tle} = sub {
246 29     29   112 my ( $list ) = @_;
247 29         162 $DB::single = 1;
248 29         59 foreach my $item ( @{ $list } ) {
  29         88  
249 29 50       107 embodies( $item->body(), 'Astro::Coord::ECI::TLE' )
250             and return $item;
251             }
252 0         0 return;
253 61         390 };
254              
255             $data{localize} = sub {
256 28     28   78174 return _localize( $tplt_name, @_ );
257 61         359 };
258              
259             # NOTE - must come after $data{localize} because
260             # $data{format_detail} uses $data{localize}
261             $data{format_detail} = sub {
262 173     173   8336 my ( $kind, $evt ) = @_;
263              
264 173 50       612 instance( $evt, FORMAT_VALUE )
265             or return;
266 173 50       698 defined ( my $type = $evt->$kind( width => '' ) )
267             or return;
268 173         692 $type =~ s/ \s+ \z //smx;
269              
270 173         535 foreach my $name ( "$kind:$type", $kind ) {
271 329 100       1113 defined ( my $tplt = $self->template( "$tplt:$name" ) )
272             or next;
273             my $output = $self->_process( \$tplt,
274             evt => $evt,
275             localize => $data{localize},
276             sp => $data{sp},
277 173         753 );
278              
279 173         577 chomp $output;
280 173         998 return $output;
281             }
282 0         0 return __localize(
283             text => [ '+template', "$tplt:$kind" ],
284             );
285 61         396 };
286              
287 61         512 my $output = $self->_process( $tplt, %data );
288              
289             # TODO would love to use \h here, but that needs 5.10.
290 60         1381 $output =~ s/ [ \t]+ (?= \n ) //sxmg;
291             $data{title}->title_gravity() eq $data{TITLE_GRAVITY_BOTTOM}
292 60 100       381 and $output =~ s/ \A \n //smx;
293              
294 60         1408 return $output;
295             }
296              
297             sub gmt {
298 57     57 1 210 my ( $self, @args ) = @_;
299 57 100       207 if ( @args ) {
300 3         13 $self->time_formatter()->gmt( @args );
301 3         30 return $self->SUPER::gmt( @args );
302             } else {
303 54         250 return $self->SUPER::gmt();
304             }
305             }
306              
307             sub local_coord {
308 207     207 1 433 my ( $self, @args ) = @_;
309 207 100       484 if ( @args ) {
310 12         42 my $val = $args[0];
311 12 100       137 defined $val
312             or $val = $self->DEFAULT_LOCAL_COORD;
313              
314 12 50       41 defined $self->template( $val )
315             or $self->wail(
316             'Unknown local coordinate specification', $val );
317              
318 12         59 return $self->SUPER::local_coord( @args );
319             } else {
320 195         831 return $self->SUPER::local_coord();
321             }
322             }
323              
324             sub permissive {
325 10     10 1 764 my ( $self, @args ) = @_;
326 10 100       37 if ( @args ) {
327 1 50 25     11 if ( $self->{permissive} xor $args[0] ) {
328 1         8 $self->_new_tt( $args[0] );
329             }
330 1         5 $self->{permissive} = $args[0];
331 1         3 return $self;
332             } else {
333 9         43 return $self->{permissive};
334             }
335             }
336              
337             sub template {
338 620     620 1 1349 my ( $self, $name, @value ) = @_;
339 620 50       1286 defined $name
340             or $self->wail( 'Template name not specified' );
341              
342 620 100       1385 if ( @value ) {
343 3         7 my $tplt_text;
344 3 50 33     22 if ( ! defined $value[0]
      33        
345             || defined( $tplt_text = __localize(
346             text => '+template',
347             default => $value[0] )
348             )
349             && $value[0] eq $tplt_text
350             ) {
351 0         0 delete $self->{canned_template}{$name};
352             } else {
353 3         11 $self->{canned_template}{$name} = $value[0];
354             }
355              
356 3         12 return $self;
357             } else {
358             defined $self->{canned_template}{$name}
359 617 100       1602 and return $self->{canned_template}{$name};
360 613         2029 return __localize(
361             text => [ '+template', $name ],
362             );
363             }
364             }
365              
366             sub tz {
367 16     16 1 52 my ( $self, @args ) = @_;
368 16 50       58 if ( @args ) {
369 16         69 my $tf = $self->time_formatter();
370             # We go through the following because the time formatter may
371             # modify the zone (e.g. if it's using DateTime, zones are
372             # case-sensitive so we may have done case conversion before
373             # storing). We want this object to have the time formatter's
374             # version of the zone.
375 16         126 $tf->tz( @args );
376 16         56 return $self->SUPER::tz( $tf->tz() );
377             } else {
378 0         0 return $self->SUPER::tz();
379             }
380             }
381              
382             sub _all_events {
383 3     3   12 my ( $self, $data ) = @_;
384 3 50       14 ARRAY_REF eq ref $data or return;
385              
386 3         10 my @events;
387 3         7 foreach my $pass ( @{ $data } ) {
  3         18  
388 3         25 push @events, $pass->__raw_events();
389             }
390 3 50       17 @events or return;
391 3         22 @events = sort { $a->{time} <=> $b->{time} } @events;
  16         42  
392              
393 3         10 return [ map { $self->_wrap( data => $_ ) } @events ];
  13         34  
394             }
395              
396             # _is_format()
397             #
398             # Returns true if the format() method is above us on the call
399             # stack, otherwise returns false.
400              
401 8     8   74 use constant REPORT_CALLER => __PACKAGE__ . '::format';
  8         23  
  8         6321  
402             sub _is_format {
403 62     62   167 my $level = 2; # Start with caller's caller.
404 62         706 while ( my @info = caller( $level ) ) {
405 261 100       654 REPORT_CALLER eq $info[3]
406             and return $level;
407 260         1666 $level++;
408             }
409 61         242 return;
410             }
411              
412             sub _localize {
413 28     28   78 my ( $report, $source, $default ) = @_;
414 28 50       81 defined $default
415             or $default = $source;
416 28 0       69 defined $report
    50          
417             or return defined $source ? $source : $default;
418              
419 28         116 return scalar __localize(
420             text => [ "-$report", 'string', $source ],
421             default => $source,
422             );
423             }
424              
425             sub _process {
426 449     449   1914 my ( $self, $tplt, %arg ) = @_;
427             ARRAY_REF eq ref $arg{arg}
428             and $arg{arg} = Astro::App::Satpass2::Wrap::Array->new(
429 449 100       2143 $arg{arg} );
430 449         736 my $output;
431 449         909 my $tt = $self->{tt};
432              
433 449         616 my $tplt_text;
434 449 100 100     1528 not ref $tplt
435             and defined( $tplt_text = $self->template( $tplt ) )
436             and $tplt = \$tplt_text;
437              
438 449 100       1925 $tt->process( $tplt, \%arg, \$output )
439             or $self->wail( $tt->error() );
440 448         110295 return $output;
441             }
442              
443             # Cribbed shamelessly from List::MoreUtils. The author reserves the
444             # right to relocate, rename or otherwise mung with this without notice
445             # to anyone. Caveat user.
446             sub _uniq {
447 25     25   81 my %found;
448 25         66 return ( grep { ! $found{$_}++ } @_ );
  679         1694  
449             }
450              
451             sub _wrap {
452 517     517   1725 my ( $self, %arg ) = @_;
453              
454 517         1009 my $data = $arg{data};
455 517         871 my $default = $arg{default};
456 517         984 my $report = $arg{report};
457              
458 517         1041 my $title = ! $data;
459 517   100     1900 $data ||= {};
460 517   66     1483 $default ||= $self->__default();
461              
462 517 50 66     1487 if ( instance( $data, FORMAT_VALUE ) ) {
    100          
    100          
    50          
463             # Do nothing
464             } elsif ( ! defined $data || HASH_REF eq ref $data ) {
465 449         1466 my $value_formatter = $self->value_formatter();
466             $data = $value_formatter->new(
467             data => $data,
468             default => $default,
469             date_format => $self->date_format(),
470             desired_equinox_dynamical =>
471             $self->desired_equinox_dynamical(),
472             provider => $self->provider(),
473             round_time => $self->round_time(),
474             time_format => $self->time_format(),
475             time_formatter => $self->time_formatter(),
476             local_coordinates => sub {
477 195     195   487 my ( $data, @arg ) = @_;
478             return $self->_process( $self->local_coord(),
479             data => $data,
480             arg => \@arg,
481             title => $self->_wrap(
482             default => $default,
483             report => $report,
484             ),
485             localize => sub {
486 0         0 return _localize( $report, @_ );
487             },
488 195         646 );
489             },
490             list_formatter => sub {
491 20     20   53 my ( $data, @arg ) = @_;
492 20         70 my $body = $data->body();
493 20 100       360 my $list_type = $body ? $body->__list_type() : 'inertial';
494 20         159 return $self->_process( "list_$list_type",
495             data => $data,
496             arg => \@arg,
497             title => $self->_wrap(
498             default => $default,
499             report => $report,
500             ),
501             );
502             },
503 449         1417 report => $report,
504             title => $title,
505             warner => $self->warner(),
506             );
507 449         1065 $data->add_formatter_method( values %{ $self->{formatter_method} } );
  449         1849  
508             } elsif ( ARRAY_REF eq ref $data ) {
509 45         132 $data = [ map { $self->_wrap( data => $_, report => $report ) } @{ $data } ];
  85         291  
  45         148  
510             } elsif ( embodies( $data, 'Astro::Coord::ECI' ) ) {
511 23         777 $data = $self->_wrap(
512             data => { body => $data },
513             report => $report,
514             );
515             }
516              
517 517         2264 return $data;
518             }
519              
520             __PACKAGE__->create_attribute_methods();
521              
522             1;
523              
524             __END__