File Coverage

blib/lib/UAV/Pilot/Commands.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package UAV::Pilot::Commands;
2 1     1   1694 use v5.14;
  1         3  
  1         45  
3 1     1   420 use Moose;
  0            
  0            
4             use namespace::autoclean;
5             use File::Spec;
6              
7             use constant MOD_EXTENSION => '.uav';
8              
9              
10             has 'lib_dirs' => (
11             is => 'ro',
12             isa => 'ArrayRef[Str]',
13             traits => [ 'Array' ],
14             default => sub {[]},
15             handles => {
16             add_lib_dir => 'push',
17             },
18             );
19             has 'condvar' => (
20             is => 'ro',
21             isa => 'AnyEvent::CondVar',
22             );
23             has 'controller_callback_ardrone' => (
24             is => 'ro',
25             isa => 'CodeRef',
26             );
27             has 'controller_callback_wumpusrover' => (
28             is => 'ro',
29             isa => 'CodeRef',
30             );
31              
32             our $s;
33              
34             #
35             # Sole command that can run without loading other libraries
36             #
37             sub load ($;$)
38             {
39             my ($mod_name, $args) = @_;
40             $$args{condvar} = $s->condvar unless exists $$args{condvar};
41             $s->load_lib( $mod_name, $args );
42             }
43              
44              
45             sub run_cmd
46             {
47             my ($self, $cmd) = @_;
48             if( (! defined $self) && (! ref($self)) ) {
49             # Must be called with a $self, not directly via package
50             return 0;
51             }
52             return 1 unless defined $cmd;
53              
54             $s = $self;
55             eval $cmd;
56             die $@ if $@;
57              
58             return 1;
59             }
60              
61              
62             sub load_lib
63             {
64             my ($self, $mod_name, $args) = @_;
65             my @search_dirs = @{ $self->lib_dirs };
66             my $mod_file = $mod_name . $self->MOD_EXTENSION;
67             my $host = delete $$args{host};
68              
69             my $found = 0;
70             foreach my $dir (@search_dirs) {
71             my $file = File::Spec->catfile( $dir, $mod_file );
72             if( -e $file) {
73             $found = 1;
74             $self->_compile_mod( $file, $args );
75             }
76             }
77              
78             die "Could not find module named '$mod_name' in search paths ("
79             . join( ', ', @search_dirs ) . ")\n"
80             if ! $found;
81              
82             return $found;
83             }
84              
85             sub _compile_mod
86             {
87             my ($self, $file, $args) = @_;
88             my $pack = delete $$args{namespace};
89              
90             my $input = defined($pack)
91             ? qq{package $pack;\n}
92             : '';
93             $input .= qq(# line 1 "$file"\n);
94             open( my $in, '<', $file ) or die "Can't open <$file> for reading: $!\n";
95             while( <$in> ) {
96             $input .= $_;
97             }
98             close $in;
99              
100             my $ret = eval $input;
101             die $@ if $@;
102             die "Parsing <$file> did not return successfully\n" unless $ret;
103              
104             $pack = ref($self) unless defined $pack;
105             if( my $call = $pack->can( 'uav_module_init' ) ) {
106             $call->( $pack, $self, $args );
107              
108             # Clear uav_module_init. Would prefer a solution without eval( STRING ),
109             # though a symbol table manipulation method may be considered just as evil.
110             my $del_str = 'delete $' . $pack . '::{uav_module_init}';
111             eval $del_str;
112             }
113              
114             return 1;
115             }
116              
117              
118             no Moose;
119             __PACKAGE__->meta->make_immutable;
120             1;
121             __END__
122              
123              
124             =head1 NAME
125              
126             UAV::Pilot::Commands
127              
128             =head1 SYNOPSIS
129              
130             my $device; # Some UAV::Pilot::Control instance, defined elsewhere
131             my $cmds = UAV::Pilot::Commands->new({
132             device => $device,
133             controller_callback_ardrone => \&make_ardrone_controller,
134             controller_callback_wumpusrover => \&make_wumpusrover_controller,
135             });
136            
137             $cmds->load_lib( 'ARDrone' );
138             $cmds->run_cmd( 'takeoff;' );
139             $cmds->run_cmd( 'land;' );
140              
141             =head1 DESCRIPTION
142              
143             Provides an interface for loading UAV extensions and running them, particularly for
144             REPL shells.
145              
146             =head1 METHODS
147              
148             =head2 new
149              
150             new({
151             condvar => $cv,
152             controller_callback_ardrone => sub { ... },
153             controller_callback_wumpusrover => sub { .. },
154             })
155              
156             Constructor. The C<condvar> parameter is an C<AnyEvent::Condvar>.
157              
158             The C<controller_callback_*> parameters take a sub ref. The subroutines take
159             a the parameters C<($cmd, $cv, $easy_event)>, where C<$cmd> is this
160             C<UAV::Pilot::Commands> instance, C<$cv> is the condvar passed above, and
161             C<$easy_event> is an C<UAV::Pilot::EasyEvent> instance. It should return a
162             C<UAV::Pilot::Control> object of the associated type (generally one of the
163             C<*::Event> types with C<init_event_loop()> called).
164              
165             Note that this API is likely to change to a factory pattern in the near future.
166              
167             =head2 load_lib
168              
169             load_lib( 'ARDrone', {
170             pack => 'AR',
171             })
172              
173             Loads an extension by name. The C<pack> paramter will load the library into a specific
174             namespace. If you don't specify it, you won't need to qualify commands with a namespace
175             prefix. Example:
176              
177             load_lib( 'ARDrone', { pack => 'AR' } );
178             run_cmd( 'takeoff;' ); # Error: no subroutine named 'takeoff'
179             run_cmd( 'AR::takeoff;' ); # This works
180            
181             load_lib( 'ARDrone' );
182             run_cmd( 'takeoff;' ); # Now this works, too
183              
184             Any other parmaeters you pass will be passed to the module's C<uav_module_init()>
185             subroutine.
186              
187             =head2 run_cmd
188              
189             run_cmd( 'takeoff;' )
190              
191             Executes a command. Note that this will execute arbitrary Perl statements.
192              
193             =head1 COMMANDS
194              
195             Commands provide an easy interface for writing simple UAV programms in a REPL shell.
196             They are usually thin interfaces over a L<UAV::Pilot::Control>. If you're writing a
197             complicated script, it's suggested that you skip this interface and write to the
198             L<UAV::Pilot::Control> directly.
199              
200             =head2 load
201              
202             load 'ARDrone', {
203             pack => 'AR',
204             };
205              
206             Direct call to C<load_lib>. The C<pack> paramter will load the library into a specific
207             namespace. If you don't specify it, you won't need to qualify commands with a namespace
208             prefix. Example:
209              
210             load 'ARDrone', { pack => 'AR' };
211             takeoff; # Error: no subroutine named 'takeoff'
212             AR::takeoff; # This works
213            
214             load ARDrone;
215             takeoff; # Now this works, too
216              
217             Any other parmaeters you pass will be passed to the module's C<uav_module_init()>
218             subroutine.
219              
220             =head1 WRITING YOUR OWN EXTENSIONS
221              
222             Extensions should go in the directory specified by:
223              
224             File::ShareDir::dist_dir( 'UAV-Pilot' )
225              
226             They should have a C<.uav> extension.
227              
228             You write them much like any Perl module, but don't use a C<package> statement--the package
229             will be controlled by C<UAV::Pilot::Command> when loaded. Like a Perl module, it should
230             return true as its final statement (put a C<1;> at the end).
231              
232             Likewise, be careful not to make any assumptions about what package you're in. Modules
233             may or may not get loaded into different, arbitrary packages.
234              
235             For ease of use, it's recommended to use function prototypes to reduce the need for
236             parens.
237              
238             The method C<uav_module_init()> is called with the package name as the first argument.
239             Subsquent arguments will be the hashref passed to C<load()/load_lib()>. After being called,
240             this sub will be deleted from the package.