File Coverage

blib/lib/App/Rad.pm
Criterion Covered Total %
statement 147 223 65.9
branch 42 86 48.8
condition 10 17 58.8
subroutine 27 42 64.2
pod 28 28 100.0
total 254 396 64.1


line stmt bran cond sub pod time code
1             package App::Rad;
2 9     9   297678 use 5.006;
  9         38  
  9         374  
3 9     9   4909 use App::Rad::Help;
  9         27  
  9         272  
4 9     9   57 use Carp ();
  9         17  
  9         158  
5 9     9   50 use warnings;
  9         15  
  9         285  
6 9     9   48 use strict;
  9         27  
  9         4670  
7              
8             our $VERSION = '1.05';
9             {
10              
11             #========================#
12             # INTERNAL FUNCTIONS #
13             #========================#
14              
15             my @OPTIONS = ();
16              
17             sub _init {
18 7     7   13256 my $c = shift;
19              
20             # instantiate references for the first time
21 7         74 $c->{'_ARGV' } = [];
22 7         25 $c->{'_options'} = {};
23 7         21 $c->{'_stash' } = {};
24 7         20 $c->{'_config' } = {};
25 7         18 $c->{'_plugins'} = [];
26              
27             # this internal variable holds
28             # references to all special
29             # pre-defined control functions
30 7         76 $c->{'_functions'} = {
31             'setup' => \&setup,
32             'pre_process' => \&pre_process,
33             'post_process' => \&post_process,
34             'default' => \&default,
35             'invalid' => \&invalid,
36             'teardown' => \&teardown,
37             };
38            
39             #load extensions
40 7         73 App::Rad::Help->load($c);
41 7         16 foreach (@OPTIONS) {
42 2 50       14 if ($_ eq 'include') {
    50          
    50          
43 0         0 eval 'use App::Rad::Include; App::Rad::Include->load($c)';
44 0 0       0 Carp::croak 'error loading "include" extension.' if ($@);
45             }
46             elsif ($_ eq 'exclude') {
47 0         0 eval 'use App::Rad::Exclude; App::Rad::Exclude->load($c)';
48 0 0       0 Carp::croak 'error loading "exclude" extension.' if ($@);
49             }
50             elsif ($_ eq 'debug') {
51 0         0 $c->{'debug'} = 1;
52             }
53             else {
54 2         8 $c->load_plugin($_);
55             }
56             }
57            
58             # tiny cheat to avoid doing a lot of processing
59             # when not in debug mode. If needed, I'll create
60             # an actual is_debugging() method or something
61 7 50       34 if ($c->{'debug'}) {
62 0         0 $c->debug('initializing: default commands are: '
63             . join ( ', ', $c->commands() )
64             );
65             }
66             }
67              
68             sub import {
69 9     9   98 my $class = shift;
70 9         7344 @OPTIONS = @_;
71             }
72              
73             sub load_plugin {
74 2     2 1 4 my $c = shift;
75 2         5 my $plugin = shift;
76 2         4 my $class = ref $c;
77              
78 2         3 my $plugin_fullname = '';
79 2 100       38 if ($plugin =~ s{^\+}{} ) {
80 1         12 $plugin_fullname = $plugin;
81             }
82             else {
83 1         3 $plugin_fullname = "App::Rad::Plugin::$plugin";
84             }
85 2     2   159 eval "use $plugin_fullname ()";
  2         1113  
  2         62  
  2         19  
86 2 50       8 Carp::croak "error loading plugin '$plugin_fullname': $@\n"
87             if $@;
88 2         9 my %methods = _get_subs_from($plugin_fullname);
89              
90 2 50       9 Carp::croak "No methods found for plugin '$plugin_fullname'\n"
91             unless keys %methods > 0;
92              
93 9     9   51 no strict 'refs';
  9         36  
  9         1700  
94 2         6 foreach my $method (keys %methods) {
95             # don't add plugin's internal methods
96 4 100       14 next if substr ($method, 0, 1) eq '_';
97              
98 2         3 *{"$class\::$method"} = $methods{$method};
  2         11  
99 2         11 $c->debug("-- method '$method' added [$plugin_fullname]");
100              
101             # fill $c->plugins()
102 2         3 push @{ $c->{'_plugins'} }, $plugin;
  2         14  
103             }
104             }
105              
106             # this function browses a file's
107             # symbol table (usually 'main') and maps
108             # each function to a hash
109             #
110             # FIXME: if I create a sub here (Rad.pm) and
111             # there is a global variable with that same name
112             # inside the user's program (e.g.: sub ARGV {}),
113             # the name will appear here as a command. It really
114             # shouldn't...
115             sub _get_subs_from {
116 3   50 3   12 my $package = shift || 'main';
117 3         7 $package .= '::';
118            
119 3         8 my %subs = ();
120              
121 9     9   53 no strict 'refs';
  9         18  
  9         27606  
122 3         5 while (my ($key, $value) = ( each %{*{$package}} )) {
  254         239  
  254         1078  
123 251         316 local (*SYMBOL) = $value;
124 251 100 66     5505 if ( defined $value && defined *SYMBOL{CODE} ) {
125 37         136 $subs{$key} = $value;
126             }
127             }
128 3         76 return %subs;
129             }
130              
131              
132             # overrides our pre-defined control
133             # functions with any available
134             # user-defined ones
135             sub _register_functions {
136 0     0   0 my $c = shift;
137 0         0 my %subs = _get_subs_from('main');
138              
139             # replaces only if the function is
140             # in 'default', 'pre_process' or 'post_process'
141 0         0 foreach ( keys %{$c->{'_functions'}} ) {
  0         0  
142 0 0       0 if ( defined $subs{$_} ) {
143 0         0 $c->debug("overriding $_ with user-defined function.");
144 0         0 $c->{'_functions'}->{$_} = $subs{$_};
145             }
146             }
147             }
148              
149             # retrieves command line arguments
150             # to be executed by the main program
151             sub _get_input {
152 2     2   10 my $c = shift;
153              
154 2 50 33     23 my $cmd = (defined ($ARGV[0]) and substr($ARGV[0], 0, 1) ne '-')
155             ? shift @ARGV
156             : ''
157             ;
158              
159 2         4 @{$c->argv} = @ARGV;
  2         9  
160 2         6 $c->{'cmd'} = $cmd;
161              
162 2         9 $c->debug('received command: ' . $c->{'cmd'});
163 2         3 $c->debug('received parameters: ' . join (' ', @{$c->argv} ));
  2         5  
164              
165 2         11 $c->_tinygetopt();
166             }
167              
168             # stores arguments passed to a
169             # command via --param[=value] or -p
170             sub _tinygetopt {
171 2     2   5 my $c = shift;
172              
173 2         4 my @argv = ();
174 2         4 foreach ( @{$c->argv} ) {
  2         5  
175              
176             # single option (could be grouped)
177 15 100       63 if ( m/^\-([^\-\=]+)$/o) {
    100          
178 6         22 my @args = split //, $1;
179 6         11 foreach (@args) {
180 12 100       23 if ($c->options->{$_}) {
181 3         7 $c->options->{$_}++;
182             }
183             else {
184 9         15 $c->options->{$_} = 1;
185             }
186             }
187             }
188             # long option: --name or --name=value
189             elsif (m/^\-\-([^\-\=]+)(?:\=(.+))?$/o) {
190 6 100       22 $c->options->{$1} = defined $2 ? $2
191             : 1
192             ;
193             }
194             else {
195 3         8 push @argv, $_;
196             }
197             }
198 2         5 @{$c->argv} = @argv;
  2         4  
199             }
200              
201              
202             #========================#
203             # PUBLIC METHODS #
204             #========================#
205              
206             sub load_config {
207 2     2 1 919 require App::Rad::Config;
208 2         8 App::Rad::Config::load_config(@_);
209             }
210              
211              
212             #TODO: this code probably could use some optimization
213             sub register_commands {
214 1     1 1 11 my $c = shift;
215 1         2 my %help_for_sub = ();
216 1         2 my %rules = ();
217              
218             # process parameters
219 1         2 foreach my $item (@_) {
220 1 50       3 if ( ref ($item) ) {
221 1 50       11 Carp::croak '"register_commands" may receive only HASH references'
222             unless ref ($item) eq 'HASH';
223 1         2 foreach my $params (keys %{$item}) {
  1         4  
224 3 50 100     19 if ($params eq '-ignore_prefix'
      66        
225             or $params eq '-ignore_suffix'
226             or $params eq '-ignore_regexp'
227             ) {
228 3         9 $rules{$params} = $item->{$params};
229             }
230             else {
231 0         0 $help_for_sub{$params} = $item->{$params};
232             }
233             }
234             }
235             else {
236 0         0 $help_for_sub{$item} = undef; # no help text
237             }
238             }
239              
240 1         4 my %subs = _get_subs_from('main');
241              
242 1         12 foreach (keys %help_for_sub) {
243              
244             # we only add the sub to the commands
245             # list if it's *not* a control function
246 0 0       0 if ( not defined $c->{'_functions'}->{$_} ) {
247              
248             # user want to register a valid (existant) sub
249 0 0       0 if ( exists $subs{$_} ) {
250 0         0 $c->debug("registering $_ as a command.");
251 0         0 $c->{'_commands'}->{$_}->{'code'} = $subs{$_};
252 0         0 App::Rad::Help->register_help($c, $_, $help_for_sub{$_});
253             }
254             else {
255 0         0 Carp::croak "'$_' does not appear to be a valid sub. Registering seems impossible.\n";
256             }
257             }
258             }
259              
260             # no parameters, or params+rules: try to register everything
261 1 50 33     7 if ((!%help_for_sub) or %rules) {
262 1         8 foreach my $subname (keys %subs) {
263              
264             # we only add the sub to the commands
265             # list if it's *not* a control function
266 33 100       78 if ( not defined $c->{'_functions'}->{$subname} ) {
267              
268 32 50       60 if ( $rules{'-ignore_prefix'} ) {
269 32 100       69 next if ( substr ($subname, 0, length($rules{'-ignore_prefix'}))
270             eq $rules{'-ignore_prefix'}
271             );
272             }
273 31 50       56 if ( $rules{'-ignore_suffix'} ) {
274 31 100       77 next if ( substr ($subname,
275             length($subname) - length($rules{'-ignore_suffix'}),
276             length($rules{'-ignore_suffix'})
277             )
278             eq $rules{'-ignore_suffix'}
279             );
280             }
281 30 50       55 if ( $rules{'-ignore_regexp'} ) {
282 30         33 my $re = $rules{'-ignore_regexp'};
283 30 100       76 next if $subname =~ m/$re/o;
284             }
285              
286             # avoid duplicate registration
287 29 50       54 if ( !exists $help_for_sub{$subname} ) {
288 29         88 $c->{'_commands'}->{$subname}->{'code'} = $subs{$subname};
289 29         78 App::Rad::Help->register_help($c, $subname, undef);
290             }
291             }
292             }
293             }
294             }
295              
296              
297 0     0 1 0 sub register_command { return register(@_) }
298             sub register {
299 7     7 1 25 my ($c, $command_name, $coderef, $helptext) = @_;
300 7         42 $c->debug("got: " . ref $coderef);
301             return undef
302 7 50       31 unless ( (ref $coderef) eq 'CODE' );
303              
304 7         37 $c->debug("registering $command_name as a command.");
305 7         178 $c->{'_commands'}->{$command_name}->{'code'} = $coderef;
306 7         46 App::Rad::Help->register_help($c, $command_name, $helptext);
307 7         15 return $command_name;
308             }
309              
310 0     0 1 0 sub unregister_command { return unregister(@_) }
311             sub unregister {
312 0     0 1 0 my ($c, $command_name) = @_;
313              
314 0 0       0 if ( $c->{'_commands'}->{$command_name} ) {
315 0         0 delete $c->{'_commands'}->{$command_name};
316             }
317             else {
318 0         0 return undef;
319             }
320             }
321              
322              
323             sub create_command_name {
324 0     0 1 0 my $id = 0;
325 0         0 foreach (commands()) {
326 0 0       0 if ( m/^cmd(\d+)$/ ) {
327 0 0       0 $id = $1 if ($1 > $id);
328             }
329             }
330 0         0 return 'cmd' . ($id + 1);
331             }
332              
333              
334             sub commands {
335 0     0 1 0 return ( keys %{$_[0]->{'_commands'}} );
  0         0  
336             }
337              
338              
339             sub is_command {
340 7     7 1 21 my ($c, $cmd) = @_;
341 7 100       62 return (defined $c->{'_commands'}->{$cmd}
342             ? 1
343             : 0
344             );
345             }
346              
347 1     1 1 7 sub command :lvalue { cmd(@_) }
348             sub cmd :lvalue {
349 7     7 1 70 $_[0]->{'cmd'};
350             }
351              
352              
353             sub run {
354 0     0 1 0 my $class = shift;
355 0         0 my $c = {};
356 0         0 bless $c, $class;
357              
358 0         0 $c->_init();
359              
360             # first we update the control functions
361             # with any overriden value
362 0         0 $c->_register_functions();
363              
364             # then we run the setup to register
365             # some commands
366 0         0 $c->{'_functions'}->{'setup'}->($c);
367              
368             # now we get the actual input from
369             # the command line (someone using the app!)
370 0         0 $c->_get_input();
371              
372             # run the specified command
373 0         0 $c->execute();
374              
375             # that's it. Tear down everything and go home :)
376 0         0 $c->{'_functions'}->{'teardown'}->($c);
377              
378 0         0 return 0;
379             }
380              
381             # run operations
382             # in a shell-like environment
383             #sub shell {
384             # my $class = shift;
385             # App::Rad::Shell::shell($class);
386             #}
387              
388             sub execute {
389 0     0 1 0 my ($c, $cmd) = @_;
390              
391             # given command has precedence
392 0 0       0 if ($cmd) {
393 0         0 $c->{'cmd'} = $cmd;
394             }
395             else {
396 0         0 $cmd = $c->{'cmd'}; # now $cmd always has the called cmd
397             }
398              
399 0         0 $c->debug('calling pre_process function...');
400 0         0 $c->{'_functions'}->{'pre_process'}->($c);
401              
402 0         0 $c->debug("executing '$cmd'...");
403              
404             # valid command, run it
405 0 0       0 if ($c->is_command($c->{'cmd'}) ) {
    0          
406 0         0 $c->{'output'} = $c->{'_commands'}->{$cmd}->{'code'}->($c);
407             }
408             # no command, run default()
409             elsif ( $cmd eq '' ) {
410 0         0 $c->debug('no command detected. Falling to default');
411 0         0 $c->{'output'} = $c->{'_functions'}->{'default'}->($c);
412             }
413             # invalid command, run invalid()
414             else {
415 0         0 $c->debug("'$cmd' is not a valid command. Falling to invalid.");
416 0         0 $c->{'output'} = $c->{'_functions'}->{'invalid'}->($c);
417             }
418              
419             # 3: post-process the result
420             # from the command
421 0         0 $c->debug('calling post_process function...');
422 0         0 $c->{'_functions'}->{'post_process'}->($c);
423              
424 0         0 $c->debug('reseting output');
425 0         0 $c->{'output'} = undef;
426             }
427              
428 12     12 1 562 sub argv { return $_[0]->{'_ARGV'} }
429 56     56 1 230 sub options { return $_[0]->{'_options'} }
430 4     4 1 1387 sub stash { return $_[0]->{'_stash'} }
431 28     28 1 176 sub config { return $_[0]->{'_config'} }
432              
433             # $c->plugins is sort of "read-only" externally
434             sub plugins {
435 2     2 1 1660 my @plugins = @{$_[0]->{'_plugins'}};
  2         8  
436 2         7 return @plugins;
437             }
438              
439              
440             sub getopt {
441 1     1 1 10 require Getopt::Long;
442 1 50       4 Carp::croak "Getopt::Long needs to be version 2.36 or above"
443             unless $Getopt::Long::VERSION >= 2.36;
444              
445 1         4 my ($c, @options) = @_;
446              
447             # reset values from tinygetopt
448 1         3 $c->{'_options'} = {};
449              
450 1         7 my $parser = new Getopt::Long::Parser;
451 1         21 $parser->configure( qw(bundling) );
452              
453 1         42 my @tARGV = @ARGV; # we gotta stick to our API
454 1         4 my $ret = $parser->getoptions($c->{'_options'}, @options);
455 1         930 @{$c->argv} = @ARGV;
  1         3  
456 1         2 @ARGV = @tARGV;
457              
458 1         12 return $ret;
459             }
460              
461             sub debug {
462 23 50   23 1 95 if (shift->{'debug'}) {
463 0         0 print "[debug] @_\n";
464             }
465             }
466              
467             # gets/sets the output (returned value)
468             # of a command, to be post processed
469             sub output {
470 0     0 1 0 my ($c, @msg) = @_;
471 0 0       0 if (@msg) {
472 0         0 $c->{'output'} = join(' ', @msg);
473             }
474             else {
475 0         0 return $c->{'output'};
476             }
477             }
478              
479              
480             #=========================#
481             # CONTROL FUNCTIONS #
482             #=========================#
483              
484 0     0 1 0 sub setup { $_[0]->register_commands( {-ignore_prefix => '_'} ) }
485              
486 0     0 1 0 sub teardown {}
487              
488 0     0 1 0 sub pre_process {}
489              
490             sub post_process {
491 0     0 1 0 my $c = shift;
492              
493 0 0       0 if ($c->output()) {
494 0         0 print $c->output() . $/;
495             }
496             }
497              
498              
499             sub default {
500 0     0 1 0 my $c = shift;
501 0         0 return $c->{'_commands'}->{'help'}->{'code'}->($c);
502             }
503              
504              
505             sub invalid {
506 0     0 1 0 my $c = shift;
507 0         0 return $c->{'_functions'}->{'default'}->($c);
508             }
509              
510              
511             }
512             42; # ...and thus ends thy module ;)
513             __END__