Astro-App-Satpass2

 view release on metacpan or  search on metacpan

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

	my $opt = _attr_hash( $name, $data, qw{ expand_tilde|expand-tilde! } );
	exists $opt->{expand_tilde}
	    or $opt->{expand_tilde} = 1;
	$attr{$code}{$name} = $opt;
	return;
    }

    sub Tweak : ATTR(CODE,RAWDATA) {
	my ( undef, undef, $code, $name, $data ) = @_;
	$attr{$code}{$name} = _attr_hash( $name, $data,
	    qw{ completion=s unsatisfied! } );
	return;
    }

    sub Verb : ATTR(CODE,RAWDATA) {
	my ( undef, undef, $code, $name, $data ) = @_;
	$attr{$code}{$name} = _attr_list( $data );
	return;
    }

    sub _attr_hash {
	my ( $name, $arg, @legal ) = @_;
	my $gol = Getopt::Long::Parser->new();
	my %opt;
	$gol->getoptionsfromarray(
	    _attr_list( $arg ),
	    \%opt,
	    @legal,
	) or do {
	    require Carp;
	    Carp::croak( "Bad $name option" );
	};
	return \%opt;
    }

    sub _attr_list {
	defined( local $_ = $_[0] )
	    or return [];
	s/ \A \s+ //smx;
	return [ split qr< \s+ >smx ];
    }

    sub __get_attr {
	my ( undef, $code, $name, $dflt ) = @_;	# $pkg unused
	defined $code
	    or return \%attr;
	defined $name
	    or return $attr{$code};
	exists $attr{$code}{$name}
	    and return $attr{$code}{$name};
	return $dflt;
    }
}

my %mutator = (
    almanac_horizon	=> \&_set_almanac_horizon,
    appulse => \&_set_angle,
    autoheight => \&_set_unmodified,
    backdate => \&_set_unmodified,
    background => \&_set_unmodified,
    continuation_prompt => \&_set_unmodified,
    country => \&_set_unmodified,
    date_format => \&_set_formatter_attribute,
    desired_equinox_dynamical => \&_set_formatter_attribute,
    debug => \&_set_unmodified,
    echo => \&_set_unmodified,
    edge_of_earths_shadow => \&_set_unmodified,
    ellipsoid => \&_set_ellipsoid,
    error_out => \&_set_unmodified,
    events	=> \&_set_unmodified,
    exact_event => \&_set_unmodified,
    execute_filter => \&_set_code_ref,	# Undocumented and unsupported
    explicit_macro_delete => \&_set_unmodified,
    extinction => \&_set_unmodified,
    filter => \&_set_unmodified,
    flare_mag_day => \&_set_unmodified,
    flare_mag_night => \&_set_unmodified,
    formatter => \&_set_formatter,
    geocoder => \&_set_geocoder,
    geometric => \&_set_unmodified,
    gmt => \&_set_formatter_attribute,
    height => \&_set_distance_meters,
    horizon => \&_set_angle,
    illum	=> \&_set_illum_class,
    latitude => \&_set_angle_or_undef,
    local_coord => \&_set_formatter_attribute,
    location => \&_set_unmodified,
    longitude => \&_set_angle_or_undef,
    model => \&_set_model,
    max_mirror_angle => \&_set_angle,
    output_layers	=> \&_set_output_layers,
    pass_threshold => \&_set_angle_or_undef,
    pass_variant	=> \&_set_pass_variant,
    perltime => \&_set_time_parser_attribute,
    prompt => \&_set_unmodified,
    refraction	=> \&_set_unmodified,
    simbad_url => \&_set_unmodified,
    singleton => \&_set_unmodified,
    spacetrack => \&_set_spacetrack,
    stdout => \&_set_stdout,
    sun	=> \&_set_sun_class,		# Only in {level1}
    time_format => \&_set_formatter_attribute,
    time_formatter => \&_set_formatter_attribute,
    time_parser => \&_set_time_parser,
##    timing => \&_set_unmodified,
    twilight => \&_set_twilight,  # 'civil', 'nautical', 'astronomical'
				# (or a unique abbreviation thereof),
				# or degrees above (positive) or below
				# (negative) the geometric horizon.
    tz => \&_set_tz,
    verbose => \&_set_unmodified, # 0 = events only
				# 1 = whenever above horizon
				# 2 = anytime
    visible => \&_set_unmodified, # 1 = only if sun down & sat illuminated
    warning => \&_set_warner_attribute,	# True to warn/die; false to carp/croak.
    warn_on_empty => \&_set_unmodified,
    				# True to have list commands warn on
				# an empty list.
    webcmd => \&_set_webcmd,	# Command to spawn for web pages
);

my %accessor = (
    date_format => \&_get_formatter_attribute,
    desired_equinox_dynamical => \&_get_formatter_attribute,
    geocoder => \&_get_geocoder,
    gmt => \&_get_formatter_attribute,
    local_coord => \&_get_formatter_attribute,
    perltime => \&_get_time_parser_attribute,
    spacetrack => \&_get_spacetrack,
    time_format => \&_get_formatter_attribute,
    time_formatter	=> \&_get_formatter_attribute,
    tz => \&_get_time_parser_attribute,
    warning => \&_get_warner_attribute,
);

foreach ( keys %mutator, qw{ initfile } ) {
    $accessor{$_} ||= sub { return $_[0]->{$_[1]} };
}

