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   1636 use strict;
  8         18  
  8         255  
4 8     8   40 use warnings;
  8         22  
  8         244  
5              
6 8     8   47 use parent qw{ Astro::App::Satpass2::Format };
  8         22  
  8         63  
7              
8 8     8   596 use Astro::App::Satpass2::Locale qw{ __localize };
  8         18  
  8         462  
9             # use Astro::App::Satpass2::FormatValue;
10 8     8   3649 use Astro::App::Satpass2::FormatValue::Formatter;
  8         37  
  8         287  
11 8         859 use Astro::App::Satpass2::Utils qw{
12             instance
13             ARRAY_REF
14             HASH_REF
15             SCALAR_REF
16             @CARP_NOT
17 8     8   95 };
  8         30  
18 8     8   3788 use Astro::App::Satpass2::Wrap::Array;
  8         33  
  8         339  
19 8     8   72 use Astro::Coord::ECI::TLE 0.059 qw{ :constants };
  8         132  
  8         1444  
20 8         528 use Astro::Coord::ECI::Utils 0.059 qw{
21             deg2rad embodies julianday PI rad2deg TWOPI
22 8     8   76 };
  8         123  
23 8     8   55 use Clone qw{ };
  8         22  
  8         201  
24 8     8   46 use POSIX qw{ floor };
  8         37  
  8         82  
25 8     8   5227 use Template;
  8         161086  
  8         283  
26 8     8   68 use Template::Provider;
  8         24  
  8         158  
27 8     8   718 use Text::Abbrev;
  8         71  
  8         438  
28 8     8   73 use Text::Wrap qw{ wrap };
  8         34  
  8         556  
29              
30             our $VERSION = '0.052';
31              
32 8     8   67 use constant FORMAT_VALUE => 'Astro::App::Satpass2::FormatValue';
  8         24  
  8         19744  
