File Coverage

lib/Term/RouterCLI.pm
Criterion Covered Total %
statement 84 567 14.8
branch 1 142 0.7
condition 0 69 0.0
subroutine 19 50 38.0
pod 0 20 0.0
total 104 848 12.2


line stmt bran cond sub pod time code
1             #####################################################################
2             # This program is not guaranteed to work at all, and by using this #
3             # program you release the author of any and all liability. #
4             # #
5             # You may use this code as long as you are in compliance with the #
6             # license (see the LICENSE file) and this notice, disclaimer and #
7             # comment box remain intact and unchanged. #
8             # #
9             # Package: Term #
10             # Class: RouterCLI #
11             # Description: Methods for building a Router (Stanford) style CLI #
12             # #
13             # This class is a fork and major rewrite of Term::ShellUI v0.98 #
14             # which was written by Scott Bronson. #
15             # #
16             # Written by: Bret Jordan (jordan at open1x littledot org) #
17             # Created: 2011-02-21 #
18             #####################################################################
19             #
20             #
21             #
22             #
23             package Term::RouterCLI;
24              
25 4     4   12142 use 5.8.8;
  4         16  
  4         321  
26 4     4   20 use strict;
  4         8  
  4         109  
27 4     4   150 use warnings;
  4         6  
  4         142  
28 4     4   3722 use parent qw(Term::RouterCLI::Base);
  4         1583  
  4         21  
29              
30 4     4   288 use Term::RouterCLI::Debugger;
  4         9  
  4         77  
31 4     4   2016 use Term::RouterCLI::Auth;
  4         14  
  4         142  
32 4     4   2499 use Term::RouterCLI::Log::History;
  4         12  
  4         116  
33 4     4   2393 use Term::RouterCLI::Log::Audit;
  4         11  
  4         146  
34 4     4   37 use Term::RouterCLI::Config;
  4         9  
  4         100  
35 4     4   2068 use Term::RouterCLI::CommandTree qw(:all);
  4         9  
  4         878  
36 4     4   2091 use Term::RouterCLI::Help qw(:all);
  4         12  
  4         1350  
37 4     4   2231 use Term::RouterCLI::Prompt qw(:all);
  4         8  
  4         609  
38              
39 4     4   4458 use Term::ReadLine();
  4         15301  
  4         208  
40 4     4   4210 use Text::Shellwords::Cursor;
  4         10346  
  4         141  
41 4     4   30 use Config::General;
  4         8  
  4         232  
42 4     4   5016 use Sys::Syslog qw(:DEFAULT setlogsock);
  4         63028  
  4         893  
43 4     4   5838 use Env qw(SSH_TTY);
  4         13290  
  4         29  
44 4     4   734 use Log::Log4perl;
  4         10  
  4         54  