my %shower = (
    date_format => \&_show_formatter_attribute,
    desired_equinox_dynamical => \&_show_formatter_attribute,
    formatter	=> \&_show_copyable,
    geocoder	=> \&_show_copyable,
    gmt => \&_show_formatter_attribute,
    local_coord => \&_show_formatter_attribute,
    pass_variant	=> \&_show_pass_variant,
    sun		=> \&_show_sun_class,	# only in {level1}
    time_parser => \&_show_time_parser,
    time_format => \&_show_formatter_attribute,
    time_formatter	=> \&_show_formatter_attribute,
);
foreach ( keys %accessor ) { $shower{$_} ||= \&_show_unmodified }

#	Attributes which must be set programmatically (i.e. not
#	interactively or in the initialization file).

my %nointeractive = map {$_ => 1} qw{
    execute_filter
    spacetrack
    stdout
};

#	Initial object contents

my %static = (
    almanac_horizon	=> 0,
    appulse => 0,
    autoheight => 1,
    background => 1,
    backdate => 0,
    continuation_prompt => '> ',
    date_format => '%a %d-%b-%Y',
    debug => 0,
    echo => 0,
    edge_of_earths_shadow => 1,
    ellipsoid => Astro::Coord::ECI->get ('ellipsoid'),
    error_out => 0,
    events	=> 0,
    exact_event => 1,
    execute_filter => sub { return 1 },	# Undocumented and unsupported
##  explicit_macro_delete => 1,			# Deprecated
    extinction => 1,
    filter => 0,
    flare_mag_day => -6,
    flare_mag_night => 0,
    formatter => 'Astro::App::Satpass2::Format::Template',	# Formatter class.
##  geocoder => $default_geocoder->(),	# Geocoder class set when accessed
    geometric => 1,
    height => undef,		# meters
#   initfile => undef,		# Set by init()
    horizon => 20,		# degrees
    illum	=> SUN_CLASS_DEFAULT,
    latitude => undef,		# degrees
    longitude => undef,		# degrees
    max_mirror_angle => HAVE_TLE_IRIDIUM ? rad2deg(
	Astro::Coord::ECI::TLE::Iridium->DEFAULT_MAX_MIRROR_ANGLE ) :
	undef,
    model => 'model',
#   pending => undef,		# Continued input line if it exists.
    pass_variant	=> PASS_VARIANT_NONE,
    perltime => 0,
    prompt => 'satpass2> ',
    refraction	=> 1,
    simbad_url => 'simbad.u-strasbg.fr',
    singleton => 0,
#   spacetrack => undef,	# Astro::SpaceTrack object set when accessed
#   stdout => undef,		# Set to stdout in new().
    output_layers	=> DEFAULT_STDOUT_LAYERS,
    time_parser => 'Astro::App::Satpass2::ParseTime',	# Time parser class.
    twilight => 'civil',
    tz => $ENV{TZ},
    verbose => 0,
    visible => 1,
    warning => 0,
    warn_on_empty => 1,
    webcmd => ''
);

my %sky_class = (
    fold_case( 'Sun' ) => [ SUN_CLASS_DEFAULT, name => 'Sun' ],
    fold_case( 'Moon' ) => [ 'Astro::Coord::ECI::Moon', name => 'Moon' ],
#    # The shape of things to come -- maybe
#    # but commented out because Astro-App-Satpass2 does not depend on
#    # these
#    ( map { fold_case( $_ ) =>
#	"Astro::Coord::ECI::VSOP87D::$_" } qw{ Mercury Venus
#	Mars Jupiter Saturn Uranus Neptune } ),
);

