File Coverage

blib/lib/UAV/Pilot/Commands.pm
Criterion Covered Total %
statement 64 65 98.4
branch 12 18 66.6
condition 3 5 60.0
subroutine 13 13 100.0
pod 3 4 75.0
total 95 105 90.4


line stmt bran cond sub pod time code
1             # Copyright (c) 2015 Timm Murray
2             # All rights reserved.
3             #
4             # Redistribution and use in source and binary forms, with or without
5             # modification, are permitted provided that the following conditions are met:
6             #
7             # * Redistributions of source code must retain the above copyright notice,
8             # this list of conditions and the following disclaimer.
9             # * Redistributions in binary form must reproduce the above copyright
10             # notice, this list of conditions and the following disclaimer in the
11             # documentation and/or other materials provided with the distribution.
12             #
13             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23             # POSSIBILITY OF SUCH DAMAGE.
24             package UAV::Pilot::Commands;
25             $UAV::Pilot::Commands::VERSION = '1.3';
26 2     2   2053 use v5.14;
  2         8  
27 2     2   11 use Moose;
  2         5  
  2         13  
28 2     2   13142 use namespace::autoclean;
  2         5  
  2         19  
29 2     2   152 use File::Spec;
  2         4  
  2         55  
30              
31 2     2   9 use constant MOD_PREFIX => 'UAV::Pilot';
  2         6  
  2         127  
32 2     2   10 use constant MOD_SUFFIX => 'Commands';
  2         5  
  2         1777  
33              
34              
35             has 'lib_dirs' => (
36             is => 'ro',
37             isa => 'ArrayRef[Str]',
38             traits => [ 'Array' ],
39             default => sub {[]},
40             handles => {
41             add_lib_dir => 'push',
42             },
43             );
44             has 'condvar' => (
45             is => 'ro',
46             isa => 'AnyEvent::CondVar',
47             );
48             has 'controller_callback_ardrone' => (
49             is => 'ro',
50             isa => 'CodeRef',
51             );
52             has 'controller_callback_wumpusrover' => (
53             is => 'ro',
54             isa => 'CodeRef',
55             );
56             has 'quit_subs' => (
57             traits => ['Array'],
58             is => 'ro',
59             isa => 'ArrayRef[CodeRef]',
60             default => sub {[]},
61             handles => {
62             '_push_quit_sub' => 'push',
63             },
64             );
65              
66             our $s;
67              
68             #
69             # Sole command that can run without loading other libraries
70             #
71             sub load ($;$)
72             {
73 4     4 1 7 my ($mod_name, $args) = @_;
74 4 50       148 $$args{condvar} = $s->condvar unless exists $$args{condvar};
75 4         12 $s->load_lib( $mod_name, $args );
76             }
77              
78              
79             sub run_cmd
80             {
81 8     8 1 3088 my ($self, $cmd) = @_;
82 8 0 33     23 if( (! defined $self) && (! ref($self)) ) {
83             # Must be called with a $self, not directly via package
84 0         0 return 0;
85             }
86 8 50       18 return 1 unless defined $cmd;
87              
88 8         9 $s = $self;
89 8         433 eval $cmd;
90 8 100       58 die $@ if $@;
91              
92 5         11 return 1;
93             }
94              
95             sub quit
96             {
97 1     1 0 830 my ($self) = @_;
98 1         2 $_->() for @{ $self->{quit_subs} };
  1         6  
99 1         10 return 1;
100             }
101              
102              
103             sub load_lib
104             {
105 4     4 1 7 my ($self, $mod_name, $args) = @_;
106 4   100     19 my $dest_namespace = delete $args->{namespace} // 'UAV::Pilot::Commands';
107            
108             # This works via the hooks placed into @INC array, which is documented
109             # in perlfunc under the require() entry. In short, we can stick a
110             # subref in @INC and mess around with how Perl loads up the module.
111             # By choosing the starting text, we can control the exact namespace
112             # where the module will end up.
113              
114 4         17 my @orig_inc = @INC;
115 4         10 local @INC = (
116             $self->_get_load_module_sub( $dest_namespace, \@orig_inc ),
117             @INC,
118             );
119              
120 4         20 my $full_mod_name = $self->MOD_PREFIX
121             . '::' . $mod_name
122             . '::' . $self->MOD_SUFFIX;
123              
124 4         206 eval "require $full_mod_name";
125 4 100       333 die "Could not load $mod_name: $@" if $@;
126              
127 3 100       43 if( my $call = $dest_namespace->can( 'uav_module_init' ) ) {
128 1         4 $call->( $dest_namespace, $self, $args );
129              
130             # Clear uav_module_init. Would prefer a solution without
131             # eval( STRING ), though a symbol table manipulation method may be
132             # considered just as evil.
133 1         7 my $del_str = 'delete $' . $dest_namespace . '::{uav_module_init}';
134 1         55 eval $del_str;
135             }
136              
137 3 50       19 if( my $quit_call = $dest_namespace->can( 'uav_module_quit' ) ) {
138 3         144 $self->_push_quit_sub( $quit_call );
139             }
140              
141             # If we want to reload the module, we need to delete its entry from the
142             # %INC cache
143 3         12 my @mod_name_components = split /::/, $full_mod_name;
144 3         34 my $mod_name_path = File::Spec->catfile( @mod_name_components ) . '.pm';
145 3         10 delete $INC{$mod_name_path};
146              
147 3         46 return 1;
148             }
149              
150             sub _get_load_module_sub
151             {
152 4     4   7 my ($self, $dest_namespace, $inc) = @_;
153 4         9 my $init_source = "package $dest_namespace;";
154              
155             my $sub = sub {
156 4     4   9 my ($this_sub, $file) = @_;
157              
158 4         6 my @return;
159 4         9 foreach (@$inc) {
160 17         147 my $full_path = File::Spec->catfile( $_, $file );
161 17 100       409 if( -e $full_path ) {
162 3 50       133 open( my $in, '<', $full_path )
163             or die "Can't open '$full_path': $!\n";
164              
165 3         6 @return = (
166             \$init_source,
167             $in,
168             );
169 3         8 last;
170             }
171             }
172              
173 4         1019 return @return;
174 4         19 };
175              
176 4         17 return $sub;
177             }
178              
179              
180 2     2   12 no Moose;
  2         4  
  2         13  
