File Coverage

lib/Devel/Trepan/CmdProcessor/Command/Subcmd/SubMgr.pm
Criterion Covered Total %
statement 111 198 56.0
branch 16 62 25.8
condition 7 21 33.3
subroutine 19 27 70.3
pod n/a
total 153 308 49.6


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2             # Copyright (C) 2011-2015 Rocky Bernstein <rocky@cpan.org>
3              
4 12     12   5788 use warnings; use utf8;
  12     12   39  
  12         351  
  12         72  
  12         33  
  12         87  
5              
6             package Devel::Trepan::CmdProcessor::Command::SubcmdMgr;
7              
8 12     12   451 use File::Basename;
  12         31  
  12         799  
9 12     12   79 use File::Spec;
  12         31  
  12         366  
10 12     12   71 use if !@ISA, Devel::Trepan::CmdProcessor::Command;
  12         43  
  12         75  
11              
12 12     12   1922 use strict;
  12         37  
  12         398  
13 12     12   73 use vars qw(@ISA @EXPORT $HELP $NAME @ALIASES);
  12         38  
  12         954  
14             @ISA = @CMD_ISA;
15 12     12   79 use vars @CMD_VARS; # Value inherited from parent
  12         30  
  12         1253  
16              
17             $NAME = '?'; # FIXME: Need to define this, but should
18             # pick this up from class/file name.
19              
20             our $MIN_ARGS = 0;
21             our $MAX_ARGS = undef;
22             our $NEED_STACK = 0;
23              
24             # attr_accessor :subcmds # Trepan::Subcmd
25             # attr_reader :name # Name of command
26             # attr_reader :last_args # Last arguments seen
27              
28 12     12   82 no warnings 'redefine';
  12         33  
  12         4019  