sub new {
    my ( $class, %args ) = @_;
    ref $class and $class = ref $class;
    my $self = {};
    $self->{bodies} = [];
    $self->{macro} = {};
    $self->{sky} = [
	SUN_CLASS_DEFAULT->new (),
	Astro::Coord::ECI::Moon->new (),
    ];
    $self->{sky_class} = { %sky_class };
    $self->{_help_module} = {
	''	=> __PACKAGE__,
	eci => 'Astro::Coord::ECI',
	moon => 'Astro::Coord::ECI::Moon',
	set => 'Astro::Coord::ECI::TLE::Set',
	sun => SUN_CLASS_DEFAULT,
	spacetrack => 'Astro::SpaceTrack',
	star => 'Astro::Coord::ECI::Star',
	tle => 'Astro::Coord::ECI::TLE',
	utils => 'Astro::Coord::ECI::Utils',
    };
    HAVE_TLE_IRIDIUM
	and $self->{_help_module}{iridium} = 'Astro::Coord::ECI::TLE::Iridium';
    bless $self, $class;
    $self->_frame_push(initial => []);
    $self->set(stdout => select());

    foreach my $name ( keys %static ) {
	exists $args{$name} or $args{$name} = $static{$name};
    }

    $self->{_warner} = Astro::App::Satpass2::Warner->new(

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

    $self->{frame}[-1]{in_else}++
	and $self->wail( 'Only one else may follow an if' );

    return $self->_twiddle_condition( ! $self->{frame}[-2]{condition} );
}

sub _twiddle_condition {
    my ( $self, $cond ) = @_;

    # Here is where I pay for the convenience of the if()
    # implementation. The if() itself is a frame because I do not yet
    # know if it will entail a begin(). But I can't do an else() unless
    # there is in fact a begin(), which creates another frame. So I end
    # up twiddling values in both frames.

    $self->{frame}[-1]{unsatisfied_if} =
	$self->{frame}[-2]{unsatisfied_if} =
	! $cond || (
	    @{ $self->{frame} } > 2 ?
		$self->{frame}[-3]{unsatisfied_if} :
		0
	    );

    $self->{frame}[-1]{condition} =
	$self->{frame}[-2]{condition} = $cond;

    return;
}

sub end : Verb() Tweak( -unsatisfied ) {
    my ( $self ) = __arguments( @_ );	# $opt, @args unused

    $self->{frame}[-1]{type} eq 'begin'
	or $self->wail( 'End without begin' );
    $self->_frame_pop();
    return;
}

sub error : Verb() {
    my ( $self, undef, @arg ) = __arguments( @_ );
    @arg
	or push @arg, 'An error has occurred';
    $self->wail( @arg );
    return;
}

# Tokenize and execute one or more commands. Optionally (and
# unsupportedly) you can pass a code reference as the first argument.
# This code reference will be used to fetch commands when the arguments
# are exhausted. IF you pass your own code reference, we return after
# the first command, since the code reference is presumed to manage the
# input stream itself.
sub execute {
    my ($self, @args) = @_;
    my $accum;
    my $in;
    my $extern;
    if ( CODE_REF eq ref $args[0] ) {
	$extern = shift @args;
	$in = sub {
	    my ( $prompt ) = @_;
	    @args and return shift @args;
	    return $extern->( $prompt );
	};
    } else {
	$in = sub { return shift @args };
    }
    @args = map { split qr{ (?<= \n ) }smx, $_ } @args;
    while ( defined ( local $_ = $in->( $self->get( 'prompt' ) ) ) ) {
	$self->{echo} and $self->whinge($self->get( 'prompt' ), $_);
	m/ \A \s* [#] /smx and next;
	my $stdout = $self->{frame}[-1]{stdout};
	my ($args, $redirect) = $self->__tokenize(
	    { in => $in }, $_, $self->{frame}[-1]{args});
	# NOTICE
	#
	# The execute_filter attribute is undocumented and unsupported.
	# It exists only so I can scavenge the user's initialization
	# file for the (possible) Space Track username and password, to
	# be used in testing, without being subject to any other
	# undesired side effects, such as running a prediction and
	# exiting. If I change my mind on how or whether to do this,
	# execute_filter will be altered or retracted without warning,
	# much less a deprecation cycle. If you have a legitimate need
	# for this functionality, contact me.
	#
	# YOU HAVE BEEN WARNED.
	$self->{execute_filter}->( $self, $args ) or next;
	@{ $args } or next;
	if ($redirect->{'>'}) {
	    my ( $mode, $name ) = map { $redirect->{'>'}{$_} } qw{ mode name };
	    my $fh;
	    $stdout = sub {
		my ( $output ) = @_;
		$fh ||= $self->_file_opener( $name, $mode );
		$fh->print( $output );
		return;
	    };
	}

	# {localout} is the output to be used for this command. It goes
	# in the frame stack because our command may start a new frame,
	# and _frame_push() needs to have a place to get the correct
	# output handle.

	my $frame_depth = $#{$self->{frame}};
	$self->{frame}[-1]{localout} = $stdout;

	my $output = $self->dispatch( @$args );

	$#{$self->{frame}} >= $frame_depth
	    and delete $self->{frame}[ $frame_depth ]{localout};

	$self->_execute_output( $output,
	    defined $stdout ? $stdout : \$accum );

	$extern and last;
    }
    return $accum;
}

#	$satpass2->_execute(...);
#
#	This subroutine calls $satpass2->execute() once for each
#	argument. The call is wrapped in an eval{}; if an exception
#	occurs the user is notified via warn.

sub _execute {
    my ($self, @args) = @_;
    my $in = CODE_REF eq ref $args[0] ? shift @args : sub { return shift

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

	# If the undocumented first option is a code reference, use it to
	# get input.
	my $in;
	CODE_REF eq ref $args[0]
	    and $in = shift @args;

	# Parse the command options. -level1 is undocumented.
	my %opt;
	$go ||= Getopt::Long::Parser->new();
	$go->getoptionsfromarray(
	    \@args,
	    \%opt,
	    qw{
		echo! filter! gmt! help initialization_file|initfile=s
		level1! version
	    },
	)
	    or $self->wail( 'See the help method for valid options' );

	# If -version, do it and return.
	if ( $opt{version} ) {
	    print $self->version();
	    return;
	}

	# If -help, do it and return.
	if ( $opt{help} ) {
	    $self->help();
	    return;
	}

	# Get an input routine if we do not already have one.
	$in ||= $self->_get_readline();

	# Some options get processed before we initialize.
	foreach my $name ( qw{ echo filter } ) {
	    exists $opt{$name}
		and $self->set( $name => delete( $opt{$name} ) );
	}

	# Display the front matter if desired.
	(!$self->get('filter') && $self->_get_interactive())
	    and print $self->version();

	# Execute the initialization file.
	eval {
	    $self->_execute_output( $self->init(
		    { level1 => delete $opt{level1} },
		    delete $opt{initialization_file},
		), $self->get( 'stdout' ) );
	    1;
	} or warn $@;	# Not whinge, since presumably we already did.

	# The remaining options set the corresponding attributes.
	%opt and $self->set(%opt);

	# Execution loop. What exit() really does is a last on this.
    SATPASS2_EXECUTE:
	{
	    $self->_execute( @args );
	    while ( defined ( my $buffer = $in->( $self->get( 'prompt' ) ) ) ) {
		$self->_execute( $in, $buffer );
	    }
	}
	$self->_execute( q{echo ''} );	# The lazy way to be sure we
					    # have a newline before exit.
	return;
    }
}

sub save : Verb( changes! overwrite! ) {
    my ( $self, $opt, $fn ) = __arguments( @_ );

    defined $fn or $fn = $self->initfile( { 'create-directory' => 1 } );
    chomp $fn;	# because initfile() adds a newline for printing
    if ($fn ne '-' && -e $fn) {
	-f $fn or $self->wail(
	    "Can not overwrite $fn: not an ordinary file");
	$opt->{overwrite} or do {
	    my $rslt = $self->_get_readline()->(
		"File $fn exists. Overwrite [y/N]? ");
	    'y' eq lc substr($rslt, 0, 1)
		or return;
	};
    }
    my @show_opt;
    my $title = 'settings';
    if ($opt->{changes}) {
	push @show_opt, '-changes';
	$title = 'setting changes';
    }

    my $output = <<"EOD" .

# Astro::App::Satpass2 $title

EOD
	$self->show( @show_opt, qw{ -nodeprecated -noreadonly } ) .
	<<"EOD" . $self->macro('list');

# Astro::App::Satpass2 macros

EOD

    if ( $self->{_perl} ) {
	$output .= <<'EOD';

# Astro::App::Satpass2 setup

EOD
	foreach my $item ( @{ $self->{_perl} } ) {
	    my ( $opt, @arg ) = @{ $item };
	    my @cmd = ( 'perl' );
	    push @cmd, map { "-$_" } grep { $opt->{$_} } sort keys %{ $opt };
	    $output .= join ' ', quoter( @cmd, @arg );
	    $output .= "\n";
	}
    }

    foreach my $attribute ( qw{ formatter spacetrack time_parser } ) {
	my $obj = $self->get( $attribute ) or next;

lib/Astro/App/Satpass2.pm  view on Meta::CPAN


    my $dumper;

    sub _get_dumper_object {
	return ( $dumper ||= do {
		require Astro::App::Satpass2::Format::Dump;
		Astro::App::Satpass2::Format::Dump->new();
	    }
	);
    }

}

#	$fmt = $satpass2->_get_formatter_object( $opt );
#
#	Gets the Astro::App::Satpass2::Format object. If $opt->{dump} is true,
#	returns a dumper object; otherwise returns the currently-set
#	formatter object.

sub _get_formatter_object {
    my ( $self, $opt ) = @_;
    $opt ||= {};
    return ( $opt && $opt->{dump} ) ? $self->_get_dumper_object() :
	$self->get( 'formatter' );
}

sub _get_formatter_attribute {
    my ( $self, $name ) = @_;
    return $self->get( 'formatter' )->$name();
}

#	$st = $satpass2->_get_geocoder()

#	Gets the geocoder object, instantiating it if
#	necesary.

sub _get_geocoder {
    my ( $self ) = @_;
    if ( ! exists $self->{geocoder} ) {
	my ( $class, $obj );
	$class = $default_geocoder->()
	    and $obj = $class->new();
	$self->{geocoder} = $obj;
    }
    return $self->{geocoder};
}

#	$boolean = $satpass2->_get_interactive();
#
#	This method returns true if the script is running interactively,
#	and false otherwise. Currently, it returns the results of -t
#	STDIN.

sub _get_interactive {
    return -t STDIN;
}

#	$code = $satpass2->_get_readline();
#
#	Returns code to read input. The code takes an argument which
#	will be used as a prompt if one is needed. What is actually
#	returned is:
#
#	If $satpass2->_get_interactive() is false, the returned code
#	just reads standard in. Otherwise,
#
#	if Term::ReadLine can be loaded, a Term::ReadLine object is
#	instantiated if need be, and the returned code calls
#	Term::ReadLine->readline($_[0]) and returns whatever that gives
#	you. Otherwise,
#
#	Otherwise the returned code writes its argument to STDERR and
#	reads STDIN.
#
#	Note that the return from this subroutine may or may not be
#	chomped.

my $readline_word_break_re;

{
    my $rl;

    sub _get_readline {
	my ($self) = @_;
	# The Perl::Critic recommendation is IO::Interactive, but that
	# fiddles with STDOUT. We want STDIN, because we want to behave
	# differently if STDIN is a pipe, but not if STDOUT is a pipe.
	# We're still missing the *ARGV logic, but that's OK too, since
	# we use the contents of @ARGV as commands, not as file names.
	return do {
	    my $buffer = '';
	    if ($self->_get_interactive()) {
		eval {
		    load_package( 'Term::ReadLine' )
			or return;
		    unless ( $rl ) {
			$rl = Term::ReadLine->new( 'satpass2' );
			if ( 'Term::ReadLine::Perl' eq $rl->ReadLine() ) {

			    $readline_word_break_re ||= qr<
				[\Q$readline::rl_completer_word_break_characters\E]+
			    >smx;

			    no warnings qw{ once };
			    $readline::rl_completion_function = sub {
				my ( $text, $line, $start ) = @_;
				return $self->__readline_completer(
				    $text, $line, $start );
			    };
			}
		    }
		    sub {
			defined $buffer or return $buffer;
			return ( $buffer = $rl->readline($_[0]) );
		    }
		} || sub {
		    defined $buffer or return $buffer;
		    print STDERR $_[0];
		    return (
			$buffer = <STDIN>	## no critic (ProhibitExplicitStdin)
		    );

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

	my ($self, $string, $dfdist) = @_;
	defined $dfdist or $dfdist = 'km';
	my $dfunits = $dfdist =~ s/ ( [[:alpha:]]+ ) \z //smx ? $1 : 'km';
	my $units = lc (
	    $string =~ s/ \s* ( [[:alpha:]]+ ) \z //smx ? $1 : $dfunits );
	$units{$units}
	    or $self->wail( "Units of '$units' are unknown" );
	$string ne '' or $string = $dfdist;
	looks_like_number ($string)
	    or $self->wail( "'$string' is not a number" );
	return $string * $units{$units};
    }
}

# Documented in POD

sub __parse_time {
    my ($self, $time, $default) = @_;
    my $pt = $self->{time_parser}
	or $self->wail( 'No time parser available' );
    $self->{time_parser}->can( 'station' )
	and $self->_set_time_parser_attribute(
	station => $self->station() );
    if ( defined( my $time = $pt->parse( $time, $default ) ) ) {
	return $time;
    }
    $self->wail( "Invalid time '$time'" );
    return;
}

#	Reset the last time set. This is called from __arguments() in
#	::Utils if the invocant is an Astro::App::Satpass2.

sub __parse_time_reset {
    my ( $self ) = @_;
    defined ( my $pt = $self->{time_parser} )
	or return;
    $pt->reset();
    return;
}

#	$string = _rad2hms ($angle)

#	Converts the given angle in radians to hours, minutes, and
#	seconds (of right ascension, presumably)

sub _rad2hms {
    my $sec = shift;
    $sec *= 12 / PI;
    my $hr = floor( $sec );
    $sec = ( $sec - $hr ) * 60;
    my $min = floor( $sec );
    $sec = ( $sec - $min ) * 60;
    my $rslt = sprintf '%2d:%02d:%02d', $hr, $min, floor( $sec + .5 );
    return $rslt;
}

#	$line = $self->_read_continuation( $in, $error_message );
#
#	Acquire a line from $in, which must be a code reference taking
#	the prompt as an argument. If $in is not a code reference, or if
#	it returns undef, we wail() with the error message.  Otherwise
#	we return the line read. I expect this to be used only by
#	__tokenize().

sub _read_continuation {
    my ( $self, $in, $error ) = @_;
    $in and defined( my $more = $in->(
	    my $prompt = $self->get( 'continuation_prompt' ) ) )
	or do {
	    $error or return;
	    ref $error eq CODE_REF
		and return $error->();
	    $self->wail( $error );
	};
    $self->{echo} and $self->whinge( $prompt, $more );
    $more =~ m/ \n \z /smx or $more .= "\n";
    return $more;
}

# my ( $old_obj ) = $self->_replace_in_sky( $name, $new_obj );
# This is restricted to objects constructed via {sky_class}.
# The return is an array containing the replaced body, or nothing if
# the body was not found. The $new_obj is optional; if not provided a
# new object is created.
sub _replace_in_sky {
    my ( $self, $name, $new_obj ) = @_;
    $new_obj
	or $self->{sky_class}{ fold_case( $name ) }
	or $self->weep( "Can not replace $name; no class defined" );
    defined( my $inx = $self->_find_in_sky( $name ) )
	or return;
    return splice @{ $self->{sky} }, $inx, $inx + 1,
	$new_obj || $self->_sky_object( $name );
}

#	$self->_rewrite_level1_command( $buffer, $context );
#
#	This method rewrites a level1 command to its current form. The
#	arguments are the buffer containing the command, and an
#	initially-empty hash reference, which the method will use to
#	preserve context across lines of command. NOTE that more than
#	one rewritten command may be returned (e.g. 'almanac' into
#	( 'location', 'almanac' ).

{

    my %level1_map = (
	almanac	=> sub {
	    return ( 'location', $_[0] );
	},
	flare	=> sub {
	    local $_ = $_[0];
	    s/ (?<= \s ) - ( am|pm|day ) \b /-no$1/sxmg;
	    return $_;
	},
	pass	=> sub {
	    return ( 'location', $_[0] );
	},
    );

    my %level1_requote = (
	# In a macro definition:
	macro	=> {
	    # In single-quoted strings,
	    q{'}	=> sub {
		# escaped interpolations and double quotes may be
		# unescaped,
		s{ (?: \A | (?<! \\ ) ) ( (?: \\\\ )* ) \\ ( [\@\$\"] )
		}{$1$2}sxmg;
		# and the string remains single-quoted.
		$_ = qq{'$_'};
		return;
	    },
	    # In double-quoted strings,
	    q{"}	=> sub {

lib/Astro/App/Satpass2.pm  view on Meta::CPAN


__END__

=head1 NAME

Astro::App::Satpass2 - Forecast satellite visibility.

=head1 SYNOPSIS

 use Astro::App::Satpass2;
 # Instantiate and set our location
 my $satpass2 = Astro::App::Satpass2->new(
     location => '1600 Pennsylvania Ave, Washington DC',
     latitude => 38.898748,    # degrees
     longitude => -77.037684,  # degrees
     height => 16.68,          # meters
 );
 # Acquire ISS data from Celestrak
 $satpass2->spacetrack( qw{ celestrak stations } );
 # Remove other bodies in the Celestrak 'stations' catalog
 $satpass2->choose( 25544 );
 # Display our location
 $satpass2->location();
 # Display visible ISS passes over our location
 $satpass2->pass();

Or equivalently, from the F<satpass2> script which is installed with
this package,

 $ satpass2
          ... front matter displayed here ...
 satpass2> # set our location
 satpass2> set location '1600 Pennsylvania Ave, Washington DC'
 satpass2> set latitude 38.898748 longitude -77.037684
 satpass2> set height 16.68
 satpass2> # Acquire ISS data from Celestrak
 satpass2> spacetrack celestrak stations
 satpass2> # Remove other bodies in the Celestrak 'stations' catalog
 satpass2> choose 25544
 satpass2> # Display our location
 satpass2> location
 satpass2> # Display visible ISS passes over our location
 satpass2> pass
 satpass2> # Guess what
 satpass2> exit

The script is implemented in terms of the L<run()|/run> method. Blank
lines and comments are ignored. The first token in the line is the
method name, and subsequent tokens are arguments to that method. See
L<run()|/run> for the details of that method, and L</TOKENIZING> for
details of the tokenizer. Finally, see L<initfile()|/initfile> for where
to put your initialization file, which is just a script that gets
executed every time you invoke the L<run()|/run> method.

If you want to be interactive, simply

 use Astro::App::Satpass2;
 Astro::App::Satpass2->run(@ARGV);

which is essentially the content of the F<satpass2> script.  In this
last case, the user will be prompted for commands once the commands in
@ARGV are used up, unless those commands include 'exit'.

=head1 NOTICE

Geocoding using TomTom has been dropped as of version 0.024.
The old, undocumented interface has been dropped, and the new one
requires an API key.

The eventual plan is to retire the F<satpass> script in favor of this
package, and to rename the satpass-less F<Astro-satpass> distribution to
F<Astro-Coord-ECI>.

=head1 OVERVIEW

This class implements an application to predict satellite visibility and
related phenomena. It is a mostly-compatible rewrite and eventual
replacement of the F<satpass> script in distribution C<Astro-satpass>,
aimed at making it easier to test, and removing some of the odder cruft
that has accumulated in the F<satpass> script.

The easiest way to make use of this class is via the bundled F<satpass2>
script, which simply calls the L<run()|/run> method.
L<Astro::App::Satpass2::TUTORIAL|Astro::App::Satpass2::TUTORIAL> covers
getting started with this script. If you do nothing else, see the
tutorial on setting up an initialization file, since the L<satpass2>
script will be much more easy to use if you configure some things up
front.

You can also instantiate an C<Astro::App::Satpass2> object yourself and
access all its functionality programmatically. If you are doing this you
may still want to consult the
L<TUTORIAL|Astro::App::Satpass2::TUTORIAL>, because the F<satpass2>
commands correspond directly to C<Astro::App::Satpass2> methods.

=head1 Optional Modules

An attempt has been made to keep the requirements of this module
reasonably modest. But there are a number of optional modules which, if
installed, give you increased functionality. If you do not install these
initially and find you want the added functionality, you can always
install them later. The optional modules are:

=over

=item L<Astro::SIMBAD::Client|Astro::SIMBAD::Client>

This module looks up the positions of astronomical bodies in the SIMBAD
database at L<https://simbad.cds.unistra.fr/simbad/>. This is only used
by the C<lookup> subcommand of the L<sky()|/sky> method.

=item L<Astro::SpaceTrack|Astro::SpaceTrack>

This module retrieves satellite orbital elements from various sources.
Since you have to have these to predict satellite positions, this is the
least optional of the optional modules. Without it, you would have to
download orbital elements some other way and then use the
L<load()|/load> method to import them into C<Astro::App::Satpass2>.

=item L<Date::Manip|Date::Manip>

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

synonyms are appropriate to the Moon and Sun respectively. See below for
how this is defaulted.

=back

The C<-q0>, C<-q1>, C<-q2>, and C<-q3> options (and their synonyms) are
defaulted as a group. If none of the group is specified, all are
asserted by default. If none is asserted but at least one is negated
(e.g. C<-nonew>), all unspecified members of the group are asserted by
default. If at least one member of the group is asserted, all
unspecified members are negated by default.

=head2 run

 Astro::App::Satpass2->run(...);

This non-interactive method runs the application. The arguments are the
options and commands to be passed to the application.

The valid options are:

 -echo to turn on command echoing;
 -filter to suppress banner text;
 -gmt to output time in GMT;
 -initfile name of the initialization file to use;
 -version to display the output of version() and return.

The -filter option defaults to true if STDIN is not a terminal.

The steps in running the application are:

1) If the first argument is a code reference, it is pulled off the
argument list and used for input. Otherwise default input code is
generated as described later.

2) The arguments are parsed as though they are a command line.

3) If the input is from a terminal and the -filter option was not
specified, a banner is printed.

4) The initialization file is located and run. If you specified an
initialization file via the C<-initfile> option, you will be warned if
it was not found. If the initialization file contains the C<exit>
command, it will be executed, and the run will end at this step.

5) Any remaining options corresponding to attribute values (currently
only C<-gmt>) are applied.

6) Any remaining arguments after removing all options are assumed to be
commands, and passed to the L<execute()|/execute> method. If one of
these is the C<exit> command, the run will end at this step.

7) Further commands are read as described below.

By default, commands come from C<STDIN>, but any commands passed as
arguments are executed first. How commands are read from C<STDIN>
depends on a number of factors. If C<STDIN> is a terminal and
L<Term::ReadLine|Term::ReadLine> can be loaded, a
L<Term::ReadLine|Term::ReadLine> object is instantiated and used to read
input.  If C<STDIN> is a terminal and L<Term::ReadLine|Term::ReadLine>
can not be loaded, the prompt is printed to C<STDERR> and C<STDIN> is
read.  If C<STDIN> is not a terminal, it is read.

If L<Term::ReadLine|Term::ReadLine> is in use and can load
C<Term::ReadLine::Perl>, command editing, history, and completion are
available. Completion will include at least command, macro, and option
names, in addition to the file name completion built into
C<Term::ReadLine::Perl>.

The default command acquisition behavior can be changed by passing, as
the first argument, a code reference. This should refer to a subroutine
that expects the prompt as its only argument, and returns the next
input. This code should return C<undef> to indicate a logical
end-of-file.

The exit command causes the method to return.

This method can also be called on an Astro::App::Satpass2 object. For example:

 use Astro::App::Satpass2;
 my $app = Astro::App::Satpass2->new(
     prompt => 'Your wish is my command: '
 );
 $app->run();

=head2 save

 $satpass2->save( $file_name );
 satpass2> save file_name

This interactive method saves your current settings to the named file.
If no file is named, they are saved to the default configuration file.
If the file already exists, you will be prompted unless you specified
the C<-overwrite> option. Nothing is returned.

File name F<-> is special, and causes output to go wherever standard
output is being sent.

This method saves all attribute values of the C<Astro::App::Satpass2> object,
all attributes of the L<Astro::SpaceTrack|Astro::SpaceTrack> object
being used to retrieve TLE data, and all defined macros. If you
overwrite a configuration file, any other contents of the file will be
lost.

The following options are allowed:

C<-changes> causes only changes from the default attributes to be
written to the output file.

C<-overwrite> causes the output file to overwrite an existing file of
the same name (if any) without getting confirmation from the user.

=head2 set

 $satpass2->set($name => $value ...);
 satpass2> set name value ...

This interactive method sets the values of the given
L<attributes|/ATTRIBUTES>. More than one attribute can be set at a time.
Nothing is returned.

When this method is being executed interactively (i.e. via the
L</dispatch> method, as opposed to being called directly as a method),
certain attributes may not be set. Also, the literal C<'undef'> is taken
to represent the undefined value.

=head2 show

 $output = $satpass2->show( $name, ... );
 satpass2> show name ...

This interactive method returns the values of the given attributes,
formatted as 'set' commands. If no arguments are given, the values of
all non-deprecated attributes that may be set interactively are
returned.

If you specify the C<-changes> option, only those values that have been
changed from the default are returned.

=head2 sky

 $output = $satpass2->sky( $subcommand ...);
 satpass2> sky subcommand ...

This interactive method manipulates the background objects. The
$subcommand argument determines what manipulation is done, and the
interpretation of subsequent arguments depends on this. The
interpretation of the subcommand names is not case-sensitive. If no
subcommand is given, 'list' is assumed.

The possible subcommands are:

=head3 add

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

Attributes that represent angles are in degrees, but may be set in other
representations (e.g. degrees, minutes, and seconds). See L</SPECIFYING
ANGLES> for more detail.

Boolean (i.e. true/false) attributes are set by convention to 1 for
true, or 0 for false. The evaluation rules are those of Perl itself:
0, '', and the undefined value are false, and everything else is true.

There are a few attributes whose names duplicate the names of methods.
These will be identified as attributes, for the sake of internal links.
For example, L</appulse>, but L</height attribute>.

The attributes are:

=head2 appulse

This numeric attribute specifies the maximum angle reportable by the
L</pass> method between the orbiting body and any of the background
objects. If the body passes closer than this, the closest point will
appear as an event in the pass. The intent is to capture transits or
near approaches.

If this attribute is set to 0, no check for close approaches to
background objects will be made.

See L</SPECIFYING ANGLES> for ways to specify an angle. This attribute
is returned in decimal degrees.

The initial setting is 0.

=head2 autoheight

This boolean attribute determines whether the L</geocode> method
attempts to acquire the height of the location above sea level.  It does
this only if the parameter is true and the geocoding returns exactly one
location. You may wish to turn this off (i.e. set it to 0) if the USGS
elevation service is being balky.

The default is 1 (i.e. true).

=head2 backdate

This boolean attribute determines whether the L</pass> method will
attempt to use orbital elements before their effective date. It is
actually simply propagated to the C<backdate> attribute of the
individual TLE objects, and so takes effect on a per-object basis. If it
is false, the L</pass> method will silently move the start of the pass
prediction to the effective date of the data if the specified pass start
is earlier than the effective date of the data.

The default is 0 (i.e. false). This is different from the old F<satpass>
script, which defaulted it to true.

=head2 background

This boolean attribute determines whether the location of the background
body is displayed when the L</appulse> logic detects an appulse.

The default is 1 (i.e. true).

=head2 continuation_prompt

This string attribute specifies the string used to prompt for
continuations of lines.

The default is C<< '> ' >>.

=head2 country

This attribute is ignored and deprecated.

This string attribute determines the default country for the L</geocode>
and L</height> methods. The intent is that it be an ISO 3166
two-character country code. At the moment it does nothing useful since
there is currently only one source for L</geocode> and L</height> data.

See L<https://www.iso.org/iso-3166-country-codes.html>
for the current list of country codes. Note that these are B<not>
always the same as the corresponding top-level geographic domain names
(e.g. Great Britain is 'GB' in ISO 3166 but for historical reasons has
both 'gb' and 'uk' as top-level geographic domain name).

The country codes are case-insensitive, since they will be converted to
lower case for use.

The default is 'us'.

=head2 date_format

This string attribute is deprecated. It is provided for backward
compatibility with the F<satpass> script. The preferred way to
manipulate this is either directly on the formatter object (if you set
it yourself and retained a reference), or via the
L<formatter()|/formatter> method, e.g.:

 $satpass2->get( 'formatter' )->date_format( '%d-%b-%Y' );
 satpass2> formatter date_format '%d-%b-%Y'

This attribute allows access to and manipulation of the formatter
object's L<date_format|Astro::App::Satpass2::Format/date_format> attribute.
This is normally used as a C<strftime (3)> format to format a date. See
the L<date_format|Astro::App::Satpass2::Format/date_format> documentation for
the default. See the documentation of the actual formatter class being
used for what it does.

This string attribute specifies the format used to display
dates. Documentation of the C<strftime (3)> subroutine may be found at
L<https://man7.org/linux/man-pages/man3/strftime.3.html> among other places.

The above is a long URL, and may be split across multiple lines. More
than that, the formatter may have inserted a hyphen at the break, which
needs to be taken out to make the URL good. I<Caveat user.>

=head2 debug

This numeric attribute turns on debugging output. The only supported
value is 0. The author makes no representation of what will happen if a
non-zero value is set, not does he promise that the behavior for a given
non-zero value will not change from release to release.

The default is 0.

=head2 desired_equinox_dynamical

lib/Astro/App/Satpass2.pm  view on Meta::CPAN


This attribute specifies the C<pass_variant> value to set when doing a
C<pass()> computation. It can be set to a number or a string consisting
of one or more of the following strings, which are equivalent to the
given C<Astro::Coord::ECI::TLE> manifest constants:

    visible_events   => PASS_VARIANT_VISIBLE_EVENTS
    fake_max         => PASS_VARIANT_FAKE_MAX
    start_end        => PASS_VARIANT_START_END
    no_illumination  => PASS_VARIANT_NO_ILLUMINATION
    brightest        => PASS_VARIANT_BRIGHTEST
    none             => PASS_VARIANT_NONE

If more than one value from the above table is specified, they can be
punctuated by any character that is not a word or a dash. They can also
be abbreviated uniquely, the underscores can be specified as dashes, and
they can be preceded by a dash, as though they were options.

When you specify a string value, the derived bits will be set in the
attribute value, or cleared if the name is preceded by C<'no'>. The
exception is C<'none'>, which clears all variant bits when it is
encountered.

For example,

 satpass2> # Note quotes in next line
 satpass2> set pass_variant 'none brightest fake-max'
 satpass2> show pass_variant
 set pass_variant brightest,fake_max
 satpass2> set pass_variant nofake
 satpass2> show pass_variant
 set pass_variant brightest
 satpass2> set pass_variant nobrightest
 satpass2> show pass_variant
 set pass_variant none

=head2 perltime

This boolean attribute is deprecated. It is provided for backward
compatibility with the F<satpass> script. The preferred way to
manipulate this is either directly on the formatter object, or via the
L<time_parser()|/time_parser> method.

This boolean attribute allows access to and manipulation of the time
parser object's L<perltime|Astro::App::Satpass2::ParseTime/perltime>
attribute.  This is normally used (if at all) to specify that the Perl
time built-ins be used to construct the parsed time. See the
L<perltime|Astro::App::Satpass2::ParseTime/perltime> documentation for
the default. See the documentation of the actual time parser class being
used for what it does.

This attribute was originally introduced because versions of
L<Date::Manip|Date::Manip> prior to 6.0 did not properly handle the
transition from standard time to summer time. Of those time parsers
distributed with this package, only
L<Astro::App::Satpass2::ParseTime::Date::Manip::v5|Astro::App::Satpass2::ParseTime::Date::Manip::v5>
uses this attribute.

The default will normally be 0 (i.e. false).

=head2 prompt

This string attribute specifies the string used to prompt for commands.

The default is C<< 'satpass2> ' >>.

=head2 refraction

This Boolean attribute specifies whether or not atmospheric refraction
is taken into account. It should ordinarily not be changed, and was
exposed only out of curiosity about the size of the effect on (say) the
time of Sunset.

The default is C<1> (i.e. true).

=head2 simbad_url

This string attribute does not, strictly speaking, specify a URL, but
does specify the server to use to perform SIMBAD lookups (see the
'lookup' subcommand of the L</sky> method). Currently-legal values are
C<'simbad.u-strasbg.fr'> (the original site) and C<'simbad.harvard.edu'>
(Harvard University's mirror).

The default is C<'simbad.u-strasbg.fr'>.

=head2 singleton

If this boolean attribute is true, the script uses
L<Astro::Coord::ECI::TLE::Set|Astro::Coord::ECI::TLE::Set> objects to
represent all bodies. If false, the set object is used only if the
observing list contains more than one instance of a given NORAD ID. This
is really only useful for testing purposes.

Use of the L<Astro::Coord::ECI::TLE::Set|Astro::Coord::ECI::TLE::Set>
object causes calculations to take about 15% longer.

The default is 0 (i.e. false).

=head2 spacetrack attribute

This attribute is the L<Astro::SpaceTrack|Astro::SpaceTrack> object used
by the L<spacetrack()|/spacetrack> method. You must set it to an
L<Astro::SpaceTrack|Astro::SpaceTrack> object, or to undef to clear the
attribute. If no L<Astro::SpaceTrack|Astro::SpaceTrack> object has been
explicitly set, the L<spacetrack()|/spacetrack> method will attempt to
load L<Astro::SpaceTrack|Astro::SpaceTrack> and set this attribute
itself. If it succeeds, this object will be available to the L</get>
method.

This attribute may only be manipulated programmatically; it may not be
gotten or set via the L</dispatch> method, and therefore not by the
F<satpass2> script.

The default is undef.

=head2 stdout

This attribute determines what the L</execute> method does with its
output. The possible values are interpreted as follows:

C<undef> - the output is returned;

scalar reference - the output is appended to the scalar;



( run in 4.006 seconds using v1.01-cache-2.11-cpan-6aa56a78535 )