33              
34             sub new {
35 9     9 1 3923 my ($class, @args) = @_;
36 9         61 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         29 $self->{canned_template} = {};
41              
42 9         58 $self->_new_tt( $self->permissive() );
43              
44 9         43 $self->{default} = {};
45 9         49 $self->{formatter_method} = {};
46              
47 9         64 return $self;
48             }
49              
50             sub _new_tt {
51 10     10   48 my ( $self, $permissive ) = @_;
52              
53 10 50       102 $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         194771 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 10 my ( $self, @arg ) = @_;
74 1 50       5 my $fmtr = HASH_REF eq ref $arg[0] ? $arg[0] : $arg[1];
75 1 50       7 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       7 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         10 $self->{formatter_method}{$fmtr_name} =
88             Astro::App::Satpass2::FormatValue::Formatter->new( $fmtr );
89 1         5 return $self;
90             }
91              
92             sub attribute_names {
93 17     17 1 41 my ( $self ) = @_;
94 17         102 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   73 my ( $self ) = @_;
121 72         101 return ( _uniq( map { keys %{ $_ } } $self->{canned_template},
  72         549  
122 24         148 __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 19 my ( $self, $method, @args ) = @_;
152 7 50       43 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   576 my ( $self, @arg ) = @_;
160 302 50       1333 @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 4834 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       531 );
182              
183 62 100       348 _is_format() and return $data{data};
184              
185             my $tplt = delete $data{template}
186 61 50       260 or $self->wail( 'template argument is required' );
187              
188 61 100       270 my $tplt_name = SCALAR_REF eq ref $tplt ? ${ $tplt } : $tplt;
  1         3  
189              
190 61   33     386 $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         486 };
197              
198 61   33     419 $data{provider} ||= $self->provider();
199              
200 61 50       298 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         353 $data{time} = $self->_wrap(
208             data => { time => time },
209             report => $tplt_name,
210             );
211             }
212              
213 61         468 my $value_formatter = $self->value_formatter();
214              
215             $data{title} = $self->_wrap(
216             default => $data{default},
217 61         346 report => $tplt_name,
218             );
219             $data{TITLE_GRAVITY_BOTTOM} =
220 61         511 $value_formatter->TITLE_GRAVITY_BOTTOM;
221             $data{TITLE_GRAVITY_TOP} =
222 61         260 $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         572 };
228              
229             local $Template::Stash::LIST_OPS->{events} = sub {
230 3     3   72 my @args = @_;
231 3         15 $DB::single = 1;
232 3         21 return $self->_all_events( $args[0] );
233 61         449 };
234              
235             local $Template::Stash::LIST_OPS->{fixed_width} = sub {
236 2     2   8 my ( $list, $value ) = @_;
237 2         6 foreach my $item ( @{ $list } ) {
  2         6  
238 10 50       32 my $code = $item->can( 'fixed_width' )
239             or next;
240 10         26 $code->( $item, $value );
241             }
242 2         11 return;
243 61         384 };
244              
245             local $Template::Stash::LIST_OPS->{first_tle} = sub {
246 29     29   144 my ( $list ) = @_;
247 29         108 $DB::single = 1;
248 29         73 foreach my $item ( @{ $list } ) {
  29         102  
249 29 50       112 embodies( $item->body(), 'Astro::Coord::ECI::TLE' )
250             and return $item;
251             }
252 0         0 return;
253 61         392 };
254              
255             $data{localize} = sub {
256 28     28   79805 return _localize( $tplt_name, @_ );
257 61         368 };
258              
259             # NOTE - must come after $data{localize} because
260             # $data{format_detail} uses $data{localize}
261             $data{format_detail} = sub {
262 173     173   8283 my ( $kind, $evt ) = @_;
263              
264 173 50       575 instance( $evt, FORMAT_VALUE )
265             or return;
266 173 50       686 defined ( my $type = $evt->$kind( width => '' ) )
267             or return;
268 173         595 $type =~ s/ \s+ \z //smx;
269              
270 173         550 foreach my $name ( "$kind:$type", $kind ) {
271 329 100       1147 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         807 );
278              
279 173         560 chomp $output;
280 173         1057 return $output;
281             }
282 0         0 return __localize(
283             text => [ '+template', "$tplt:$kind" ],
284             );
285 61         425 };
286              
287 61         504 my $output = $self->_process( $tplt, %data );
288              
289             # TODO would love to use \h here, but that needs 5.10.
290 60         1365 $output =~ s/ [ \t]+ (?= \n ) //sxmg;
291             $data{title}->title_gravity() eq $data{TITLE_GRAVITY_BOTTOM}
292 60 100       442 and $output =~ s/ \A \n //smx;
293              
294 60         1744 return $output;
295             }
296              
297             sub gmt {
298 57     57 1 206 my ( $self, @args ) = @_;
299 57 100       200 if ( @args ) {
300 3         15 $self->time_formatter()->gmt( @args );
301 3         22 return $self->SUPER::gmt( @args );
302             } else {
303 54         243 return $self->SUPER::gmt();
304             }
305             }
306              
307             sub local_coord {
308 207     207 1 438 my ( $self, @args ) = @_;
309 207 100       545 if ( @args ) {
310 12         43 my $val = $args[0];
311 12 100       116 defined $val
312             or $val = $self->DEFAULT_LOCAL_COORD;
313              
314 12 50       70 defined $self->template( $val )
315             or $self->wail(
316             'Unknown local coordinate specification', $val );
317              
318 12         72 return $self->SUPER::local_coord( @args );
319             } else {
320 195         746 return $self->SUPER::local_coord();
321             }
322             }
323              
324             sub permissive {
325 10     10 1 887 my ( $self, @args ) = @_;
326 10 100       37 if ( @args ) {
327 1 50 25     10 if ( $self->{permissive} xor $args[0] ) {
328 1         9 $self->_new_tt( $args[0] );
329             }
330 1         4 $self->{permissive} = $args[0];
331 1         4 return $self;
332             } else {
333 9         53 return $self->{permissive};
334             }
335             }
336              
337             sub template {
338 620     620 1 1295 my ( $self, $name, @value ) = @_;
339 620 50       1485 defined $name
340             or $self->wail( 'Template name not specified' );
341              
342 620 100       1294 if ( @value ) {
343 3         6 my $tplt_text;
344 3 50 33     731 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         20 $self->{canned_template}{$name} = $value[0];
354             }
355              
356 3         12 return $self;
357             } else {
358             defined $self->{canned_template}{$name}
359 617 100       1542 and return $self->{canned_template}{$name};
360 613         2224 return __localize(
361             text => [ '+template', $name ],
362             );
363             }
364             }
365              
366             sub tz {
367 16     16 1 67 my ( $self, @args ) = @_;
368 16 50       44 if ( @args ) {
369 16         88 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         177 $tf->tz( @args );
376 16         46 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   13 my ( $self, $data ) = @_;
384 3 50       16 ARRAY_REF eq ref $data or return;
385              
386 3         8 my @events;
387 3         10 foreach my $pass ( @{ $data } ) {
  3         12  
388 3         20 push @events, $pass->__raw_events();
389             }
390 3 50       19 @events or return;
391 3         25 @events = sort { $a->{time} <=> $b->{time} } @events;
  16         46  
392              
393 3         12 return [ map { $self->_wrap( data => $_ ) } @events ];
  13         39  
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   72 use constant REPORT_CALLER => __PACKAGE__ . '::format';
  8         21  
  8         6537  
402             sub _is_format {
403 62     62   180 my $level = 2; # Start with caller's caller.
404 62         797 while ( my @info = caller( $level ) ) {
405 261 100       654 REPORT_CALLER eq $info[3]
406             and return $level;
407 260         1691 $level++;
408             }
409 61         338 return;
410             }
411              
412             sub _localize {
413 28     28   90 my ( $report, $source, $default ) = @_;
414 28 50       81 defined $default
415             or $default = $source;
416 28 0       78 defined $report
    50          
417             or return defined $source ? $source : $default;
418              
419 28         123 return scalar __localize(
420             text => [ "-$report", 'string', $source ],
421             default => $source,
422             );
423             }
424              
425             sub _process {
426 449     449   1947 my ( $self, $tplt, %arg ) = @_;
427             ARRAY_REF eq ref $arg{arg}
428             and $arg{arg} = Astro::App::Satpass2::Wrap::Array->new(
429 449 100       2144 $arg{arg} );
430 449         693 my $output;
431 449         919 my $tt = $self->{tt};
432              
433 449         616 my $tplt_text;
434 449 100 100     1668 not ref $tplt
435             and defined( $tplt_text = $self->template( $tplt ) )
436             and $tplt = \$tplt_text;
437              
438 449 100       1861 $tt->process( $tplt, \%arg, \$output )
439             or $self->wail( $tt->error() );
440 448         113106 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   87 my %found;
448 25         82 return ( grep { ! $found{$_}++ } @_ );
  679         1586  
449             }
450              
451             sub _wrap {
452 517     517   1924 my ( $self, %arg ) = @_;
453              
454 517         1068 my $data = $arg{data};
455 517         898 my $default = $arg{default};
456 517         942 my $report = $arg{report};
457              
458 517         1042 my $title = ! $data;
459 517   100     2109 $data ||= {};
460 517   66     1563 $default ||= $self->__default();
461              
462 517 50 66     1604 if ( instance( $data, FORMAT_VALUE ) ) {
    100          
    100          
    50          
463             # Do nothing
464             } elsif ( ! defined $data || HASH_REF eq ref $data ) {
465 449         1808 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   449 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         619 );
489             },
490             list_formatter => sub {
491 20     20   57 my ( $data, @arg ) = @_;
492 20         74 my $body = $data->body();
493 20 100       403 my $list_type = $body ? $body->__list_type() : 'inertial';
494 20         167 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         1526 report => $report,
504             title => $title,
505             warner => $self->warner(),
506             );
507 449         1114 $data->add_formatter_method( values %{ $self->{formatter_method} } );
  449         1952  
508             } elsif ( ARRAY_REF eq ref $data ) {
509 45         116 $data = [ map { $self->_wrap( data => $_, report => $report ) } @{ $data } ];
  85         288  
  45         146  
510             } elsif ( embodies( $data, 'Astro::Coord::ECI' ) ) {
511 23         773 $data = $self->_wrap(
512             data => { body => $data },
513             report => $report,
514             );
515             }
516              
517 517         2442 return $data;
518             }
519              
520             __PACKAGE__->create_attribute_methods();
521              
522             1;
523              
524             __END__