29              
30             # Because we use Exporter we want to silence:
31             # Use of inherited AUTOLOAD for non-method ... is deprecated
32             sub AUTOLOAD
33             {
34 41     41   138 my $name = our $AUTOLOAD;
35 41         268 $name =~ s/.*:://; # lose package name
36 41         140 my $target = "DynaLoader::$name";
37 41         867 goto &$target;
38             }
39              
40             # Initialize show subcommands. Note: instance variable name
41             # has to be setcmds ('set' + 'cmds') for subcommand completion
42             # to work.
43             sub new($$)
44             {
45 55     55   304 my ($class, $proc, $name) = @_;
46 55         450 my @prefix = split('::', $class);
47 55         163 shift @prefix; shift @prefix; shift @prefix; shift @prefix;
  55         155  
  55         146  
  55         147  
48             my $self = {
49             subcmds => {},
50             name => $name,
51             proc => $proc,
52             prefix => \@prefix,
53 55         273 cmd_str => join(' ', map {lc $_} @prefix)
  55         506  
54             };
55             # Initialization
56 55         218 my $base_prefix="Devel::Trepan::CmdProcessor::Command::";
57 55         246 my $excluded_cmd_vars = {'$HELP' => 1, '$NAME'=>2};
58 55         217 for my $field (@CMD_VARS) {
59             next if exists $excluded_cmd_vars->{$field} &&
60 275 100 100     1303 $excluded_cmd_vars->{$field} == 2;
61 220         589 my $sigil = substr($field, 0, 1);
62 220 50       842 my $new_field = index('$@', $sigil) >= 0 ? substr($field, 1) : $field;
63 220 100       749 if ($sigil eq '$') {
64 165         406 my $lc_field = lc $new_field;
65 165         9522 $self->{$lc_field} = eval "\$${class}::${new_field}";
66             next if exists $excluded_cmd_vars->{$field} ||
67 165 50 66     1338 exists $self->{$lc_field};
68 0         0 $self->{$lc_field} = "\$${base_prefix}${new_field}";
69             }
70             }
71 12     12   92 no warnings;
  12         34  
  12         794  
72 55         2952 my @ary = eval "${class}::ALIASES()";
73 55 100       394 $self->{aliases} = @ary ? [@ary] : [];
74 12     12   85 no strict 'refs';
  12         43  
  12         20259  
75 55     1   3422 *{"${class}::Category"} = eval "sub { ${class}::CATEGORY() }";
  55         460  
  1         1713  
  1         1273  
  1         1584  
  1         1241  
  0         0  
  0         0  
  0         0  
  0         0  
76 55         2447 my $short_help = eval "${class}::SHORT_HELP()";
77 55 50       361 $self->{short_help} = $short_help if $short_help;
78 55         188 bless $self, $class;
79 55         434 $self->load_debugger_subcommands;
80 55         1725 $self;
81             }
82              
83             sub load_debugger_subcommand($$)
84             {
85 631     631   1933 my ($self, $parent_name, $pm) = @_;
86              
87 631 50       19268 return unless -r $pm;
88 631         1888 my $rc = '';
89 631         1443 eval { $rc = do $pm; };
  631         240904  
90 631 50 33     5337 return if !$rc or $rc eq 'Skip me!';
91              
92 631         30370 my $basename = basename($pm, '.pm');
93 631         4268 my $item = sprintf("%s::%s", ucfirst($parent_name), ucfirst($basename));
94 631 100       38524 if (-d File::Spec->catfile(dirname($pm), $basename . '_Subcmd')) {
95 167         659 push @{$self->{subcmd_names}}, $item;
  167         794  
96             } else {
97 464         1544 push @{$self->{cmd_names}}, $item;
  464         2012  
98 464         1138 push @{$self->{cmd_basenames}}, $basename;
  464         1432  
99             }
100 631 50       41681 if (eval "require '$pm'; 1") {
101 631         5309 return $self->setup_subcommand($parent_name, $basename);
102             } else {
103 0         0 $self->errmsg("Trouble reading ${pm}:");
104 0         0 $self->errmsg($@);
105 0         0 return 0;
106             }
107             }
108              
109             # Create an instance of each of the debugger subcommands. Commands are
110             # found by importing files in the directory 'name' + '_Subcmd'. Some
111             # files are excluded via an array set in initialize. For each of the
112             # remaining files, we 'require' them and scan for class names inside
113             # those files and for each class name, we will create an instance of
114             # that class. The set of TrepanCommand class instances form set of
115             # possible debugger commands.
116             sub load_debugger_subcommands($)
117             {
118 55     55   188 my ($self) = @_;
119 55         462 $self->{cmd_names} = ();
120 55         241 $self->{subcmd_names} = ();
121 55         171 $self->{cmd_basenames} = ();
122 55         1931 my $cmd_dir = dirname(__FILE__);
123 55         265 my $parent_name = ucfirst $self->{name};
124 55         829 my $subcmd_dir = File::Spec->catfile($cmd_dir, '..',
125             $parent_name . '_Subcmd');
126 55 50       1925 if (-d $subcmd_dir) {
127 55         14489 my @files = glob(File::Spec->catfile($subcmd_dir, '*.pm'));
128 55         345 for my $pm (@files) {
129 631         2959 $self->load_debugger_subcommand($parent_name, $pm);
130             }
131             }
132             }
133              
134             sub setup_subcommand($$$$)
135             {
136 631     631   2257 my ($self, $parent_name, $name) = @_;
137 631         1371 my $cmd_obj;
138 631         1725 my $cmd_name = lc $name;
139 631         2409 my $new_cmd = "\$cmd_obj=Devel::Trepan::CmdProcessor::Command::" .
140             "${parent_name}::${name}->new(\$self, '$cmd_name'); 1";
141 631 50       41589 if (eval $new_cmd) {
142             # Add to hash of commands, and list of subcmds
143 631         2882 $self->{subcmds}->{$cmd_name} = $cmd_obj;
144 631         4148 $self->add($cmd_obj, $cmd_name);
145 631         4252 return 1;
146             } else {
147 0         0 $self->errmsg("Error instantiating ${parent_name}::$name");
148 0         0 $self->errmsg($@);
149 0         0 return 0;
150             }
151              
152             }
153              
154             # Find subcmd in self.subcmds
155             sub lookup($$;$)
156             {
157 0     0   0 my ($self, $subcmd_prefix, $use_regexp) = @_;
158 0 0       0 $use_regexp = 0 if scalar @_ < 3;
159 0         0 my $compare;
160 0 0       0 if (!$self->{proc}{settings}{abbrev}) {
    0          
161 0     0   0 $compare = sub($) { my $name = shift; $name eq $subcmd_prefix};
  0         0  
  0         0  
162             } elsif ($use_regexp) {
163 0     0   0 $compare = sub($) { my $name = shift; $name =~ /^${subcmd_prefix}/};
  0         0  
  0         0  
164             } else {
165             $compare = sub($) {
166 0     0   0 my $name = shift; 0 == index($name, $subcmd_prefix)
  0         0  
167 0         0 };
168             }
169 0         0 my @candidates = ();
170 0         0 while (my ($subcmd_name, $subcmd) = each %{$self->{subcmds}}) {
  0         0  
171 0 0 0     0 if ($compare->($subcmd_name) &&
172             length($subcmd_prefix) >= $subcmd->{min_abbrev}) {
173 0         0 push @candidates, $subcmd;
174             }
175             }
176 0 0       0 if (scalar @candidates == 1) {
177 0         0 return $candidates[0];
178             }
179 0         0 return undef;
180             }
181              
182             # Show short help for a subcommand.
183             sub short_help($$$;$)
184             {
185 0     0   0 my ($self, $subcmd_cb, $subcmd_name, $label) = @_;
186 0 0       0 $label = 0 unless defined $label;
187 0         0 my $entry = $self->lookup($subcmd_name);
188 0 0       0 if ($entry) {
189 0         0 my $prefix = '';
190 0 0       0 $prefix = $entry->{name} if $label;
191 0 0       0 if (exist $entry->{short_help}) {
192 0 0       0 $prefix .= ' -- ' if $prefix;
193 0         0 $self->{proc}->msg($prefix . $entry->{short_help});
194             }
195             } else {
196 0         0 $self->{proc}->undefined_subcmd("help", $subcmd_name);
197             }
198             }
199              
200             # Add subcmd to the available subcommands for this object.
201             # It will have the supplied docstring, and subcmd_cb will be called
202             # when we want to run the command. min_len is the minimum length
203             # allowed to abbreviate the command. in_list indicates with the
204             # show command will be run when giving a list of all sub commands
205             # of this object. Some commands have long output like "show commands"
206             # so we might not want to show that.
207             sub add($$;$)
208             {
209 631     631   2098 my ($self, $subcmd_cb, $subcmd_name) = @_;
210 631   33     1983 $subcmd_name ||= $subcmd_cb->{name};
211              
212             # We keep a list of subcommands to assist command completion
213 631         1338 push @{$self->{cmdlist}}, $subcmd_name;
  631         2435  
214             }
215              
216             sub help($$)
217             {
218 0     0   0 my ($self, $args) = @_;
219 0 0       0 if (scalar @$args <= 2) {
220             # "help cmd". Give the general help for the command part.
221 0         0 return $self->{help};
222             }
223              
224 0         0 my $subcmd_name = $args->[2];
225 0         0 my @help_text = ();
226 0         0 my $subcmds_ref = $self->{subcmds};
227 0         0 my @subcmds = $self->list();
228              
229 0 0       0 if ('*' eq $subcmd_name) {
230             @help_text = (sprintf("B<List of subcommands for command I<%s>:>",
231 0         0 $self->{name}));
232 0         0 my $subcmds = $self->columnize_commands(\@subcmds); chomp $subcmds;
  0         0  
233 0         0 push @help_text, $subcmds;
234 0         0 return join("\n\n", @help_text);
235             }
236              
237             # "help cmd subcmd". Give help specific for that subcommand.
238 0         0 my $cmd = $self->lookup($subcmd_name, 0);
239 0 0       0 if (defined $cmd) {
240 0 0       0 if ($cmd->can("help")) {
241 0         0 return $cmd->help($args);
242             } else {
243 0         0 return $cmd->{help};
244             }
245             } else {
246 0         0 my $proc = $self->{proc};
247 0         0 my @matches = sort(grep /^$subcmd_name/, @subcmds);
248 0         0 my $name = $self->{name};
249 0 0       0 if (0 == scalar @matches) {
    0          
250 0         0 $proc->errmsg("No ${name} subcommands found matching /^{$subcmd_name}/. Try \"help $name *\".");
251 0         0 return undef;
252             } elsif (1 == scalar @matches) {
253 0         0 $args->[-1] = $matches[0];
254 0         0 $self->help($args);
255             } else {
256             # pod2text formatting used below. That's why B<>, I<> and
257             # \n\n for \n.
258 0         0 @help_text = ("B<Subcommands of I<$name> matching /^$subcmd_name/:>");
259 0         0 my @sort_matches = sort @matches;
260 0         0 push @help_text, $self->columnize_commands(\@sort_matches);
261 0         0 return join("\n\n", @help_text);
262             }
263             }
264             }
265              
266             sub list($) {
267 0     0   0 my $self = shift;
268 0         0 sort keys %{$self->{subcmds}};
  0         0  
269             }
270              
271             # # Return an Array of subcommands that can start with +arg+. If none
272             # # found we just return +arg+.
273             # # FIXME: Not used any more?
274             # sub complete(prefix)
275             # Trepan::Complete.complete_token(@subcmds.subcmds.keys, prefix)
276             # }
277              
278             sub complete_token_with_next($$;$)
279             {
280 10     10   27 my ($self, $prefix, $cmd_prefix) = @_;
281 10         27 my $subcmds = $self->{subcmds};
282 10         31 Devel::Trepan::Complete::complete_token_with_next($subcmds, $prefix);
283             }
284              
285             sub run($$)
286             {
287 0     0     my ($self, $args) = @_;
288 0           $self->{last_args} = $args;
289 0           my $args_len = scalar @$args;
290 0 0 0       if ($args_len < 2 || $args_len == 2 && $args->[-1] eq '*') {
      0        
291 0           $self->{proc}->summary_list($self->{name}, $self->{subcmds});
292 0           return 0;
293             }
294              
295 0           my $subcmd_prefix = $args->[1];
296             # We were given: cmd subcmd ...
297             # Run that.
298 0           my $subcmd = $self->lookup($subcmd_prefix);
299 0 0         if ($subcmd) {
300 0 0         if ($self->{proc}->ok_for_running($subcmd, $subcmd->{cmd_str},
301             $args_len-2)) {
302 0           $subcmd->run($args);
303             }
304             } else {
305 0           $self->{proc}->undefined_subcmd($self->{name}, $subcmd_prefix);
306             }
307             }
308              
309             unless(caller) {
310             # Demo it.
311             require Devel::Trepan::CmdProcessor;
312             my $cmdproc = Devel::Trepan::CmdProcessor->new(undef, 'bogus');
313             require Devel::Trepan::CmdProcessor::Command::Set;
314             my $mgr = Devel::Trepan::CmdProcessor::Command::Set->new($cmdproc, 'set');
315             printf "name: %s, cmd_str: %s\n", $mgr->{name}, $mgr->{cmd_str};
316             print "subcmds: ", join(', ', $mgr->list), "\n";
317             print $mgr->lookup('abbrev'), "\n";
318             }
319              
320             1;