File Coverage

blib/lib/Astro/App/Satpass2/Format.pm
Criterion Covered Total %
statement 103 106 97.1
branch 41 58 70.6
condition 10 15 66.6
subroutine 19 20 95.0
pod 9 9 100.0
total 182 208 87.5


line stmt bran cond sub pod time code
1             package Astro::App::Satpass2::Format;
2              
3 10     10   6934 use strict;
  10         25  
  10         308  
4 10     10   51 use warnings;
  10         21  
  10         334  
5              
6 10     10   54 use parent qw{ Astro::App::Satpass2::Copier };
  10         23  
  10         91  
7              
8 10     10   649 use Clone ();
  10         30  
  10         209  
9 10     10   53 use Astro::App::Satpass2::FormatTime;
  10         31  
  10         415  
10 10         1683 use Astro::App::Satpass2::Utils qw{
11             instance
12             load_package __parse_class_and_args
13             CODE_REF
14             @CARP_NOT
15 10     10   78 };
  10         25  
16 10     10   83 use Scalar::Util 1.26 qw{ weaken };
  10         284  
  10         751  
17              
18             our $VERSION = '0.051';
19              
20 10     10   73 use constant DEFAULT_LOCAL_COORD => 'azel_rng';
  10         26  
  10         7837  
21              
22             # Note that the fact that new() works when called from
23             # My::Module::Test::App is unsupported and undocumented, and
24             # the functionality may be revoked or changed without warning.
25              
26             my %static = (
27             desired_equinox_dynamical => 0,
28             gmt => 0,
29             local_coord => DEFAULT_LOCAL_COORD,
30             provider => 'Astro::App::Satpass2',
31             value_formatter => 'Astro::App::Satpass2::FormatValue',
32             );
33              
34             sub new {
35 12     12 1 52 my ( $class, %args ) = @_;
36 12 50       56 ref $class and $class = ref $class;
37              
38 12         103 my $self = { %static };
39 12         48 bless $self, $class;
40              
41 12         77 $self->warner( delete $args{warner} );
42              
43 12 50 66     67 $class eq __PACKAGE__
44             and 'My::Module::Test::App' ne caller
45             and $self->wail( __PACKAGE__,
46             ' may not be instantiated. Use a subclass' );
47              
48 12         41 $self->{parent} = delete $args{parent};
49              
50             # FIXME the below is verbatim from
51             # Astro::App::Satpass2::Macro->init(), ca. line 63.
52             defined $self->{parent}
53 12 50       43 or $self->wail( q{Attribute 'parent' is required} );
54 12 50       76 instance( $self->{parent}, 'Astro::App::Satpass2' )
55             or $self->wail( q{Attribute 'parent' must be an Astro::App::Satpass2} );
56 12         84 weaken( $self->{parent} );
57              
58 12 50       101 exists $args{tz} or $args{tz} = $ENV{TZ};
59              
60 12         59 $self->time_formatter( delete $args{time_formatter} );
61 12         41 $self->time_formatter()->warner( $self->warner() );
62              
63             $args{date_format}
64 12 50       92 or $self->date_format( $self->time_formatter()->DATE_FORMAT() );
65             $args{time_format}
66 12 50       70 or $self->time_format( $self->time_formatter()->TIME_FORMAT() );
67             exists $args{round_time}
68 12 50       102 or $self->round_time( $self->time_formatter()->ROUND_TIME() );
69              
70             $self->value_formatter( delete $args{value_formatter} ||
71 12   33     104 $static{value_formatter} );
72 12         70 $self->value_formatter()->warner( $self->warner() );
73              
74 12         117 $self->init( %args );
75              
76 12         53 return $self;
77             }
78              
79             sub round_time {
80 463     463 1 1013 my ( $self, @arg ) = @_;
81 463 100       1068 if ( @arg ) {
82 12         40 $self->time_formatter()->round_time( @arg );
83 12         35 $self->{round_time} = $arg[0];
84 12         28 return $self;
85             } else {
86 451         1034 return $self->time_formatter()->round_time();
87             }
88             }
89              
90             sub attribute_names {
91 32     32 1 89 my ( $self ) = @_;
92 32         177 return ( $self->SUPER::attribute_names(),
93             qw{ date_format desired_equinox_dynamical gmt
94             local_coord parent provider round_time
95             time_format time_formatter tz
96             value_formatter
97             } );
98             }
99              
100             {
101              
102             my %original_value = (
103             date_format => sub {
104             return $_[0]->time_formatter()->DATE_FORMAT()
105             },
106             round_time => sub {
107             return $_[0]->time_formatter()->ROUND_TIME()
108             },
109             time_format => sub {
110             return $_[0]->time_formatter()->TIME_FORMAT()
111             },
112             );
113              
114             foreach my $key ( keys %static ) {
115             $original_value{ $key } ||= sub {
116             return $static{$key};
117             };
118             }
119              
120             my %not_part_of_config = map { $_ => 1 } qw{ parent warner };
121              
122             sub config {
123 2     2 1 10 my ( $self, %args ) = @_;
124 2         4 my @data;
125              
126 2         8 foreach my $name ( $self->attribute_names() ) {
127              
128 24 100       55 $not_part_of_config{$name}
129             and next;
130              
131 20         51 my $val = $self->$name();
132             $args{decode}
133 20 100 66     74 and ref $val
134             and $val = $self->decode( $name );
135              
136 10     10   79 no warnings qw{ uninitialized };
  10         22  
  10         6785  
137              
138             next if $args{changes} &&
139             $val eq ( $original_value{$name} ?
140 20 100 100     67 $original_value{$name}->( $self, $name ) :
    100          
141             undef );
142              
143 14         37 push @data, [ $name, $val ];
144             }
145              
146 2 50       11 return wantarray ? @data : \@data;
147             }
148              
149             }
150              
151             {
152              
153             my %decoder = (
154             desired_equinox_dynamical => sub {
155             my ( $self, $method, @args ) = @_;
156             my $rslt = $self->$method( @args );
157             @args and return $rslt;
158             $rslt or return $rslt;
159             return $self->{time_formatter}->format_datetime(
160             $self->{time_formatter}->ISO_8601_FORMAT(),
161             $rslt, 1 );
162             },
163             time_formatter => sub {
164             my ( $self, $method, @args ) = @_;
165             my $rslt = $self->$method( @args );
166             @args and return $rslt;
167             # return ref $rslt || $rslt;
168             return $rslt->class_name_of_record();
169             },
170             );
171             $decoder{value_formatter} = $decoder{time_formatter};
172              
173             sub decode {
174 11     11 1 28 my ( $self, $method, @args ) = @_;
175 11 100       48 my $dcdr = $decoder{$method}
176             or return $self->$method( @args );
177 4 50       12 my $type = ref $dcdr
178             or $self->weep( "Decoder for $method is scalar" );
179 4 50       11 CODE_REF eq $type
180             or $self->weep(
181             "Decoder for $method is $type reference" );
182 4         10 return $dcdr->( $self, $method, @args );
183             }
184             }
185              
186             sub format : method { ## no critic (ProhibitBuiltInHomonyms,RequireFinalReturn)
187 0     0 1 0 my ( $self ) = @_;
188             # ->weep() throws an exception.
189 0         0 $self->weep(
190             'The format() method must be overridden' );
191             }
192              
193             sub local_coord {
194 210     210 1 467 my ( $self, @args ) = @_;
195 210 100       504 if ( @args ) {
196 12 100       65 defined $args[0] or $args[0] = DEFAULT_LOCAL_COORD;
197 12         39 $self->{local_coord} = $args[0];
198 12         45 return $self;
199             } else {
200 198         1067 return $self->{local_coord};
201             }
202             }
203              
204             foreach my $attribute (
205             [ time_formatter => 'Astro::App::Satpass2::FormatTime',
206             'Astro::App::Satpass2::FormatTime', sub {
207             my ( $self, $old, $new ) = @_;
208             if ( $old->FORMAT_TYPE() ne $new->FORMAT_TYPE() ) {
209             $self->date_format( $new->DATE_FORMAT() );
210             $self->time_format( $new->TIME_FORMAT() );
211             }
212             return;
213             },
214             ],
215             [ value_formatter => 'Astro::App::Satpass2::FormatValue',
216             'Astro::App::Satpass2', sub {} ],
217             ) {
218             my ( $name, $class, $prefix, $pre_set ) = @{ $attribute };
219             __PACKAGE__->can( $name )
220             and next;
221 10     10   114 no strict qw{ refs };
  10         39  
  10         5252  
222             *$name = sub {
223 1548     1548   2821 my ( $self, @args ) = @_;
224 1548 100       2818 if ( @args ) {
225 24         74 my $fmtr = shift @args;
226 24 100 66     154 defined $fmtr and $fmtr ne ''
227             or $fmtr = $class;
228 24         67 my $old = $self->{$name};
229 24 50       93 ref $fmtr or do {
230 24         149 my ( $pkg, @fmtr_arg ) = (
231             $self->__parse_class_and_args( $fmtr ), @args );
232 24 50       123 my $fatal = $self->parent()->get( 'error_out' ) ?
233             'wail' : 'whinge';
234 24 50       151 my $class = $self->load_package(
235             { fatal => $fatal }, $pkg, $prefix )
236             or return $self;
237 24         123 $fmtr = $class->new(
238             warner => scalar $self->warner(),
239             @fmtr_arg,
240             );
241 24 50       92 ref $old
242             and $old->copy( $fmtr, @fmtr_arg );
243             };
244 24 50       94 ref $old
245             and $pre_set->( $self, $old, $fmtr );
246 24         95 $self->{$name} = $fmtr;
247 24         57 return $self;
248             } else {
249 1524         9049 return $self->{$name};
250             }
251             };
252             }
253              
254             sub tz {
255 24     24 1 101 my ( $self, @args ) = @_;
256 24 100       114 if ( @args ) {
257 20         52 $self->{tz} = $args[0];
258 20         73 return $self;
259             } else {
260 4         11 return $self->{tz};
261             }
262             }
263              
264             sub warner {
265 510     510 1 1243 my ( $self, @args ) = @_;
266 510 100       1334 if ( @args ) {
267 12         25 my $warner = $args[0];
268 12 50       51 if ( my $fmtr = $self->time_formatter() ) {
269 0         0 $fmtr->warner( $warner );
270             }
271             }
272 510         1811 return $self->SUPER::warner( @args );
273             }
274              
275             __PACKAGE__->create_attribute_methods();
276              
277             1;
278              
279             =head1 NAME
280              
281             Astro::App::Satpass2::Format - Format Astro::App::Satpass2 output
282              
283             =head1 SYNOPSIS
284              
285             No user-serviceable parts inside.
286              
287             =head1 DETAILS
288              
289             This formatter is an abstract class providing output formatting
290             functionality for L. It
291             should not be instantiated directly.
292              
293             This class is a subclass of
294             L.
295              
296             =head1 METHODS
297              
298             This class supports the following public methods:
299              
300             =head2 Instantiator
301              
302             =head3 new
303              
304             $fmt = Astro::Satpass::Format::Some_Subclass_Thereof->new(...);
305              
306             This method instantiates a formatter. It may not be called on this
307             class, but may be called on a subclass. If you wish to modify the
308             default attribute values you can pass the relevant name/value pairs as
309             arguments. For example:
310              
311             $fmt = Astro::Satpass::Format::Some_Subclass_Thereof->new(
312             date_format => '%Y%m%d',
313             time_format => 'T%H:%M:%S',
314             );
315              
316             =head2 Accessors and Mutators
317              
318             =head3 date_format
319              
320             print 'Date format: ', $fmt->date_format(), "\n";
321             $fmt->date_format( '%d-%b-%Y' );
322              
323             The C attribute is maintained on behalf of subclasses of
324             this class, which B (but need not) use it to format dates. This
325             method B be overridden by subclasses, but the override B call
326             C, and return values consistent with the following
327             description.
328              
329             This method acts as both accessor and mutator for the C
330             attribute. Without arguments it is an accessor, returning the current
331             value of the C attribute.
332              
333             If passed an argument, that argument becomes the new value of
334             C, and the object itself is returned so that calls may be
335             chained.
336              
337             The interpretation of the argument is up to the subclass, but
338             it is recommended for sanity's sake that the subclasses interpret this
339             value as a C format producing a date (but not a time),
340             if they use this attribute at all.
341              
342             The default value, if used by the subclass at all, should produce a
343             numeric date of the form year-month-day. For formatters that use
344             C, this will be '%Y-%m-%d'.
345              
346             B that this value will be reset to its default if the
347             L attribute is modified and the new
348             object has a different C than the old one.
349              
350             =head3 desired_equinox_dynamical
351              
352             print 'Desired equinox: ',
353             strftime( '%d-%b-%Y %H:%M:%S dynamical',
354             gmtime $fmt->desired_equinox_dynamical() ),
355             "\n";
356             $fmt->desired_equinox_dynamical(
357             timegm( 0, 0, 12, 1, 0, 100 ) ); # J2000.0
358              
359             The C attribute is maintained on behalf of
360             subclasses of this class, which B (but need not) use it to
361             calculate inertial coordinates. If the subclass does not make use of
362             this attribute it B document the fact.
363              
364             This method B be overridden by subclasses, but the override B
365             call C, and return values consistent
366             with the following description.
367              
368             This method acts as both accessor and mutator for the
369             C attribute. Without arguments it is an
370             accessor, returning the current value of the
371             C attribute.
372              
373             If passed an argument, that argument becomes the new value of
374             C, and the object itself is returned so that
375             calls may be chained.
376              
377             The interpretation of the argument is up to the subclass, but it is
378             recommended for sanity's sake that the subclasses interpret this value
379             as a dynamical time (even though it is represented as a normal Perl
380             time) if they use this attribute at all. If the value is true (in the
381             Perl sense) inertial coordinates should be precessed to the dynamical
382             time represented by this attribute. If the value is false (in the Perl
383             sense) they should not be precessed.
384              
385             =head3 gmt
386              
387             print 'Time zone: ', ( $fmt->gmt() ? 'GMT' : 'local' ), "\n";
388             $fmt->gmt( 1 );
389              
390             The C attribute is maintained on behalf of subclasses of this
391             class, which B (but need not) use it to decide whether to display
392             dates in GMT or in the local time zone. This method B be overridden
393             by subclasses, but the override B call C, and return
394             values consistent with the following description.
395              
396             This method acts as both accessor and mutator for the C
397             attribute. Without arguments it is an accessor, returning the current
398             value of the C attribute. This value is to be interpreted as a
399             Boolean under the usual Perl rules.
400              
401             If passed an argument, that argument becomes the new value of
402             C, and the object itself is returned so that calls may be
403             chained.
404              
405             =head3 local_coord
406              
407             print 'Local coord: ', $fmt->local_coord(), "\n";
408             $fmt->local_coord( 'azel_rng' );
409              
410             The C attribute is maintained on behalf of subclasses of
411             this class, which B (but need not) use it to determine what
412             coordinates to display. This method B be overridden by subclasses,
413             but the override B call C, and return values
414             consistent with the following description.
415              
416             This method acts as both accessor and mutator for the C
417             attribute. Without arguments it is an accessor, returning the current
418             value of the C attribute.
419              
420             If passed an argument, that argument becomes the new value of
421             C, and the object itself is returned so that calls may be
422             chained. The interpretation of the argument is up to the subclass, but
423             it is recommended for sanity's sake that the subclasses support at least
424             the following values if they use this attribute at all:
425              
426             az_rng --------- azimuth and range;
427             azel ----------- azimuth and elevation;
428             azel_rng ------- azimuth, elevation and range;
429             equatorial ----- right ascension and declination;
430             equatorial_rng - right ascension, declination and range.
431              
432             It is further recommended that C be the default.
433              
434             =head3 provider
435              
436             print 'Provider: ', $fmt->provider(), "\n";
437             $fmt->provider( 'Astro::App::Satpass2 v' . Astro::App::Satpass2->VERSION() );
438              
439             The C attribute is maintained on behalf of subclasses of this
440             class, which B (but need not) use it to identify the provider of
441             the data for informational purposes. This method B be overridden by
442             subclasses, but the override B call C, and return
443             values consistent with the following description.
444              
445             This method acts as both accessor and mutator for the C
446             attribute. Without arguments it is an accessor, returning the current
447             value of the C attribute.
448              
449             If passed an argument, that argument becomes the new value of
450             C, and the object itself is returned so that calls may be
451             chained.
452              
453             =head3 round_time
454              
455             print 'Time rounded to: ', $fmt->round_time(), " seconds\n";
456             $fmt->round_time( 60 );
457              
458             The C attribute is maintained on behalf of subclasses of
459             this class, which B (but need not) use it to format times. This
460             method B be overridden by subclasses, but the override B call
461             C, and return values consistent with the following
462             description.
463              
464             This method acts as both accessor and mutator for the C
465             attribute. Without arguments it is an accessor, returning the current
466             value of the C attribute.
467              
468             If passed an argument, that argument becomes the new value of
469             C, and the object itself is returned so that calls may be
470             chained.
471              
472             The interpretation of the argument is up to the subclass, but
473             it is recommended for sanity's sake that the subclasses interpret this
474             value in the same way as
475             L
476             if they use this attribute at all.
477              
478             =head3 time_format
479              
480             print 'Time format: ', $fmt->time_format(), "\n";
481             $fmt->time_format( '%H:%M:%S' );
482              
483             The C attribute is maintained on behalf of subclasses of
484             this class, which B (but need not) use it to format times. This
485             method B be overridden by subclasses, but the override B call
486             C, and return values consistent with the following
487             description.
488              
489             This method acts as both accessor and mutator for the C
490             attribute. Without arguments it is an accessor, returning the current
491             value of the C attribute.
492              
493             If passed an argument, that argument becomes the new value of
494             C, and the object itself is returned so that calls may be
495             chained.
496              
497             The interpretation of the argument is up to the subclass, but
498             it is recommended for sanity's sake that the subclasses interpret this
499             value as a C format producing a time (but not a date),
500             if they use this attribute at all.
501              
502             The default value, if used by the subclass at all, should produce a
503             numeric time of the form hour:minute:second. For formatters that use
504             C, this will be '%H:%M:%S'.
505              
506             B that this value will be reset to its default if the
507             L attribute is modified and the new
508             object has a different C than the old one.
509              
510             =head3 time_formatter
511              
512             This method acts as both accessor and mutator for the object used to
513             format times. It will probably be a
514             L
515             object of some sort, and will certainly conform to that interface. When
516             setting the value, you can specify either a class name or an object. If
517             a class name, the leading C can be
518             omitted.
519              
520             B that setting this will reset the L and
521             L attributes to values appropriate to the
522             new time formatter's class, if the new formatter object has a different
523             C than the old one.
524              
525             =head3 tz
526              
527             print 'Time zone: ', $fmt->tz()->name(), "\n";
528             $fmt->tz( 'MST7MDT' );
529              
530             The C attribute is maintained on behalf of subclasses of this class,
531             which B (but need not) use it to format times. This method B
532             be overridden by subclasses, but the override B call C,
533             and return values consistent with the following description.
534              
535             This method acts as both accessor and mutator for the C attribute.
536             Without arguments it is an accessor, returning the current value of the
537             C attribute.
538              
539             If passed an argument, that argument becomes the new value of C, and
540             the object itself is returned so that calls may be chained.
541              
542             The use of the argument is up to the subclass, but it is
543             recommended for sanity's sake that the subclasses interpret this value
544             as a time zone to be used to derive the local time if they use this
545             attribute at all.
546              
547             A complication is that subclasses may need to validate zone values. It
548             is to be hoped that their digestions will be rugged enough to handle the
549             usual conventions, since convention rather than standard seems to rule
550             here.
551              
552             =head3 value_formatter
553              
554             This method acts as both accessor and mutator for the object used to
555             format values. It will probably be a
556             L
557             object of some sort, and will certainly conform to that interface. When
558             setting the value, you can specify either a class name or an object. If
559             a class name, the leading C can be omitted.
560              
561             Author's note:
562              
563             This method is B. Documentation is for the
564             benefit of the author and the curious. I wanted to screw around with
565             extra formatters, and the best way to do that seemed to be to subclass
566             C, but then I needed a way to put the
567             subclass into use. But I am not really ready to document the necessary
568             interface (and therefore commit to not changing it, or at least not
569             doing so without going through a deprecation cycle). If you have a need
570             for this kind of thing, please contact me.
571              
572             =head2 Formatters
573              
574             There is actually only one formatter method. The subclass B
575             provide it, because this class does not.
576              
577             =head3 format
578              
579             print $fmt->format( template => $name, data => $data );
580              
581             This method takes named arguments.
582              
583             The only required argument is C