Astro-App-Satpass2
view release on metacpan or search on metacpan
lib/Astro/App/Satpass2/Format/Template.pm view on Meta::CPAN
package Astro::App::Satpass2::Format::Template;
use strict;
use warnings;
use parent qw{ Astro::App::Satpass2::Format };
use Astro::App::Satpass2::Locale qw{ __localize };
# use Astro::App::Satpass2::FormatValue;
use Astro::App::Satpass2::FormatValue::Formatter;
use Astro::App::Satpass2::Utils qw{
instance
ARRAY_REF
HASH_REF
SCALAR_REF
@CARP_NOT
};
use Astro::App::Satpass2::Wrap::Array;
use Astro::App::Satpass2::Format::Template::Provider;
use Astro::Coord::ECI::TLE 0.059 qw{ :constants };
use Astro::Coord::ECI::Utils 0.059 qw{
deg2rad embodies julianday PI rad2deg TWOPI
};
use Clone qw{ };
use POSIX qw{ floor };
use Template;
use Text::Abbrev;
use Text::Wrap qw{ wrap };
our $VERSION = '0.057';
use constant FORMAT_VALUE => 'Astro::App::Satpass2::FormatValue';
sub new {
my ($class, @args) = @_;
my $self = $class->SUPER::new( @args );
# As of 0.020_002 the template definitions are in the
# locale system. The attribute simply holds modifications.
$self->{canned_template} = {};
$self->_new_tt( $self->permissive() );
$self->{default} = {};
$self->{formatter_method} = {};
return $self;
}
sub _new_tt {
my ( $self, $permissive ) = @_;
$self->{tt} = Template->new(
{
LOAD_TEMPLATES => [
Astro::App::Satpass2::Format::Template::Provider->new(
ABSOLUTE => $permissive,
RELATIVE => $permissive,
),
],
}
) or $self->weep(
"Failed to instantate tt: $Template::ERROR" );
return;
}
sub add_formatter_method {
# TODO I want the arguments to be ( $self, $fmtr ), but for the
# moment I have to live with an unreleased version that passed the
# name as the first argument. I will go to the desired signature as
# soon as I get this version installed on my own machine.
my ( $self, @arg ) = @_;
my $fmtr = HASH_REF eq ref $arg[0] ? $arg[0] : $arg[1];
HASH_REF eq ref $fmtr
or $self->wail(
'Formatter definition must be a HASH reference' );
defined( my $fmtr_name = $fmtr->{name} )
or $self->wail(
'Formatter definition must have {name} defined' );
$self->{formatter_method}{$fmtr_name}
and $self->{warner}->wail(
"Formatter method $fmtr_name already exists" );
FORMAT_VALUE->can( $fmtr_name )
and $self->{warner}->wail(
"Formatter $fmtr_name can not override built-in formatter" );
$self->{formatter_method}{$fmtr_name} =
Astro::App::Satpass2::FormatValue::Formatter->new( $fmtr );
return $self;
}
sub attribute_names {
my ( $self ) = @_;
return ( $self->SUPER::attribute_names(),
qw{ permissive },
);
}
sub config {
my ( $self, %args ) = @_;
my @data = $self->SUPER::config( %args );
# TODO support for the {default} key.
foreach my $name (
sort $args{changes} ?
keys %{ $self->{canned_template} } :
$self->__list_templates()
) {
push @data, [ template => $name,
$self->{canned_template}{$name} ];
}
return wantarray ? @data : \@data;
}
# Return the names of all known templates, in no particular order. No
# arguments other than the invocant.
sub __list_templates {
my ( $self ) = @_;
return ( _uniq( map { keys %{ $_ } } $self->{canned_template},
__localize(
text => '+template',
default => {},
) ) );
}
{
my %decoder = (
template => sub {
my ( $self, $method, @args ) = @_;
=begin comment
1 == @args
and return ( @args, $self->$method( @args ) );
$self->$method( @args );
return $self;
=end comment
=cut
1 == @args
or return $self->$method( @args );
return ( @args, $self->$method( @args ) );
},
);
sub decode {
my ( $self, $method, @args ) = @_;
my $dcdr = $decoder{$method}
or return $self->SUPER::decode( $method, @args );
goto $dcdr;
}
}
sub __default {
my ( $self, @arg ) = @_;
@arg or return $self->{default};
my $action = shift @arg;
@arg or return $self->{default}{$action};
my $attrib = shift @arg;
defined $attrib
or return delete $self->{default}{$action};
@arg or return $self->{default}{$action}{$attrib};
my $value = shift @arg;
defined $value
or return delete $self->{default}{$action}{$attrib};
$self->{default}{$action}{$attrib} = $value;
return $value;
}
sub format : method { ## no critic (ProhibitBuiltInHomonyms)
my ( $self, %data ) = @_;
exists $data{data}
and $data{data} = $self->_wrap(
data => $data{data},
report => $data{template},
);
_is_format() and return $data{data};
my $tplt = delete $data{template}
or $self->wail( 'template argument is required' );
my $tplt_name = SCALAR_REF eq ref $tplt ? ${ $tplt } : $tplt;
$data{default} ||= $self->__default();
$data{instantiate} = sub {
my @args = @_;
my $class = Astro::App::Satpass2::Utils::load_package( @args );
return $class->new();
};
$data{provider} ||= $self->provider();
if ( $data{time} ) {
ref $data{time}
or $data{time} = $self->_wrap(
data => { time => $data{time} },
report => $tplt_name,
);
} else {
$data{time} = $self->_wrap(
data => { time => time },
report => $tplt_name,
);
}
my $value_formatter = $self->value_formatter();
lib/Astro/App/Satpass2/Format/Template.pm view on Meta::CPAN
$code->( $item, $value );
}
return;
};
local $Template::Stash::LIST_OPS->{first_tle} = sub {
my ( $list ) = @_;
foreach my $item ( @{ $list } ) {
embodies( $item->body(), 'Astro::Coord::ECI::TLE' )
and return $item;
}
return;
};
$data{localize} = sub {
return _localize( $tplt_name, @_ );
};
# NOTE - must come after $data{localize} because
# $data{format_detail} uses $data{localize}
$data{format_detail} = sub {
my ( $kind, $evt ) = @_;
instance( $evt, FORMAT_VALUE )
or return;
defined ( my $type = $evt->$kind( width => '' ) )
or return;
$type =~ s/ \s+ \z //smx;
foreach my $name ( "$kind:$type", $kind ) {
defined ( my $tplt = $self->template( "$tplt:$name" ) )
or next;
my $output = $self->_process( \$tplt,
evt => $evt,
localize => $data{localize},
sp => $data{sp},
);
chomp $output;
return $output;
}
return __localize(
text => [ '+template', "$tplt:$kind" ],
);
};
my $output = $self->_process( $tplt, %data );
# TODO would love to use \h here, but that needs 5.10.
$output =~ s/ [ \t]+ (?= \n ) //sxmg;
$data{title}->title_gravity() eq $data{TITLE_GRAVITY_BOTTOM}
and $output =~ s/ \A \n //smx;
return $output;
}
sub gmt {
my ( $self, @args ) = @_;
if ( @args ) {
$self->time_formatter()->gmt( @args );
return $self->SUPER::gmt( @args );
} else {
return $self->SUPER::gmt();
}
}
sub local_coord {
my ( $self, @args ) = @_;
if ( @args ) {
my $val = $args[0];
defined $val
or $val = $self->DEFAULT_LOCAL_COORD;
defined $self->template( $val )
or $self->wail(
'Unknown local coordinate specification', $val );
return $self->SUPER::local_coord( @args );
} else {
return $self->SUPER::local_coord();
}
}
sub permissive {
my ( $self, @args ) = @_;
if ( @args ) {
if ( $self->{permissive} xor $args[0] ) {
$self->_new_tt( $args[0] );
}
$self->{permissive} = $args[0];
return $self;
} else {
return $self->{permissive};
}
}
sub template {
my ( $self, $name, @value ) = @_;
defined $name
or $self->wail( 'Template name not specified' );
if ( @value ) {
my $tplt_text;
if ( ! defined $value[0]
|| defined( $tplt_text = __localize(
text => '+template',
default => $value[0] )
)
&& $value[0] eq $tplt_text
) {
delete $self->{canned_template}{$name};
} else {
$self->{canned_template}{$name} = $value[0];
}
return $self;
} else {
defined $self->{canned_template}{$name}
and return $self->{canned_template}{$name};
return __localize(
text => [ '+template', $name ],
);
}
}
sub tz {
my ( $self, @args ) = @_;
if ( @args ) {
my $tf = $self->time_formatter();
# We go through the following because the time formatter may
# modify the zone (e.g. if it's using DateTime, zones are
# case-sensitive so we may have done case conversion before
# storing). We want this object to have the time formatter's
# version of the zone.
$tf->tz( @args );
return $self->SUPER::tz( $tf->tz() );
} else {
return $self->SUPER::tz();
}
}
sub _all_events {
my ( $self, $data ) = @_;
ARRAY_REF eq ref $data or return;
my @events;
foreach my $pass ( @{ $data } ) {
push @events, $pass->__raw_events();
}
@events or return;
@events = sort { $a->{time} <=> $b->{time} } @events;
return [ map { $self->_wrap( data => $_ ) } @events ];
}
# _is_format()
#
# Returns true if the format() method is above us on the call
# stack, otherwise returns false.
use constant REPORT_CALLER => __PACKAGE__ . '::format';
sub _is_format {
my $level = 2; # Start with caller's caller.
while ( my @info = caller( $level ) ) {
REPORT_CALLER eq $info[3]
and return $level;
$level++;
}
return;
}
sub _localize {
my ( $report, $source, $default ) = @_;
defined $default
or $default = $source;
defined $report
or return defined $source ? $source : $default;
return scalar __localize(
text => [ "-$report", 'string', $source ],
default => $source,
);
}
sub _process {
my ( $self, $tplt, %arg ) = @_;
ARRAY_REF eq ref $arg{arg}
and $arg{arg} = Astro::App::Satpass2::Wrap::Array->new(
$arg{arg} );
my $output;
my $tt = $self->{tt};
my $tplt_text;
not ref $tplt
and defined( $tplt_text = $self->template( $tplt ) )
and $tplt = \$tplt_text;
$tt->process( $tplt, \%arg, \$output )
lib/Astro/App/Satpass2/Format/Template.pm view on Meta::CPAN
=head3 pass_events
This template is used by the C<pass()> method if the C<-events> option
is specified. It orders events chronologically without respect to their
source.
=head3 pass_ics
This template is used by the C<pass()> method if the C<-ics> option
is specified. It formats passes as iCal entries.
=head3 phase
This template is used by the C<phase()> method.
=head3 position
This template is used by the C<position()> method.
=head3 tle
This template is used by the C<tle()> method, unless C<-verbose> is
specified. Note that the default template does not generate a trailing
newline, since the result of the body's C<tle()> method is assumed to
provide this.
=head3 tle_verbose
This template is used by the C<tle()> method if C<-verbose> is
specified. It is assumed to provide some sort of formatted version of
the TLE.
=head2 Other Methods
The following other methods are provided.
=head2 decode
$fmt->decode( format_effector => 'azimuth' );
This method overrides the L<Astro::App::Satpass2::Format
decode()|Astro::App::Satpass2::Format/decode> method. In addition to
the functionality provided by the parent, the following methods return
something different when invoked via this method:
=over
=item format_effector
If called as an accessor, the name of the formatter accessed is
prepended to the returned array. If this leaves the returned array with
just one entry, the string C<'undef'> is appended. The return is still
an array in list context, and an array reference in scalar context.
If called as a mutator, you still get back the object reference.
=back
If a subclass overrides this method, the override should either perform
the decoding itself, or delegate to C<SUPER::decode>.
=head3 format
$fmt->format( template => $template, ... );
This method represents the interface to L<Template-Toolkit|Template>,
and all the formatter methods come through here eventually.
The arguments to this method are name/value pairs. The C<template>
argument is required, and is either the name of a template file, or a
reference to a string containing the template. All other arguments are
passed to C<Template-Toolkit> as variables. If argument C<arg> is
specified and its value is an array reference, the value is enclosed in
an
L<Astro::App::Satpass2::Wrap::Array|Astro::App::Satpass2::Wrap::Array>
object, since by convention this is the argument passed back to
L<Astro::App::Satpass2|Astro::App::Satpass2> methods.
In addition to any variables passed in, the following array methods are
defined for C<Template-Toolkit> before it is invoked:
=over
=item events
If called on an array of passes, returns all events in all passes, in
chronological order.
=item fixed_width
If called on an array of
L<Astro::App::Satpass2::FormatValue|Astro::App::Satpass2::FormatValue>
objects, calls
L<fixed_width()|Astro::App::Satpass2::FormatValue/fixed_width> on them.
You may specify an argument to C<fixed_width()>.
Nothing is returned.
=back
The canned templates can also be run as reports, and in fact will be
taken in preference to files of the same name. If you do this, you will
need to pass the relevant L<Astro::App::Satpass2|Astro::App::Satpass2>
object as the C<sp> argument, since by convention the canned templates
all look to that variable to compute their data if they do not already
have a C<data> variable.
=head3 template
print "Template 'almanac' is :\n", $fmt->template( 'almanac' );
$fmt->template( almanac => <<'EOD' );
[% UNLESS data %]
[%- SET data = sp.almanac( arg ) %]
[%- END %]
[%- FOREACH item IN data %]
[%- item.date %] [% item.time %]
[%= item.almanac( units = 'description' ) %]
[% END -%]
EOD
( run in 2.759 seconds using v1.01-cache-2.11-cpan-5735350b133 )