File Coverage

blib/lib/Astro/App/Satpass2/Format/Template.pm
Criterion Covered Total %
statement 213 242 88.0
branch 61 102 59.8
condition 14 27 51.8
subroutine 43 46 93.4
pod 11 11 100.0
total 342 428 79.9


line stmt bran cond sub pod time code
1             package Astro::App::Satpass2::Format::Template;
2              
3 8     8   220478 use strict;
  8         21  
  8         344  
4 8     8   46 use warnings;
  8         14  
  8         682  
5              
6 8     8   488 use parent qw{ Astro::App::Satpass2::Format };
  8         261  
  8         59  
7              
8 8     8   536 use Astro::App::Satpass2::Locale qw{ __localize };
  8         20  
  8         509  
9             # use Astro::App::Satpass2::FormatValue;
10 8     8   4234 use Astro::App::Satpass2::FormatValue::Formatter;
  8         30  
  8         424  
11 8         1092 use Astro::App::Satpass2::Utils qw{
12             instance
13             ARRAY_REF
14             HASH_REF
15             SCALAR_REF
16             @CARP_NOT
17 8     8   57 };
  8         32  
18 8     8   5703 use Astro::App::Satpass2::Wrap::Array;
  8         30  
  8         379  
19 8     8   5138 use Astro::App::Satpass2::Format::Template::Provider;
  8         34  
  8         558  
20 8     8   64 use Astro::Coord::ECI::TLE 0.059 qw{ :constants };
  8         308  
  8         2234  
21 8         709 use Astro::Coord::ECI::Utils 0.059 qw{
22             deg2rad embodies julianday PI rad2deg TWOPI
23 8     8   62 };
  8         288  
24 8     8   54 use Clone qw{ };
  8         16  
  8         221  
25 8     8   44 use POSIX qw{ floor };
  8         19  
  8         81  
26 8     8   5076 use Template;
  8         40886  
  8         356  
27 8     8   883 use Text::Abbrev;
  8         106  
  8         658  
28 8     8   63 use Text::Wrap qw{ wrap };
  8         19  
  8         754  
29              
30             our $VERSION = '0.057';
31              
32 8     8   63 use constant FORMAT_VALUE => 'Astro::App::Satpass2::FormatValue';
  8         19  
  8         24410  