45              
46             our $VERSION = '1.00';
47             $VERSION = eval $VERSION;
48              
49              
50             my $oDebugger = new Term::RouterCLI::Debugger();
51             my $oConfig = new Term::RouterCLI::Config();
52              
53              
54             sub _init
55             {
56 3     3   10 my $self = shift;
57 3         19 my %hParameters = @_;
58            
59             # Application data
60 3         28 $self->{_sConfigFilename} = './etc/RouterCLI.conf';
61 3         10 $self->{_sDebuggerConfigFilename} = './etc/log4perl.conf';
62 3         13 $self->{_sCurrentPrompt} = "Router> ";
63 3         6 $self->{_sCurrentPromptLevel} = '> ';
64 3         9 $self->{_sActiveLoggedOnUser} = "";
65 3         9 $self->{_sTTYInUse} = "localhost";
66 3         12 $self->{_iExit} = 0;
67 3         12 $self->{OUT} = undef;
68              
69             # Options
70 3         9 $self->{blank_repeats_cmd} = 0;
71 3         8 $self->{backslash_continues_command} = 1; # This allows commands to be entered across multiple lines
72 3         9 $self->{display_summary_in_help} = 1;
73 3         11 $self->{display_subcommands_in_help} = 1;
74 3         8 $self->{suppress_completion_escape} = 0;
75              
76             # Text::Shellwords::Cursor module options
77 3         10 $self->{_sTokenCharacters} = '';
78 3         13 $self->{_iKeepQuotes} = 1;
79              
80             # Lets overwrite any defaults with values that are passed in
81 3 50       13 if (%hParameters)
82             {
83 3         15 foreach (keys (%hParameters)) { $self->{$_} = $hParameters{$_}; }
  6         17  
84             }
85              
86             # We need to make sure the debugger always starts, and starts really before everything else
87             # so lets start it here in the _init script
88 3         33 $oDebugger->SetFilename( $self->{_sDebuggerConfigFilename} );
89 3         17 $oDebugger->StartDebugger();
90              
91             # Load the current configuration in to memory, this has to be done before we load command trees
92 3         15568 $oConfig->SetFilename( $self->{_sConfigFilename} );
93 3         19 $oConfig->LoadConfig();
94              
95              
96             # Objects
97 3         114 $self->{_oAuditLog} = new Term::RouterCLI::Log::Audit( _oParent => $self, _sFilename => './logs/.cli-auditlog' );
98 3         33 $self->{_oHistory} = new Term::RouterCLI::Log::History( _oParent => $self, _sFilename => './logs/.cli-history' );
99 3         47 $self->{_oTerm} = new Term::ReadLine("$0");
100             $self->{_oParser} = Text::Shellwords::Cursor->new(
101             token_chars => $self->{_sTokenCharacters},
102             keep_quotes => $self->{_iKeepQuotes},
103             debug => 0,
104 0     0   0 error => sub { shift; $self->error(@_); },
  0         0  
105 3         77058 );
106              
107             # Create object for terminal and define some initial values
108 3         326 $self->{_oTerm}->MinLine(0);
109 3         1822 $self->{_oTerm}->parse_and_bind("\"?\": complete");
110 0     0     $self->{_oTerm}->Attribs->{completion_function} = sub { _CompletionFunction($self, @_); };
  0            
111 0           $self->SetOutput("term");
112              
113             # Setup Data structure
114 0           $self->{_hFullCommandTree} = undef; # Full command tree for active session
115 0           $self->RESET();
116              
117             # Lets capture the tty that they used to connected to the CLI
118 0 0         if (defined $SSH_TTY) { $self->{_sTTYInUse} = $SSH_TTY; }
  0            
119             }
120              
121             sub RESET
122             {
123             # This method will reset the data structure
124 0     0 0   my $self = shift;
125             # Data structure
126 0           $self->{_hCommandTreeAtLevel} = undef; # Command tree at current level for searching for a command
127 0           $self->{_hCommandDirectives} = undef; # Directives of command found at deepest level
128 0           $self->{_aFullCommandName} = undef; # Full name of deepest command
129 0           $self->{_aCommandArguments} = undef; # All remaining arguments once command is determined
130              
131             # Data structure helper values
132 0           $self->{_sStringToComplete} = ""; # The exact string that needs to be tab completed
133 0           $self->{_sCompleteRawline} = ""; # Pre-tokenized command line
134 0           $self->{_iStringToCompleteTextStartPosition} = 0; # Position in _sCompleteRawline of the start of _sStringToComplete
135 0           $self->{_iCurrentCursorLocation} = 0; # Position in _sCompleteRawline of the cursor (end of _sStringToComplete)
136 0           $self->{_aCommandTokens} = undef; # Tokenized command-line
137 0           $self->{_iTokenNumber} = 0; # The index of the token containing the cursor
138 0           $self->{_iTokenOffset} = 0; # the character offset of the cursor in $tokno.
139 0           $self->{_iArgumentNumber} = 0; # The argument number containing the cursor
140 0           $self->{_iNumberOfContinuedLines} = 0; # The number of lines that have been entered in wrapped line continue mode
141 0           $self->{_sPreviousCommand} = "";
142             }
143              
144              
145             # ----------------------------------------
146             # Public Convenience Methods
147             # ----------------------------------------
148 0     0 0   sub EnableAuditLog { shift->{_oAuditLog}->Enable(); }
149 0     0 0   sub DisableAuditLog { shift->{_oAuditLog}->Disable(); }
150 0     0 0   sub SetAuditLogFilename { shift->{_oAuditLog}->SetFilename(@_); }
151 0     0 0   sub SetAuditLogFileLength { shift->{_oAuditLog}->SetFileLength(@_); }
152              
153 0     0 0   sub EnableHistory { shift->{_oHistory}->Enable(); }
154 0     0 0   sub DisableHistory { shift->{_oHistory}->Disable(); }
155 0     0 0   sub SetHistoryFilename { shift->{_oHistory}->SetFilename(@_); }
156 0     0 0   sub SetHistoryFileLength { shift->{_oHistory}->SetFileLength(@_); }
157 0     0 0   sub PrintHistory { shift->{_oHistory}->PrintHistory(@_); }
158              
159              
160              
161             # ----------------------------------------
162             # Public Methods
163             # ----------------------------------------
164             sub SaveConfig
165             {
166             # This method is used for saving the current running configuration
167 0     0 0   my $self = shift;
168 0           $oConfig->SaveConfig();
169             }
170              
171             sub ClearScreen
172             {
173             # This method will clear the screen from all login information
174 0     0 0   my $self = shift;
175 0           print `clear`;
176             }
177              
178             sub PrintMOTD
179             {
180             # This method will print out a welcome message
181 0     0 0   my $self = shift;
182 0           my $config = $oConfig->GetRunningConfig();
183 0           print "\n\n$config->{motd}->{text}\n";
184             }
185              
186             sub SetHostname
187             {
188             # This method will set the hostname
189 0     0 0   my $self = shift;
190 0           my $parameter = shift;
191 0           my $config = $oConfig->GetRunningConfig();
192            
193 0 0         unless (defined $parameter) { $parameter = $self->{_aCommandArguments}->[0]; }
  0            
194 0           $config->{hostname} = $parameter;
195             # When ever the hostname is changes, we need to refresh the prompt
196 0           $self->SetPrompt($parameter);
197             }
198              
199             sub StartCLI
200             {
201             # This method will start the actual processing of the CLI
202 0     0 0   my $self = shift;
203 0           my $config = $oConfig->GetRunningConfig();
204 0 0         $self->{_oAuditLog}->StartAuditLog() if ($self->{_oAuditLog}->{_bEnabled} == 1 );
205            
206 0 0         unless (defined $self->{_hFullCommandTree}) { die "Please load an initial command tree\n"; }
  0            
207            
208             # Set prompt from configuration file
209 0           $self->ClearPromptOrnaments();
210 0           $self->SetPrompt($config->{hostname});
211            
212            
213             # Load the previous command history in to memory
214 0 0         $self->{_oHistory}->LoadCommandHistoryFromFile() if ($self->{_oHistory}->{_bEnabled} == 1 );
215              
216 0           while($self->{_iExit} == 0)
217             {
218 0           $self->_ProcessCommands();
219             }
220            
221             # Close AuditLog and save command History
222 0 0         $self->{_oHistory}->SaveCommandHistoryToFile() if ($self->{_oHistory}->{_bEnabled} == 1 );
223 0           $self->{_oHistory}->CloseFileHandle();
224 0           $self->{_oAuditLog}->CloseFileHandle();
225             }
226              
227             sub SetOutput
228             {
229             # This method will define where the output goes
230             # Required:
231             # string (term/stdout)
232 0     0 0   my $self = shift;
233 0   0       my $parameter = shift || "";
234 0 0 0       if ($parameter eq "term") {$self->{OUT} = $self->{_oTerm}->OUT || \*STDOUT;}
  0            
235 0           else { $self->{OUT} = \*STDOUT; }
236             }
237              
238             sub Exit
239             {
240             # This method will cause the CLI to exit
241 0     0 0   shift->{_iExit} = 1;
242             }
243              
244             sub PreventEscape
245             {
246             # This method will capture the various signals and prevent termination and esacpe through control characters
247             # Turn off the following CTRLs
248 0     0 0   my $self = shift;
249 0           $self->{_oTerm}->Attribs->{'catch_signals'} = 0;
250 0           system("stty eof \"?\""); # CTRL-D
251 0           $SIG{"INT"} = 'IGNORE'; # CTRL-C
252 0           $SIG{"TSTP"} = 'IGNORE'; # CTRL-Z
253 0           $SIG{"QUIT"} = 'IGNORE'; # CTRL-\
254 0           $SIG{"TERM"} = 'IGNORE';
255 0           $SIG{"ABRT"} = 'IGNORE';
256 0           $SIG{"SEGV"} = 'IGNORE';
257 0           $SIG{"ILL"} = 'IGNORE';
258             }
259              
260             sub TabCompleteArguments
261             {
262             # This method will provide tab completion for the "help" arguments and "no" arguments
263             # Required:
264             # hash_ref (full data structure)
265 0     0 0   my $self = shift;
266              
267             # Lets backup the data structure before we run the _CompleteFunction again
268 0           my $sStringToCompleteBackup = $self->{_sStringToComplete};
269 0           my $sCompleteRawlineBackup = $self->{_sCompleteRawline};
270 0           my $aFullCommandNameBackup = $self->{_aFullCommandName};
271 0           my $hCommandTreeAtLevelBackup = $self->{_hCommandTreeAtLevel};
272 0           my $hCommandDirectivesBackup = $self->{_hCommandDirectives};
273            
274 0           my ($sArgsToComplete) = $self->_GetFullArgumentsName();
275 0 0         $self->_CompletionFunction("NONE", $sArgsToComplete) unless ($sArgsToComplete eq "");
276              
277             # Lets grab what came back, which is really arguments, and put it in the arguments array
278 0           $self->{_aCommandArguments} = $self->{_aFullCommandName};
279            
280             # Lets now restore the command name from the beginning along with the command directives
281 0           $self->{_sStringToComplete} = $sStringToCompleteBackup;
282 0           $self->{_sCompleteRawline} = $sCompleteRawlineBackup;
283 0           $self->{_aFullCommandName} = $aFullCommandNameBackup;
284 0           $self->{_hCommandTreeAtLevel} = $hCommandTreeAtLevelBackup;
285 0           $self->{_hCommandDirectives} = $hCommandDirectivesBackup;
286              
287             # TODO look at this and see if I need it
288             # without this we'd complete with $shCommandTreeAtLevel for all further args
289             #return [] if $self->{_iArgumentNumber} >= @{$self->{_aFullCommandName}};
290             }
291              
292             sub error
293             {
294 0     0 0   my $self = shift;
295 0           print STDERR @_;
296             }
297              
298              
299              
300              
301              
302             # ----------------------------------------
303             # Private Methods
304             # ----------------------------------------
305             sub _ProcessCommands
306             {
307             # This method prompts for and returns the results from a single command. Returns undef if no command was called.
308 0     0     my $self = shift;
309 0           my $logger = $oDebugger->GetLogger($self);
310 0           my $config = $oConfig->GetRunningConfig();
311            
312             # Before we get started, lets clear out the data structure from the last command we processed
313 0           $self->RESET();
314              
315 0           my $iSaveToHistory = 1;
316 0           my $sPrompt;
317              
318 0           my $OUT = $self->{'OUT'};
319              
320            
321             # Setup an infinte loop to catch all of the commands entered on the console makeing sure
322             # to watch for "\" continue to next line characters
323 0           for(;;)
324             {
325 0           $sPrompt = $self->GetPrompt();
326              
327             # This next command is where we jump to the _CompletionFunction method and it does not come
328             # back to this fucntion until the enter key is pressed
329             # TODO we need to make sure the readline is returning a valid option with "?" is pressed
330 0           my $sNewline = $self->{_oTerm}->readline($sPrompt);
331            
332 0 0         if (defined $sNewline)
333             {
334 0           $logger->debug("$self->{'_sName'} - Newline returned from readline: $sNewline");
335             }
336              
337             # In the off chance that the readline module does not return anything, lets just print a new line and go on.
338 0 0         unless (defined $sNewline)
339             {
340 0 0 0       if (!exists $self->{_aFullCommandName}->[0] || $self->{_aFullCommandName}->[0] eq "")
341             {
342             # Print out possible options for the matches that were found. This was added once
343             # "?" based completion was added
344 0           print $OUT "\n";
345 0           print $OUT $self->_GetCommandSummaries();
346            
347             # We need to redraw the prompt and command line options since we are going to output text via _GetCommandSummaries
348 0           $self->{_oTerm}->rl_on_new_line();
349 0           return;
350             }
351             else
352             {
353 0           print $OUT "\n";
354 0           $self->{_oTerm}->rl_on_new_line();
355 0           return;
356             }
357             }
358              
359             # If there is any white space at the start or end of the command lets remove it just to be safe
360 0           $sNewline =~ s/^\s+//g;
361 0           $sNewline =~ s/\s+$//g;
362              
363              
364             # Search for a "\" at the end as a continue character and remove it along with any white space
365             # if one was found lets set bContinued to TRUE so we know that we need more commands. Lets
366             # also keep track of the number of lines that are continued. This makes the logic easier down
367             # below.
368 0           my $bContinued = 0;
369 0 0         if ($self->{backslash_continues_command} == 1)
370             {
371 0           $bContinued = ($sNewline =~ s/\s*\\$/ /);
372 0 0         if ($bContinued == 1) { $self->{_iNumberOfContinuedLines} = $self->{_iNumberOfContinuedLines} + $bContinued; }
  0            
373             }
374            
375            
376 0           $logger->debug("$self->{'_sName'} - _iNumberOfContinuedLines: $self->{_iNumberOfContinuedLines}");
377 0           $logger->debug("$self->{'_sName'} - bContinued: $bContinued");
378              
379             # Lets concatenate the lines together to form a single command
380 0 0 0       if (($self->{backslash_continues_command} == 1) && ($self->{_iNumberOfContinuedLines} > 0))
381             {
382 0           $self->{_sCompleteRawline} = $self->{_sCompleteRawline} . $sNewline;
383 0 0         if ($bContinued == 1) { next; }
  0            
384             }
385 0           else { $self->{_sCompleteRawline} = $sNewline; }
386              
387             # This will allow us to enter partial commands on the command line and have them completed
388 0           $logger->debug("$self->{'_sName'} - _sCompleteRawline: $self->{_sCompleteRawline}");
389 0 0         $self->_CompletionFunction("NONE", $self->{_sCompleteRawline}, "0") unless ($self->{_sCompleteRawline} eq "");
390 0           last;
391             }
392              
393             # Is this a blank line? If so, then we might need to repeat the last command
394 0 0         if ($self->{_sCompleteRawline} =~ /^\s*$/)
395             {
396 0 0 0       if ($self->{blank_repeats_cmd} && $self->{_sPreviousCommand} ne "")
397             {
398 0           $self->{_oTerm}->rl_forced_update_display();
399 0           print $OUT $self->{_sPreviousCommand};
400 0           $self->_CompletionFunction("NONE", $self->{_sPreviousCommand});
401             }
402 0           else { $self->{_sCompleteRawline} = undef; }
403 0 0 0       return unless ((defined $self->{_sCompleteRawline}) && ($self->{_sCompleteRawline} !~ /^\s*$/));
404             }
405              
406 0           my $sCommandString = undef;
407              
408 0 0         if (exists $self->{_aFullCommandName})
409             {
410 0           my ($sCommandName) = $self->_GetFullCommandName();
411 0           my ($sCommandArgs) = $self->_GetFullArgumentsName();
412 0           $sCommandString = $sCommandName . $sCommandArgs;
413              
414 0           $self->_RunCodeDirective();
415              
416              
417             # TODO we need to make sure that sub commands can inherit the hidden flag from the parent
418             # If the command has an exclude from history or hidden option attached to it, lets NOT record it in the history file
419 0 0 0       if (exists $self->{_hCommandDirectives}->{exclude_from_history} || exists $self->{_hCommandDirectives}->{hidden})
420             {
421 0           $iSaveToHistory = 0;
422             }
423             }
424              
425             # Add to history unless it's a dupe of the previous command.
426 0 0 0       if (($iSaveToHistory == 1) && ($sCommandString ne $self->{_sPreviousCommand}) && ($self->{_oHistory}->{_bEnabled} == 1 ))
      0        
427             {
428 0           $self->{_oTerm}->addhistory($sCommandString);
429             }
430 0           $self->{_sPreviousCommand} = $sCommandString;
431            
432              
433            
434             # Lets save the typed in command to the audit log if the audit log is enabled and after the
435             # commands have been tab completed
436 0 0         if ($self->{_oAuditLog}->{_bEnabled} == 1)
437             {
438 0           my $hAuditData = { "username" => $self->{_sActiveLoggedOnUser}, "tty" => $self->{_sTTYInUse}, "prompt" => $sPrompt, "commands" => $sCommandString};
439 0           $self->{_oAuditLog}->RecordToLog($hAuditData);
440             }
441            
442             # TODO build a logger that all of this will go in to
443             # TODO add support to send history to RADIUS in the form of RADIUS account records
444             # TODO add support for sending history to syslog server
445             # if (($iSaveToHistory == 1) && ($config->{syslog} == 1))
446             # {
447             # setlogsock('udp');
448             # $Sys::Syslog::host = $config->{syslog_server};
449             # my $sTimeStamp = strftime "%Y-%b-%e %a %H:%M:%S", localtime; # I removed the use POSIX that imported the strftime function
450             # openlog("RouterCLI", 'ndelay', 'user');
451             # syslog('info', "($sTimeStamp) \[$sPrompt\] $sCommandString");
452             # closelog;
453             # }
454              
455 0           return;
456             }
457              
458             sub _GetFullCommandName
459             {
460             # This method will take in an array reference of the commands and return a single string value and the
461             # length of the string as an array
462             # Required:
463             # $self->{_aFullCommandName} array_ref (commands typed in on the CLI)
464             # Return:
465             # string (full command name)
466             # int (length of command name)
467 0     0     my $self = shift;
468 0           my $sCommandName = join(" ", @{$self->{_aFullCommandName}});
  0            
469 0           $sCommandName = $sCommandName . " ";
470 0           $sCommandName =~ s/^\s+//g;
471 0           my $iCommandLength = length($sCommandName);
472 0           return ($sCommandName, $iCommandLength);
473             }
474              
475             sub _GetFullArgumentsName
476             {
477             # This method will take in an array reference of the command arguments and return a single string value
478             # and the length of the string as an array
479             # Required:
480             # $self->{_aCommandArguments} array_ref (command arguments typed in on the CLI)
481             # Return:
482             # string (full command argument name minus the space at the end as you want to leave the cursor at the end)
483             # int (length of argument name)
484 0     0     my $self = shift;
485 0           my $sArgumentName = join(" ", @{$self->{_aCommandArguments}});
  0            
486 0           $sArgumentName =~ s/^\s+//g;
487 0           my $iArgumentNameLength = length($sArgumentName);
488 0           return ($sArgumentName, $iArgumentNameLength);
489             }
490              
491             sub _FindCommandInCommandTree
492             {
493             # This method will attempt to looks up the supplied commands from the $self->{_aCommandTokens}
494             # array_ref in the command tree hash. It will follows all synonyms and subcommands in an effort
495             # to find the command that the user typed in. After it finds all of the commands it can
496             # find, it will store the remaining data in to the _aCommandArgument array.
497             # Required:
498             # hash_ref $self->{_hCommandTreeAtLevel} (command tree)
499             # array_ref $self->{_aCommandTokens} (typed in commands/tokens) these have already been
500             # split on whitespace by Text::Shellwords::Cursor
501             #
502             # Variables set in the object:
503             # _hCurrentCommandTreeAtLevel: The deepest command tree set found. Always returned.
504             # _hCommandDirectives: The command directives hash for the command. Sets an empty hash if
505             # no command was found.
506             # _aFullCommandName: The full name of the command. This is an array of tokens,
507             # i.e. ('show', 'info'). Returns as deep as it could find commands.
508             # _aCommandArguments: The command's arguments (all remaining tokens after the command is found).
509              
510 0     0     my $self = shift;
511 0           my $logger = $oDebugger->GetLogger($self);
512 0           $logger->debug("$self->{'_sName'} - ", '### Entering Method ###');
513            
514 0           my $aCommandTokens = $self->{_aCommandTokens};
515 0           my $hCommandTree = $self->GetFullCommandTree();
516 0           my $hCommandDirectives = undef;
517 0           my $iCurrentToken = 0;
518 0           my $iNumberOfTokens = @$aCommandTokens;
519 0           my @aFullCommandName;
520             my @aCommandArguments;
521              
522              
523 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 0-- Initial variable values");
524 0           $logger->debug("$self->{'_sName'} - \thCommandTree: ", ${$oDebugger->DumpHashKeys($hCommandTree)});
  0            
525 0           $logger->debug("$self->{'_sName'} - \taCommandTokens: ", ${$oDebugger->DumpArray($aCommandTokens)});
  0            
526 0           $logger->debug("$self->{'_sName'} - \tiCurrentToken: $iCurrentToken");
527 0           $logger->debug("$self->{'_sName'} - \tiNumberOfTokens: $iNumberOfTokens\n");
528              
529 0           foreach my $sToken (@$aCommandTokens)
530             {
531             # If the user has already gone beyond the number of args, then lets not complete and lets return
532             # an empty array so that things stop
533             # TODO write a unit test for this and we need to figure out how to track if the show has a maxargs of 3 but
534             # int does not a maxargs entry.
535             # my $iMaxArgCheck = 0;
536             # my $iCurrentTokenInMaxArgCheck = $iCurrentToken;
537             # foreach (@$aCommandTokens)
538             # {
539             # $iMaxArgCheck = 1 if ((exists($self->{hCommandTree}->{$_}->{maxargs})) && ($iCurrentTokenInMaxArgCheck >= $self->{hCommandTree}->{$_}->{maxargs}));
540             # $iCurrentTokenInMaxArgCheck--;
541             # }
542             # $logger->debug("$self->{'_sName'} - Maximum argument limit reached for token: $sToken'}");
543             # last if ($iMaxArgCheck == 1);
544              
545 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 1--");
546 0           $logger->debug("$self->{'_sName'} - \tWorking with token ($iCurrentToken): $sToken");
547            
548             # If the token is NOT currently found then it might be a partial command or an abbreviation
549             # so let try and expand the token if we can with what we know.
550 0           my @aAllCommandsAtThisLevel;
551             my @aCommandsAtThisLevel;
552 0           my $iNumberOfCommandMatches = 0;
553 0 0         if (!exists $hCommandTree->{$sToken})
554             {
555 0           @aAllCommandsAtThisLevel = keys(%$hCommandTree);
556            
557 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 2--");
558 0           $logger->debug("$self->{'_sName'} - \taAllCommandsAtThisLevel: ", ${$oDebugger->DumpArray(\@aAllCommandsAtThisLevel)});
  0            
559            
560             # We need to grab just the command in this list that match the data/token that was typed in on the command line
561 0           @aAllCommandsAtThisLevel = grep {/^$sToken/} @aAllCommandsAtThisLevel;
  0            
562              
563 0           $logger->debug("$self->{'_sName'} - \taAllCommandsAtThisLevel: ", ${$oDebugger->DumpArray(\@aAllCommandsAtThisLevel)});
  0            
564              
565              
566             # We need to stip out any commands that are "hidden" so they do not mess up the tab completion by the system
567             # thinking there is more options at that level then there really is.
568 0           foreach (@aAllCommandsAtThisLevel)
569             {
570 0 0         unless (exists $hCommandTree->{$_}->{hidden})
571             {
572 0           push(@aCommandsAtThisLevel, $_);
573             }
574             }
575            
576 0           $logger->debug("$self->{'_sName'} - \taCommandsAtThisLevel: ", ${$oDebugger->DumpArray(\@aCommandsAtThisLevel)});
  0            
577            
578             # If there is only one option in the array, then it must be the right one. If not
579             # then we have an ambiguous command situation. Also we need to make sure that the
580             # command is not set be excluded from completion or flagged as hidden.
581 0           $iNumberOfCommandMatches = @aCommandsAtThisLevel;
582 0           $logger->debug("$self->{'_sName'} - \tiNumberOfCommandMatches: $iNumberOfCommandMatches");
583 0 0 0       if (($iNumberOfCommandMatches == 1) && (!exists ($hCommandTree->{$aCommandsAtThisLevel[0]}->{exclude_from_completion})) && (!exists ($hCommandTree->{$aCommandsAtThisLevel[0]}->{hidden})))
      0        
584             {
585 0           $logger->debug("$self->{'_sName'} - \tSetting sToken to $aCommandsAtThisLevel[0]");
586 0           $sToken = $aCommandsAtThisLevel[0];
587             }
588             }
589            
590             # Lets loop through all synonyms to find the actual command and then update the token
591 0   0       while (exists($hCommandTree->{$sToken}) && exists($hCommandTree->{$sToken}->{'alias'}))
592             {
593 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 3--");
594 0           $logger->debug("$self->{'_sName'} - \tChecking aliases");
595 0           $sToken = $hCommandTree->{$sToken}->{'alias'};
596             }
597            
598             # If the command exists we need to capture the current directives for it and we need to add
599             # the command to the aFullCommandName array. If it does not exist, then we should put the
600             # remaining arguments in the aCommandArgument array and return. This first one will also
601             # match a blank line entered if a default command is enabled. So we need to watch for that
602             # when the rest of the default commands will match in the else statement below.
603 0 0         if (exists $hCommandTree->{$sToken} )
604             {
605 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 4--");
606 0           $logger->debug("$self->{'_sName'} - \tCommand $sToken found");
607              
608 0           $hCommandDirectives = $hCommandTree->{$sToken};
609 0           push(@aFullCommandName, $sToken);
610            
611             # We need to zero out the hCommandTree if their is no subcommands so that we do not get
612             # in to a state where we can continue completing the last command over and over again.
613             # Example: 'sh' 'hist' 'hist'
614 0 0         if (exists($hCommandDirectives->{cmds})) { $hCommandTree = $hCommandDirectives->{cmds}; }
  0 0          
615             elsif ($sToken eq "") { }
616 0           else { $hCommandTree = {}; }
617             }
618             else
619             {
620             # Lets check to see if the command is a default command. Which means if they typed in
621             # something that was not found in the command list, then there should be no _hCommandDirectives.
622             # But we also need to make sure that a default command option was defined in the configuration file
623 0 0 0       if (!defined $hCommandDirectives && exists $hCommandTree->{''} && $iNumberOfCommandMatches < 1)
      0        
624             {
625 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 5--");
626 0           $logger->debug("$self->{'_sName'} - \tDefault command found");
627              
628 0           $hCommandDirectives = $hCommandTree->{''};
629 0           push(@aFullCommandName, $sToken);
630              
631             # Since we are using the active token as a command, a default command, then lets not include that
632             # in the arguments. Thus the +1
633 0           foreach ($iCurrentToken+1..$iNumberOfTokens-1)
634             {
635 0           $logger->debug("$self->{'_sName'} - Command to be added to arguments array is $aCommandTokens->[$_]");
636 0 0         unless ($aCommandTokens->[$_] eq "") { push(@aCommandArguments, $aCommandTokens->[$_]); }
  0            
637             }
638 0           last;
639             }
640             else
641             {
642             # We need to grab the remaining tokens, once a command is not found, and add them to the
643             # aCommandArguments array
644 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 6--");
645 0           $logger->debug("$self->{'_sName'} - \tCommand $sToken NOT found");
646 0           foreach ($iCurrentToken..$iNumberOfTokens-1)
647             {
648 0           $logger->debug("$self->{'_sName'} - Command to be added to arguments array is $aCommandTokens->[$_]");
649 0 0         unless ($aCommandTokens->[$_] eq "") { push(@aCommandArguments, $aCommandTokens->[$_]); }
  0            
650             }
651 0           last;
652             }
653              
654             }
655              
656 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 7--");
657 0           $logger->debug("$self->{'_sName'} - \tVariables defined for iCurrentToken: $iCurrentToken");
658              
659 0           $logger->debug("$self->{'_sName'} - \thCommandTree: ", ${$oDebugger->DumpHashKeys($hCommandTree)});
  0            
660 0 0         if (defined $hCommandDirectives)
661             {
662 0           $logger->debug("$self->{'_sName'} - \thCommandDirectives: ", ${$oDebugger->DumpHashKeys($hCommandDirectives)});
  0            
663             }
664             else
665             {
666 0           $logger->debug("$self->{'_sName'} - \thCommandDirectives: NOT DEFINED");
667             }
668 0           $logger->debug("$self->{'_sName'} - \taCommandTokens: ", ${$oDebugger->DumpArray($aCommandTokens)});
  0            
669 0           $logger->debug("$self->{'_sName'} - \taFullCommandName: ", ${$oDebugger->DumpArray(\@aFullCommandName)});
  0            
670 0           $logger->debug("$self->{'_sName'} - \taCommandArguments: ", ${$oDebugger->DumpArray(\@aCommandArguments)});
  0            
671              
672 0           $iCurrentToken++;
673             }
674            
675 0           $self->{_hCommandTreeAtLevel} = $hCommandTree;
676 0   0       $self->{_hCommandDirectives} = $hCommandDirectives || {};
677 0           $self->{_aFullCommandName} = \@aFullCommandName;
678 0           $self->{_aCommandArguments} = \@aCommandArguments;
679              
680             # Escape the completions so they're valid on the command line
681             # I am not sure if this is the right place yet for this to be done. Need to write some unit
682             # tests to verify
683 0 0         $self->{_oParser}->parse_escape($self->{_aFullCommandName}) unless $self->{suppress_completion_escape};
684 0 0         $self->{_oParser}->parse_escape($self->{_aCommandArguments}) unless $self->{suppress_completion_escape};
685            
686            
687 0           $logger->debug("$self->{'_sName'} - --DEBUG FIND 8--");
688 0           $logger->debug("$self->{'_sName'} - \tFinal variables set by _FindCommandInCommandTree function");
689 0           $logger->debug("$self->{'_sName'} - \t_hCommandTreeAtLevel: ", ${$oDebugger->DumpHashKeys($self->{_hCommandTreeAtLevel})});
  0            
690 0           $logger->debug("$self->{'_sName'} - \t_hCommandDirectives: ", ${$oDebugger->DumpHashKeys($self->{_hCommandDirectives})});
  0            
691 0           $logger->debug("$self->{'_sName'} - \t_aFullCommandName: ", ${$oDebugger->DumpArray($self->{_aFullCommandName})});
  0            
692 0           $logger->debug("$self->{'_sName'} - \t_aCommandArguments: ", ${$oDebugger->DumpArray($self->{_aCommandArguments})});
  0            
693              
694 0           $logger->debug("$self->{'_sName'} - ", '### Leaving Method ###');
695 0           return 1;
696             }
697              
698             sub _RunCodeDirective
699             {
700             # This method will execute the code directives when called. It performs some sanity checking
701             # before it actually runs the commands
702             # Required:
703             # $self->{_hCommandTreeAtLevel} hash_ref
704             # $self->{_hCommandDirectives} hash_ref
705             # $self->{_aCommandArguments} array_ref
706 0     0     my $self = shift;
707 0           my $logger = $oDebugger->GetLogger($self);
708 0           $logger->debug("$self->{'_sName'} - ", '### Entering Method ###');
709            
710            
711 0 0         if(!$self->{_hCommandDirectives})
712             {
713             # This is for processing a default command at each level
714 0 0 0       if ((exists $self->{_hCommandTreeAtLevel}->{''}) && (exists $self->{_hCommandTreeAtLevel}->{''}->{code}))
715             {
716             # The default command exists and has a code directive
717             # my $save = $self->{_hCommandDirectives};
718 0           $self->{_hCommandDirectives} = $self->{_hCommandTreeAtLevel}->{''};
719             # $self->_RunCommand();
720             # $self->{_hCommandDirectives} = $save;
721             # return;
722             }
723 0           my ($sCommandName) = $self->_GetFullCommandName();
724 0           $self->error( "$sCommandName: unknown command\n");
725 0           return undef;
726             }
727              
728             # Lets check and verify the max and min values for number of arguments if they exist
729             # TODO Instead of printing an error, we should print the command syntax
730 0 0 0       if (exists($self->{_hCommandDirectives}->{minargs}) && @{$self->{_aCommandArguments}} < $self->{_hCommandDirectives}->{minargs})
  0            
731             {
732 0           $self->error("Too few args! " . $self->{_hCommandDirectives}->{minargs} . " minimum.\n");
733 0           return undef;
734             }
735 0 0 0       if (exists($self->{_hCommandDirectives}->{maxargs}) && @{$self->{_aCommandArguments}} > $self->{_hCommandDirectives}->{maxargs})
  0            
736             {
737 0           $self->error("Too many args! " . $self->{_hCommandDirectives}->{maxargs} . " maximum.\n");
738 0           return undef;
739             }
740              
741             # Lets add support for authenticated commands
742 0 0 0       if ( exists $self->{_hCommandDirectives}->{auth} && $self->{_hCommandDirectives}->{auth} == 1 )
743             {
744 0           my $iSuccess = $self->_AuthCommand();
745 0 0         if ( $iSuccess == 1 ) { $self->_RunCommand(); }
  0            
746             }
747 0           else { $self->_RunCommand(); }
748 0           $logger->debug("$self->{'_sName'} - ", '### Leaving Method ###');
749 0           return;
750             }
751              
752             sub _AuthCommand
753             {
754             # This method will perform authentication for a command.
755             # Return:
756             # 1 = successful authentication
757             # 0 = failed authentication
758 0     0     my $self = shift;
759 0           my $logger = $oDebugger->GetLogger($self);
760 0           my $config = $oConfig->GetRunningConfig();
761 0           my $OUT = $self->{OUT};
762              
763 0           $logger->debug("$self->{'_sName'} - ", '### Entering Method ###');
764              
765 0           my $bAuthStatus = 0;
766 0           my $iAttempt = 1;
767 0           my $sStoredUsername = "";
768 0           my $sStoredPassword = "";
769 0           my $sStoredSalt = "";
770 0           my $iCryptID;
771              
772 0           my $oAuth = new Term::RouterCLI::Auth();
773            
774 0           my $iMaxAttempt = 3;
775 0 0         if ( exists $config->{auth}->{max_attempts} ) { $iMaxAttempt = $config->{auth}->{max_attempts}; }
  0            
776            
777 0           my $sAuthMode = "shared";
778 0 0         if ( exists $config->{auth}->{mode} ) { $sAuthMode = $config->{auth}->{mode}; }
  0            
779            
780 0           $logger->debug("$self->{'_sName'} - iMaxAttempt: $iMaxAttempt");
781 0           $logger->debug("$self->{'_sName'} - sAuthMode: $sAuthMode");
782            
783 0 0         if ($sAuthMode eq "shared")
    0          
784             {
785 0 0         if ( exists $config->{auth}->{password} )
786             {
787 0           $sStoredPassword = $config->{auth}->{password};
788 0           ($iCryptID, $sStoredSalt, $sStoredPassword) = $oAuth->SplitPasswordString(\$sStoredPassword);
789             }
790            
791             # Lets not prompt for a password if the password is blank in the configuration file or does not exist
792             # in the configuration file
793 0 0         if ( $$sStoredPassword eq "" )
794             {
795 0           $logger->debug("$self->{'_sName'} - No password found for shared auth mode, exiting");
796 0           $logger->debug("$self->{'_sName'} - ", '### Leaving Method ###');
797             # Return code 1 = "success"
798 0           return 1;
799             }
800              
801 0           while ($iAttempt <= $iMaxAttempt)
802             {
803 0           $self->ChangeActivePrompt("Password: ");
804 0           my $sPassword = $oAuth->PromptForPassword();
805 0           $logger->debug("$self->{'_sName'} - sPassword: $$sPassword");
806            
807 0           my $sEncryptedPassword = $oAuth->EncryptPassword($iCryptID, $sPassword, $sStoredSalt);
808 0           $logger->debug("$self->{'_sName'} - sEncryptedPassword: $$sEncryptedPassword");
809            
810             # TODO Need to provide a way for users to change the password
811 0 0         if ($$sEncryptedPassword eq $$sStoredPassword)
812             {
813 0           $logger->debug("$self->{'_sName'} - Match Found");
814 0           $bAuthStatus = 1;
815 0           last;
816             }
817 0 0         if ($iAttempt == $iMaxAttempt)
818             {
819 0           $logger->debug("$self->{'_sName'} - Too many failed authentication attempts!");
820 0           $bAuthStatus = 0;
821 0           last;
822             }
823 0           $iAttempt++;
824             }
825             }
826             elsif ($sAuthMode eq "user")
827             {
828 0           my $sUserAuthMode = "local";
829            
830 0           while ($iAttempt <= $iMaxAttempt)
831             {
832 0           $self->ChangeActivePrompt("Username: ");
833 0           my $sUsername = ${$oAuth->PromptForUsername()};
  0            
834 0           $logger->debug("$self->{'_sName'} - sUsername: $sUsername");
835            
836 0           $self->ChangeActivePrompt("Password: ");
837 0           my $sPassword = $oAuth->PromptForPassword();
838 0           $logger->debug("$self->{'_sName'} - sPassword: $$sPassword");
839              
840 0 0         unless ( exists $config->{auth}->{user}->{$sUsername} )
841             {
842 0           $logger->debug("$self->{'_sName'} - iAttempt: $iAttempt");
843 0           $iAttempt++;
844 0           next;
845             }
846            
847             # This is where we add support for things like RADIUS or TACACS from the configuration file
848 0 0         if ( exists $config->{auth}->{user}->{$sUsername}->{authmode} )
849             {
850 0           $sUserAuthMode = $config->{auth}->{user}->{$sUsername}->{authmode};
851             }
852              
853             # We do not allow undefined passwords
854 0 0         unless ( exists $config->{auth}->{user}->{$sUsername}->{password} )
855             {
856 0           $logger->debug("$self->{'_sName'} - iAttempt: $iAttempt");
857 0           $iAttempt++;
858 0           next;
859             }
860 0           $sStoredPassword = $config->{auth}->{user}->{$sUsername}->{password};
861 0           ($iCryptID, $sStoredSalt, $sStoredPassword) = $oAuth->SplitPasswordString(\$sStoredPassword);
862              
863 0           my $sEncryptedPassword = $oAuth->EncryptPassword($iCryptID, $sPassword, $sStoredSalt);
864 0           $logger->debug("$self->{'_sName'} - sEncryptedPassword: $$sEncryptedPassword");
865            
866 0 0         if ($$sEncryptedPassword eq $$sStoredPassword)
867             {
868 0           $logger->debug("$self->{'_sName'} - Match Found");
869 0           $self->{_sActiveLoggedOnUser} = $sUsername;
870            
871             # We need to clear and load the new command history file for this user
872 0 0         if ($self->{_oHistory}->{_bEnabled} == 1 )
873             {
874 0           $self->{_oHistory}->SaveCommandHistoryToFile();
875 0           $self->{_oHistory}->ClearHistory();
876 0           my $sNewHistoryFilename = './logs/.cli-history-' . $sUsername;
877 0           $self->{_oHistory}->SetFilename($sNewHistoryFilename);
878 0           $self->{_oHistory}->LoadCommandHistoryFromFile();
879             }
880            
881 0           $bAuthStatus = 1;
882 0           last;
883             }
884              
885 0 0         if ($iAttempt == $iMaxAttempt)
886             {
887 0           print $OUT "Too many failed authentication attempts!\n\n";
888 0           $bAuthStatus = 0;
889 0           last;
890             }
891 0           $iAttempt++;
892             }
893             }
894              
895 0           $logger->debug("$self->{'_sName'} - ", '### Leaving Method ###');
896 0           return $bAuthStatus;
897             }
898              
899             sub _RunCommand
900             {
901             # This method will actually run the commands called out in the code directives
902             # Required:
903             # $self->{_hCommandDirectives} hash_ref
904 0     0     my $self = shift;
905 0           my $logger = $oDebugger->GetLogger($self);
906 0           my $OUT = $self->{OUT};
907              
908 0           $logger->debug("$self->{'_sName'} - ", '### Entering Method ###');
909 0 0         if (exists $self->{_hCommandDirectives}->{code})
910             {
911 0           my $oCode = $self->{_hCommandDirectives}->{code};
912             # If oCode is a code ref, call it, else it's a string, print it.
913 0 0         if (ref($oCode) eq 'CODE')
914             {
915             # This is where we actually run the code. All commands and arguments are in the object
916 0           eval { &$oCode($self) };
  0            
917 0 0         $self->error($@) if $@;
918             }
919 0           else { print $OUT $oCode; }
920             }
921             else
922             {
923 0 0         if (exists $self->{_hCommandDirectives}->{cmds})
924             {
925 0           print $OUT $self->_GetCommandSummaries();
926             }
927             else
928             {
929 0           my ($sCommandName) = $self->_GetFullCommandName();
930 0           $self->error("The $sCommandName command has no code directive to call!\n");
931             }
932             }
933 0           $logger->debug("$self->{'_sName'} - ", '### Leaving Method ###');
934 0           return;
935             }
936              
937             sub _CompletionFunction
938             {
939             # This method is the entry point to the ReadLine completion callback and will complete a string
940             # of data against the command tree.
941             # Required:
942             # string (The word directly to the left of the cursor)
943             # string (The entire line)
944             # int (the position in the line of the beginning of $text)
945              
946 0     0     my $self = shift;
947 0           $self->{_sStringToComplete} = shift;
948 0           $self->{_sCompleteRawline} = shift;
949 0           $self->{_iStringToCompleteTextStartPosition} = shift;
950 0           my $OUT = $self->{OUT};
951 0           my $logger = $oDebugger->GetLogger($self);
952              
953 0           $logger->debug("$self->{'_sName'} - ", '### Entering Method ###');
954              
955             # Lets figure out where the cursor is currently at and thus how long the original line is
956 0           $self->{_iCurrentCursorLocation} = $self->{_oTerm}->Attribs->{'point'};
957              
958 0           $logger->debug("$self->{'_sName'} - Values passed in to the function and computed from those values");
959 0           $logger->debug("$self->{'_sName'} - \t_sStringToComplete: $self->{_sStringToComplete}");
960 0           $logger->debug("$self->{'_sName'} - \t_sCompleteRawline: $self->{_sCompleteRawline}");
961 0           $logger->debug("$self->{'_sName'} - \t_iStringToCompleteTextStartPosition: $self->{_iStringToCompleteTextStartPosition}");
962 0           $logger->debug("$self->{'_sName'} - \t_iCurrentCursorLocation: $self->{_iCurrentCursorLocation}");
963              
964             # If there is any white space at the start or end of the command lets remove it just to be safe
965 0           $self->{_sCompleteRawline} =~ s/^\s+//g;
966 0           $self->{_sCompleteRawline} =~ s/\s+$//g;
967              
968              
969             # Parse the _sCompleteRawline in to a series of command line tokens
970 0           ($self->{_aCommandTokens}, $self->{_iTokenNumber}, $self->{_iTokenOffset}) = $self->{_oParser}->parse_line(
971             $self->{_sCompleteRawline},
972             messages=>0,
973             cursorpos=>$self->{_iCurrentCursorLocation},
974             fixclosequote=>1
975             );
976            
977 0           $logger->debug("$self->{'_sName'} - Data returned from the parser function");
978 0           $logger->debug("$self->{'_sName'} - \t_aCommandTokens: ", ${$oDebugger->DumpArray($self->{_aCommandTokens})});
  0            
979 0 0         $logger->debug("$self->{'_sName'} - \t_iTokenNumber: $self->{_iTokenNumber}") if (defined $self->{_iTokenNumber});
980 0 0         $logger->debug("$self->{'_sName'} - \t_iTokenOffset: $self->{_iTokenOffset}") if (defined $self->{_iTokenOffset});
981            
982             # Punt if nothing comes back from the parser
983 0 0         unless (defined($self->{_aCommandTokens})) { $logger->fatal("ERROR 1001"); return; }
  0            
  0            
984              
985             # Lets try and find the command in the command tree
986 0           $self->_FindCommandInCommandTree();
987              
988              
989              
990             # --------------------------------------------------------------------------------
991             # Process Arguments
992             # --------------------------------------------------------------------------------
993             # Lets check to see if there are any arguments returned from the Find function. The three use cases are:
994             # 1) There are no arguments, meaning everything is a command found in the command tree
995             # 2) There are multiple matches found for the command abbreviation that was entered
996             # 3) No match was found
997             # 3a) The command was typed in wrong
998             # 3b) The values typed in are in fact arguments and not part of the command at all
999             # 3c) The values need to be passed to a method defined in the args directive to see if they are commands
1000             # 3d) There was nothing entered on the command line
1001 0           my $iNumberOfArguments = @{$self->{_aCommandArguments}};
  0            
1002 0           $logger->debug("$self->{'_sName'} - iNumberOfArguments: $iNumberOfArguments");
1003 0 0         if ($iNumberOfArguments > 0)
1004             {
1005             # Use Cases 2 and 3
1006             # Lets figure out how many matches there are for that first argument that could not be completed
1007 0           my @aCommandsThatMatchAtThisLevel = keys(%{$self->{_hCommandTreeAtLevel}});
  0            
1008 0           @aCommandsThatMatchAtThisLevel = grep {/^$self->{_aCommandArguments}->[0]/ } @aCommandsThatMatchAtThisLevel;
  0            
1009 0           my $iNumberOfCommandsThatMatchAtThisLevel = @aCommandsThatMatchAtThisLevel;
1010            
1011 0           $logger->debug("$self->{'_sName'} - Entering Use Case 2 and 3");
1012 0           $logger->debug("$self->{'_sName'} - \tiNumberOfCommandsThatMatchAtThisLevel: $iNumberOfCommandsThatMatchAtThisLevel");
1013 0 0         if ($iNumberOfCommandsThatMatchAtThisLevel > 1)
1014             {
1015             # Use Case 2: There was more than one match found. So we need to print out the options for just these commmands
1016 0           $logger->debug("$self->{'_sName'} - Entering Use Case 2");
1017 0           $self->_RewriteLine();
1018              
1019             # Print out possible options for the matches that were found
1020 0           print $OUT "\n";
1021 0           print $OUT $self->_GetCommandSummaries(\@aCommandsThatMatchAtThisLevel);
1022              
1023             # We need to redraw the prompt and command line options since we are going to output text via _GetCommandSummaries
1024 0           $self->{_oTerm}->rl_on_new_line();
1025 0           return;
1026             }
1027             else
1028             {
1029             # Use Case 3: There were no matches found for this argument, meaning that the argument is not found in the
1030             # command tree so the arguments must truely be arguments or they are incorrectly entered commands.
1031             # But before we can know this for sure, lets check for an args directive.
1032            
1033 0           $logger->debug("$self->{'_sName'} - Entering Use Case 3");
1034 0 0 0       if (exists $self->{_hCommandDirectives}->{args} && !exists $self->{_hCommandDirectives}->{minargs})
    0 0        
      0        
1035             {
1036             # Use Case 3c: This is for something like "help" or "no" that needs to restart the completion at the
1037             # beginning of the command tree. So we will need to check the args directive
1038 0           $logger->debug("$self->{'_sName'} - Entering Use Case 3c");
1039              
1040 0 0         if (ref($self->{_hCommandDirectives}->{args}) eq 'CODE')
1041             {
1042             # This is where we call the subroutine listed in the args directive
1043 0           eval { &{$self->{_hCommandDirectives}->{args}}($self) };
  0            
  0            
1044             }
1045            
1046 0           $self->_RewriteLine();
1047             }
1048             elsif (!exists $self->{_hCommandDirectives}->{args} && (exists $self->{_hCommandDirectives}->{minargs} && $self->{_hCommandDirectives}->{minargs} > 0))
1049             {
1050             # Use Case 3b: The arguments are in fact arguments
1051 0           $logger->debug("$self->{'_sName'} - Entering Use Case 3b");
1052 0           $self->_RewriteLine();
1053 0           return;
1054             }
1055             else
1056             {
1057             # Use Case 3a: The command was typed in wrong
1058 0           $logger->debug("$self->{'_sName'} - Entering Use Case 3a");
1059 0           $self->_RewriteLine();
1060             }
1061            
1062             }
1063             }
1064             else
1065             {
1066 0 0 0       if (!exists $self->{_aFullCommandName}->[0] || $self->{_aFullCommandName}->[0] eq "")
1067             {
1068             # Use Case 3d: There was nothing entered on the command line. So we need to print out all options at that level
1069 0           $logger->debug("$self->{'_sName'} - Entering Use Case 3d");
1070 0           $self->_RewriteLine();
1071              
1072             # Print out possible options for the matches that were found
1073 0           print $OUT "\n";
1074 0           print $OUT $self->_GetCommandSummaries();
1075              
1076             # We need to redraw the prompt and command line options since we are going to output text via _GetCommandSummaries
1077 0           $self->{_oTerm}->rl_on_new_line();
1078 0           return;
1079             }
1080             else
1081             {
1082             # Use Case 1: There were no arguments found, so everything is a full blown command
1083 0           $logger->debug("$self->{'_sName'} - Entering Use Case 1");
1084 0           $self->_RewriteLine();
1085             }
1086             }
1087             # --------------------------------------------------------------------------------
1088            
1089             # These next two lines will make the screen scroll up like it does on a router
1090             # If we are in a internal loop, like processing the args directive lets not scroll
1091             # the screen as that will just add extra lines when we do not want them
1092 0 0         if ($self->{_sStringToComplete} ne "NONE")
1093             {
1094 0           print $OUT "\n";
1095 0           $self->{_oTerm}->rl_on_new_line();
1096             }
1097            
1098             # If there is nothing to do, meaning, there is no command to complete, then lets print out
1099             # the command options at that level. If there are no options at that level, print .
1100             # If there are currently no commands found, then lets not print either
1101 0           my $iNumberOfCommands = @{$self->{_aFullCommandName}};
  0            
1102 0 0 0       if ($self->{_sStringToComplete} eq "" && $iNumberOfCommands > 0)
1103             {
1104 0           $logger->debug("$self->{'_sName'} - Lets get the data from _GetCommandSummaries");
1105 0           print $OUT $self->_GetCommandSummaries();
1106             }
1107              
1108 0           $logger->debug("$self->{'_sName'} - ", '### Leaving Method ###');
1109 0           return;
1110             }
1111              
1112             sub _RewriteLine
1113             {
1114             # This method will do the actual rewriting of the command line during command completion
1115             # Required:
1116 0     0     my $self = shift;
1117 0           my $logger = $oDebugger->GetLogger($self);
1118 0           $logger->debug("$self->{'_sName'} - ", '### Entering Method ###');
1119            
1120 0           my ($sCommands, $iCommandsLength) = $self->_GetFullCommandName();
1121 0           my ($sArguments, $iArgumentLength) = $self->_GetFullArgumentsName();
1122 0           my $iCurrentPoint = $self->{_iCurrentCursorLocation};
1123              
1124             # We need to set the cursor to the end of the new fully completed line
1125 0           my $iNewPointLocation = $iCommandsLength + $iArgumentLength;
1126            
1127            
1128 0           $logger->debug("$self->{'_sName'} - iCurrentPoint: $iCurrentPoint");
1129 0           $logger->debug("$self->{'_sName'} - sCommands: $sCommands");
1130 0           $logger->debug("$self->{'_sName'} - iCommandsLength: $iCommandsLength");
1131 0           $logger->debug("$self->{'_sName'} - sArguments: $sArguments");
1132 0           $logger->debug("$self->{'_sName'} - iArgumentLength: $iArgumentLength");
1133 0           $logger->debug("$self->{'_sName'} - iNewPointLocation: $iNewPointLocation");
1134            
1135 0           $self->{_oTerm}->Attribs->{'line_buffer'} = $sCommands . $sArguments;
1136 0           $self->{_oTerm}->Attribs->{'point'} = $iNewPointLocation;
1137              
1138 0           $logger->debug("$self->{'_sName'} - ", '### Leaving Method ###');
1139             }
1140              
1141              
1142             return 1;