UAV-Pilot
view release on metacpan or search on metacpan
lib/UAV/Pilot/Commands.pm view on Meta::CPAN
# Copyright (c) 2015 Timm Murray
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
package UAV::Pilot::Commands;
$UAV::Pilot::Commands::VERSION = '1.3';
use v5.14;
use Moose;
use namespace::autoclean;
use File::Spec;
use constant MOD_PREFIX => 'UAV::Pilot';
use constant MOD_SUFFIX => 'Commands';
has 'lib_dirs' => (
is => 'ro',
isa => 'ArrayRef[Str]',
traits => [ 'Array' ],
default => sub {[]},
handles => {
add_lib_dir => 'push',
},
);
has 'condvar' => (
is => 'ro',
isa => 'AnyEvent::CondVar',
);
has 'controller_callback_ardrone' => (
is => 'ro',
isa => 'CodeRef',
);
has 'controller_callback_wumpusrover' => (
is => 'ro',
isa => 'CodeRef',
);
has 'quit_subs' => (
traits => ['Array'],
is => 'ro',
isa => 'ArrayRef[CodeRef]',
default => sub {[]},
handles => {
'_push_quit_sub' => 'push',
},
);
our $s;
#
# Sole command that can run without loading other libraries
#
sub load ($;$)
{
my ($mod_name, $args) = @_;
$$args{condvar} = $s->condvar unless exists $$args{condvar};
$s->load_lib( $mod_name, $args );
}
sub run_cmd
{
my ($self, $cmd) = @_;
if( (! defined $self) && (! ref($self)) ) {
# Must be called with a $self, not directly via package
return 0;
}
return 1 unless defined $cmd;
$s = $self;
eval $cmd;
die $@ if $@;
return 1;
}
sub quit
{
my ($self) = @_;
$_->() for @{ $self->{quit_subs} };
return 1;
}
sub load_lib
{
my ($self, $mod_name, $args) = @_;
my $dest_namespace = delete $args->{namespace} // 'UAV::Pilot::Commands';
# This works via the hooks placed into @INC array, which is documented
# in perlfunc under the require() entry. In short, we can stick a
# subref in @INC and mess around with how Perl loads up the module.
# By choosing the starting text, we can control the exact namespace
# where the module will end up.
my @orig_inc = @INC;
local @INC = (
$self->_get_load_module_sub( $dest_namespace, \@orig_inc ),
@INC,
);
my $full_mod_name = $self->MOD_PREFIX
. '::' . $mod_name
. '::' . $self->MOD_SUFFIX;
eval "require $full_mod_name";
die "Could not load $mod_name: $@" if $@;
if( my $call = $dest_namespace->can( 'uav_module_init' ) ) {
$call->( $dest_namespace, $self, $args );
# Clear uav_module_init. Would prefer a solution without
# eval( STRING ), though a symbol table manipulation method may be
# considered just as evil.
my $del_str = 'delete $' . $dest_namespace . '::{uav_module_init}';
eval $del_str;
}
if( my $quit_call = $dest_namespace->can( 'uav_module_quit' ) ) {
$self->_push_quit_sub( $quit_call );
}
# If we want to reload the module, we need to delete its entry from the
# %INC cache
my @mod_name_components = split /::/, $full_mod_name;
my $mod_name_path = File::Spec->catfile( @mod_name_components ) . '.pm';
delete $INC{$mod_name_path};
return 1;
}
sub _get_load_module_sub
{
my ($self, $dest_namespace, $inc) = @_;
my $init_source = "package $dest_namespace;";
my $sub = sub {
my ($this_sub, $file) = @_;
my @return;
foreach (@$inc) {
my $full_path = File::Spec->catfile( $_, $file );
if( -e $full_path ) {
open( my $in, '<', $full_path )
or die "Can't open '$full_path': $!\n";
@return = (
\$init_source,
$in,
);
last;
}
}
return @return;
};
return $sub;
}
no Moose;
__PACKAGE__->meta->make_immutable;
1;
__END__
=head1 NAME
UAV::Pilot::Commands
=head1 SYNOPSIS
my $device; # Some UAV::Pilot::Control instance, defined elsewhere
my $cmds = UAV::Pilot::Commands->new({
device => $device,
controller_callback_ardrone => \&make_ardrone_controller,
controller_callback_wumpusrover => \&make_wumpusrover_controller,
});
$cmds->load_lib( 'ARDrone' );
$cmds->run_cmd( 'takeoff;' );
$cmds->run_cmd( 'land;' );
=head1 DESCRIPTION
Provides an interface for loading UAV extensions and running them, particularly for
REPL shells.
=head1 METHODS
=head2 new
new({
condvar => $cv,
controller_callback_ardrone => sub { ... },
controller_callback_wumpusrover => sub { .. },
})
Constructor. The C<condvar> parameter is an C<AnyEvent::Condvar>.
The C<controller_callback_*> parameters take a sub ref. The subroutines take
a the parameters C<($cmd, $cv, $easy_event)>, where C<$cmd> is this
C<UAV::Pilot::Commands> instance, C<$cv> is the condvar passed above, and
C<$easy_event> is an C<UAV::Pilot::EasyEvent> instance. It should return a
C<UAV::Pilot::Control> object of the associated type (generally one of the
C<*::Event> types with C<init_event_loop()> called).
Note that this API is likely to change to a factory pattern in the near future.
=head2 load_lib
load_lib( 'ARDrone', {
pack => 'AR',
})
Loads an extension by name. The C<pack> paramter will load the library into a specific
namespace. If you don't specify it, you won't need to qualify commands with a namespace
prefix. Example:
load_lib( 'ARDrone', { pack => 'AR' } );
run_cmd( 'takeoff;' ); # Error: no subroutine named 'takeoff'
run_cmd( 'AR::takeoff;' ); # This works
load_lib( 'ARDrone' );
run_cmd( 'takeoff;' ); # Now this works, too
Any other parmaeters you pass will be passed to the module's C<uav_module_init()>
subroutine.
=head2 run_cmd
run_cmd( 'takeoff;' )
Executes a command. Note that this will execute arbitrary Perl statements.
=head1 COMMANDS
Commands provide an easy interface for writing simple UAV programms in a REPL shell.
They are usually thin interfaces over a L<UAV::Pilot::Control>. If you're writing a
complicated script, it's suggested that you skip this interface and write to the
L<UAV::Pilot::Control> directly.
=head2 load
load 'ARDrone', {
namespace => 'AR',
};
Direct call to C<load_lib>. The C<namespace> paramter will load the library
into a specific namespace. If you don't specify it, you won't need to qualify
commands with a namespace prefix. Example:
load 'ARDrone', { namespace => 'AR' };
takeoff; # Error: no subroutine named 'takeoff'
AR::takeoff; # This works
load ARDrone;
takeoff; # Now this works, too
Any other parmaeters you pass will be passed to the module's
C<uav_module_init()> subroutine.
=head1 WRITING YOUR OWN EXTENSIONS
When calling C<load_lib( 'Foo' )>, we look for C<UAV::Pilot::Foo::Commands>
in the current C<@INC>.
You write them much like any Perl module, but don't use a C<package>
statement--the package will be controlled by C<UAV::Pilot::Command> when
loaded. Like a Perl module, it should return true as its final statement
(put a C<1;> at the end).
Likewise, be careful not to make any assumptions about what package you're in.
Modules may or may not get loaded into different, arbitrary packages.
For ease of use, it's recommended to use function prototypes to reduce the need
for parens.
The method C<uav_module_init()> is called with the package name as the first
argument. Subsquent arguments will be the hashref passed to
C<load()/load_lib()>. After being called, this sub will be deleted from the
package.
The method C<uav_module_quit()> is called when the REPL is closing.
( run in 0.267 second using v1.01-cache-2.11-cpan-283623ac599 )