181             __PACKAGE__->meta->make_immutable;
182             1;
183             __END__
184              
185              
186             =head1 NAME
187              
188             UAV::Pilot::Commands
189              
190             =head1 SYNOPSIS
191              
192             my $device; # Some UAV::Pilot::Control instance, defined elsewhere
193             my $cmds = UAV::Pilot::Commands->new({
194             device => $device,
195             controller_callback_ardrone => \&make_ardrone_controller,
196             controller_callback_wumpusrover => \&make_wumpusrover_controller,
197             });
198            
199             $cmds->load_lib( 'ARDrone' );
200             $cmds->run_cmd( 'takeoff;' );
201             $cmds->run_cmd( 'land;' );
202              
203             =head1 DESCRIPTION
204              
205             Provides an interface for loading UAV extensions and running them, particularly for
206             REPL shells.
207              
208             =head1 METHODS
209              
210             =head2 new
211              
212             new({
213             condvar => $cv,
214             controller_callback_ardrone => sub { ... },
215             controller_callback_wumpusrover => sub { .. },
216             })
217              
218             Constructor. The C<condvar> parameter is an C<AnyEvent::Condvar>.
219              
220             The C<controller_callback_*> parameters take a sub ref. The subroutines take
221             a the parameters C<($cmd, $cv, $easy_event)>, where C<$cmd> is this
222             C<UAV::Pilot::Commands> instance, C<$cv> is the condvar passed above, and
223             C<$easy_event> is an C<UAV::Pilot::EasyEvent> instance. It should return a
224             C<UAV::Pilot::Control> object of the associated type (generally one of the
225             C<*::Event> types with C<init_event_loop()> called).
226              
227             Note that this API is likely to change to a factory pattern in the near future.
228              
229             =head2 load_lib
230              
231             load_lib( 'ARDrone', {
232             pack => 'AR',
233             })
234              
235             Loads an extension by name. The C<pack> paramter will load the library into a specific
236             namespace. If you don't specify it, you won't need to qualify commands with a namespace
237             prefix. Example:
238              
239             load_lib( 'ARDrone', { pack => 'AR' } );
240             run_cmd( 'takeoff;' ); # Error: no subroutine named 'takeoff'
241             run_cmd( 'AR::takeoff;' ); # This works
242            
243             load_lib( 'ARDrone' );
244             run_cmd( 'takeoff;' ); # Now this works, too
245              
246             Any other parmaeters you pass will be passed to the module's C<uav_module_init()>
247             subroutine.
248              
249             =head2 run_cmd
250              
251             run_cmd( 'takeoff;' )
252              
253             Executes a command. Note that this will execute arbitrary Perl statements.
254              
255             =head1 COMMANDS
256              
257             Commands provide an easy interface for writing simple UAV programms in a REPL shell.
258             They are usually thin interfaces over a L<UAV::Pilot::Control>. If you're writing a
259             complicated script, it's suggested that you skip this interface and write to the
260             L<UAV::Pilot::Control> directly.
261              
262             =head2 load
263              
264             load 'ARDrone', {
265             namespace => 'AR',
266             };
267              
268             Direct call to C<load_lib>. The C<namespace> paramter will load the library
269             into a specific namespace. If you don't specify it, you won't need to qualify
270             commands with a namespace prefix. Example:
271              
272             load 'ARDrone', { namespace => 'AR' };
273             takeoff; # Error: no subroutine named 'takeoff'
274             AR::takeoff; # This works
275            
276             load ARDrone;
277             takeoff; # Now this works, too
278              
279             Any other parmaeters you pass will be passed to the module's
280             C<uav_module_init()> subroutine.
281              
282             =head1 WRITING YOUR OWN EXTENSIONS
283              
284             When calling C<load_lib( 'Foo' )>, we look for C<UAV::Pilot::Foo::Commands>
285             in the current C<@INC>.
286              
287             You write them much like any Perl module, but don't use a C<package>
288             statement--the package will be controlled by C<UAV::Pilot::Command> when
289             loaded. Like a Perl module, it should return true as its final statement
290             (put a C<1;> at the end).
291              
292             Likewise, be careful not to make any assumptions about what package you're in.
293             Modules may or may not get loaded into different, arbitrary packages.
294              
295             For ease of use, it's recommended to use function prototypes to reduce the need
296             for parens.
297              
298             The method C<uav_module_init()> is called with the package name as the first
299             argument. Subsquent arguments will be the hashref passed to
300             C<load()/load_lib()>. After being called, this sub will be deleted from the
301             package.
302              
303             The method C<uav_module_quit()> is called when the REPL is closing.