33              
34             sub new {
35 9     9 1 13948 my ($class, @args) = @_;
36 9         76 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         35 $self->{canned_template} = {};
41              
42 9         47 $self->_new_tt( $self->permissive() );
43              
44 9         41 $self->{default} = {};
45 9         32 $self->{formatter_method} = {};
46              
47 9         102 return $self;
48             }
49              
50             sub _new_tt {
51 10     10   32 my ( $self, $permissive ) = @_;
52              
53 10 50       191 $self->{tt} = Template->new(
54             {
55             LOAD_TEMPLATES => [
56             Astro::App::Satpass2::Format::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         234752 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 3 my ( $self, @arg ) = @_;
74 1 50       6 my $fmtr = HASH_REF eq ref $arg[0] ? $arg[0] : $arg[1];
75 1 50       3 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       3 or $self->wail(
80             'Formatter definition must have {name} defined' );
81             $self->{formatter_method}{$fmtr_name}
82             and $self->{warner}->wail(
83 1 50       4 "Formatter method $fmtr_name already exists" );
84             FORMAT_VALUE->can( $fmtr_name )
85             and $self->{warner}->wail(
86 1 50       20 "Formatter $fmtr_name can not override built-in formatter" );
87 1         9 $self->{formatter_method}{$fmtr_name} =
88             Astro::App::Satpass2::FormatValue::Formatter->new( $fmtr );
89 1         3 return $self;
90             }
91              
92             sub attribute_names {
93 17     17 1 48 my ( $self ) = @_;
94 17         104 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   63 my ( $self ) = @_;
121 72         138 return ( _uniq( map { keys %{ $_ } } $self->{canned_template},
  72         614  
122 24         2051 __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 24 my ( $self, $method, @args ) = @_;
152 7 50       59 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   612 my ( $self, @arg ) = @_;
160 302 50       1589 @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 4285 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       601 );
182              
183 62 100       236 _is_format() and return $data{data};
184              
185             my $tplt = delete $data{template}
186 61 50       291 or $self->wail( 'template argument is required' );
187              
188 61 100       263 my $tplt_name = SCALAR_REF eq ref $tplt ? ${ $tplt } : $tplt;
  1         5  
189              
190 61   33     384 $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         426 };
197              
198 61   33     494 $data{provider} ||= $self->provider();
199              
200 61 50       257 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         349 $data{time} = $self->_wrap(
208             data => { time => time },
209             report => $tplt_name,
210             );
211             }
212              
213 61         259 my $value_formatter = $self->value_formatter();
214              
215             $data{title} = $self->_wrap(
216             default => $data{default},
217 61         256 report => $tplt_name,
218             );
219             $data{TITLE_GRAVITY_BOTTOM} =
220 61         399 $value_formatter->TITLE_GRAVITY_BOTTOM;
221             $data{TITLE_GRAVITY_TOP} =
222 61         233 $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         533 };
228              
229             local $Template::Stash::LIST_OPS->{events} = sub {
230 3     3   58 my @args = @_;
231 3         19 return $self->_all_events( $args[0] );
232 61         382 };
233              
234             local $Template::Stash::LIST_OPS->{fixed_width} = sub {
235 2     2   7 my ( $list, $value ) = @_;
236 2         4 foreach my $item ( @{ $list } ) {
  2         6  
237 10 50       44 my $code = $item->can( 'fixed_width' )
238             or next;
239 10         28 $code->( $item, $value );
240             }
241 2         16 return;
242 61         447 };
243              
244             local $Template::Stash::LIST_OPS->{first_tle} = sub {
245 29     29   128 my ( $list ) = @_;
246 29         78 foreach my $item ( @{ $list } ) {
  29         101  
247 29 50       148 embodies( $item->body(), 'Astro::Coord::ECI::TLE' )
248             and return $item;
249             }
250 0         0 return;
251 61         435 };
252              
253             $data{localize} = sub {
254 28     28   68556 return _localize( $tplt_name, @_ );
255 61         345 };
256              
257             # NOTE - must come after $data{localize} because
258             # $data{format_detail} uses $data{localize}
259             $data{format_detail} = sub {
260 173     173   11569 my ( $kind, $evt ) = @_;
261              
262 173 50       1031 instance( $evt, FORMAT_VALUE )
263             or return;
264 173 50       929 defined ( my $type = $evt->$kind( width => '' ) )
265             or return;
266 173         850 $type =~ s/ \s+ \z //smx;
267              
268 173         766 foreach my $name ( "$kind:$type", $kind ) {
269 329 100       1787 defined ( my $tplt = $self->template( "$tplt:$name" ) )
270             or next;
271             my $output = $self->_process( \$tplt,
272             evt => $evt,
273             localize => $data{localize},
274             sp => $data{sp},
275 173         1487 );
276              
277 173         858 chomp $output;
278 173         1708 return $output;
279             }
280 0         0 return __localize(
281             text => [ '+template', "$tplt:$kind" ],
282             );
283 61         466 };
284              
285 61         478 my $output = $self->_process( $tplt, %data );
286              
287             # TODO would love to use \h here, but that needs 5.10.
288 60         1543 $output =~ s/ [ \t]+ (?= \n ) //sxmg;
289             $data{title}->title_gravity() eq $data{TITLE_GRAVITY_BOTTOM}
290 60 100       442 and $output =~ s/ \A \n //smx;
291              
292 60         2097 return $output;
293             }
294              
295             sub gmt {
296 57     57 1 187 my ( $self, @args ) = @_;
297 57 100       172 if ( @args ) {
298 3         23 $self->time_formatter()->gmt( @args );
299 3         30 return $self->SUPER::gmt( @args );
300             } else {
301 54         380 return $self->SUPER::gmt();
302             }
303             }
304              
305             sub local_coord {
306 207     207 1 756 my ( $self, @args ) = @_;
307 207 100       756 if ( @args ) {
308 12         35 my $val = $args[0];
309 12 100       129 defined $val
310             or $val = $self->DEFAULT_LOCAL_COORD;
311              
312 12 50       59 defined $self->template( $val )
313             or $self->wail(
314             'Unknown local coordinate specification', $val );
315              
316 12         93 return $self->SUPER::local_coord( @args );
317             } else {
318 195         1582 return $self->SUPER::local_coord();
319             }
320             }
321              
322             sub permissive {
323 10     10 1 501 my ( $self, @args ) = @_;
324 10 100       58 if ( @args ) {
325 1 50 25     20 if ( $self->{permissive} xor $args[0] ) {
326 1         5 $self->_new_tt( $args[0] );
327             }
328 1         5 $self->{permissive} = $args[0];
329 1         5 return $self;
330             } else {
331 9         45 return $self->{permissive};
332             }
333             }
334              
335             sub template {
336 620     620 1 1973 my ( $self, $name, @value ) = @_;
337 620 50       1956 defined $name
338             or $self->wail( 'Template name not specified' );
339              
340 620 100       1970 if ( @value ) {
341 3         5 my $tplt_text;
342 3 50 33     26 if ( ! defined $value[0]
      33        
343             || defined( $tplt_text = __localize(
344             text => '+template',
345             default => $value[0] )
346             )
347             && $value[0] eq $tplt_text
348             ) {
349 0         0 delete $self->{canned_template}{$name};
350             } else {
351 3         17 $self->{canned_template}{$name} = $value[0];
352             }
353              
354 3         13 return $self;
355             } else {
356             defined $self->{canned_template}{$name}
357 617 100       2380 and return $self->{canned_template}{$name};
358 613         3552 return __localize(
359             text => [ '+template', $name ],
360             );
361             }
362             }
363              
364             sub tz {
365 16     16 1 110 my ( $self, @args ) = @_;
366 16 50       94 if ( @args ) {
367 16         120 my $tf = $self->time_formatter();
368             # We go through the following because the time formatter may
369             # modify the zone (e.g. if it's using DateTime, zones are
370             # case-sensitive so we may have done case conversion before
371             # storing). We want this object to have the time formatter's
372             # version of the zone.
373 16         119 $tf->tz( @args );
374 16         50 return $self->SUPER::tz( $tf->tz() );
375             } else {
376 0         0 return $self->SUPER::tz();
377             }
378             }
379              
380             sub _all_events {
381 3     3   10 my ( $self, $data ) = @_;
382 3 50       15 ARRAY_REF eq ref $data or return;
383              
384 3         8 my @events;
385 3         6 foreach my $pass ( @{ $data } ) {
  3         21  
386 3         23 push @events, $pass->__raw_events();
387             }
388 3 50       13 @events or return;
389 3         40 @events = sort { $a->{time} <=> $b->{time} } @events;
  16         45  
390              
391 3         9 return [ map { $self->_wrap( data => $_ ) } @events ];
  13         58  
392             }
393              
394             # _is_format()
395             #
396             # Returns true if the format() method is above us on the call
397             # stack, otherwise returns false.
398              
399 8     8   79 use constant REPORT_CALLER => __PACKAGE__ . '::format';
  8         18  
  8         7864  
400             sub _is_format {
401 62     62   158 my $level = 2; # Start with caller's caller.
402 62         589 while ( my @info = caller( $level ) ) {
403 343 100       862 REPORT_CALLER eq $info[3]
404             and return $level;
405 342         2531 $level++;
406             }
407 61         237 return;
408             }
409              
410             sub _localize {
411 28     28   103 my ( $report, $source, $default ) = @_;
412 28 50       84 defined $default
413             or $default = $source;
414 28 0       72 defined $report
    50          
415             or return defined $source ? $source : $default;
416              
417 28         159 return scalar __localize(
418             text => [ "-$report", 'string', $source ],
419             default => $source,
420             );
421             }
422              
423             sub _process {
424 449     449   3288 my ( $self, $tplt, %arg ) = @_;
425             ARRAY_REF eq ref $arg{arg}
426             and $arg{arg} = Astro::App::Satpass2::Wrap::Array->new(
427 449 100       3558 $arg{arg} );
428 449         905 my $output;
429 449         1226 my $tt = $self->{tt};
430              
431 449         875 my $tplt_text;
432 449 100 100     2392 not ref $tplt
433             and defined( $tplt_text = $self->template( $tplt ) )
434             and $tplt = \$tplt_text;
435              
436 449 100       3485 $tt->process( $tplt, \%arg, \$output )
437             or $self->wail( $tt->error() );
438 448         152969 return $output;
439             }
440              
441             # Cribbed shamelessly from List::MoreUtils. The author reserves the
442             # right to relocate, rename or otherwise mung with this without notice
443             # to anyone. Caveat user.
444             sub _uniq {
445 25     25   62 my %found;
446 25         71 return ( grep { ! $found{$_}++ } @_ );
  679         2446  
447             }
448              
449             sub _wrap {
450 517     517   2546 my ( $self, %arg ) = @_;
451              
452 517         1348 my $data = $arg{data};
453 517         1187 my $default = $arg{default};
454 517         1333 my $report = $arg{report};
455              
456 517         1138 my $title = ! $data;
457 517   100     2473 $data ||= {};
458 517   66     1879 $default ||= $self->__default();
459              
460 517 50 66     2312 if ( instance( $data, FORMAT_VALUE ) ) {
    100          
    100          
    50          
461             # Do nothing
462             } elsif ( ! defined $data || HASH_REF eq ref $data ) {
463 449         2526 my $value_formatter = $self->value_formatter();
464             $data = $value_formatter->new(
465             data => $data,
466             default => $default,
467             date_format => $self->date_format(),
468             desired_equinox_dynamical =>
469             $self->desired_equinox_dynamical(),
470             provider => $self->provider(),
471             round_time => $self->round_time(),
472             time_format => $self->time_format(),
473             time_formatter => $self->time_formatter(),
474             local_coordinates => sub {
475 195     195   644 my ( $data, @arg ) = @_;
476             return $self->_process( $self->local_coord(),
477             data => $data,
478             arg => \@arg,
479             title => $self->_wrap(
480             default => $default,
481             report => $report,
482             ),
483             localize => sub {
484 0         0 return _localize( $report, @_ );
485             },
486 195         1315 );
487             },
488             list_formatter => sub {
489 20     20   51 my ( $data, @arg ) = @_;
490 20         92 my $body = $data->body();
491 20 100       468 my $list_type = $body ? $body->__list_type() : 'inertial';
492 20         216 return $self->_process( "list_$list_type",
493             data => $data,
494             arg => \@arg,
495             title => $self->_wrap(
496             default => $default,
497             report => $report,
498             ),
499             );
500             },
501 449         2239 report => $report,
502             title => $title,
503             warner => $self->warner(),
504             );
505 449         1390 $data->add_formatter_method( values %{ $self->{formatter_method} } );
  449         2704  
506             } elsif ( ARRAY_REF eq ref $data ) {
507 45         110 $data = [ map { $self->_wrap( data => $_, report => $report ) } @{ $data } ];
  85         405  
  45         173  
508             } elsif ( embodies( $data, 'Astro::Coord::ECI' ) ) {
509 23         897 $data = $self->_wrap(
510             data => { body => $data },
511             report => $report,
512             );
513             }
514              
515 517         3460 return $data;
516             }
517              
518             __PACKAGE__->create_attribute_methods();
519              
520             1;
521              
522             __END__