// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.
#ident "$XORP: xorp/cli/cli_command.cc,v 1.30 2007/02/16 22:45:28 pavlin Exp $"
//
// CLI (Command-Line Interface) commands structuring implementation
//
#include "cli_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/ipvx.hh"
#include "libxorp/token.hh"
#include "libxorp/utils.hh"
#include "cli_command_pipe.hh"
//
// Exported variables
//
//
// Local constants definitions
//
//
// Local structures/classes, typedefs and macros
//
//
// Local variables
//
static string EXECUTE_THIS_COMMAND_STRING =
"<[Enter]> Execute this command\r\n";
//
// Local functions prototypes
//
CliCommand::CliCommand(CliCommand *init_parent_command,
const string& init_command_name,
const string& init_command_help)
: _parent_command(init_parent_command),
_name(init_command_name),
_help(init_command_help),
_default_nomore_mode(false),
_is_command_argument(false),
_is_argument_expected(false)
{
if (_parent_command != NULL)
_root_command = _parent_command->root_command();
else
_root_command = this;
set_allow_cd(false, "");
set_can_pipe(false); // XXX: default
set_cli_command_pipe(NULL);
// Set the command-completion help string
// TODO: parameterize the hard-coded number
_help_completion = c_format(" %*s%s\r\n", (int)(20 - _name.size()), " ",
_help.c_str());
// XXX: set the CLI completion function to its default value
set_cli_completion_func(cli_attempt_command_completion_byname);
_has_dynamic_children = false;
}
CliCommand::~CliCommand()
{
// Delete recursively all child commands
delete_pointers_list(_child_command_list);
}
//
// Enable/disable "cd" to this command, and set the "cd prompt"
//
void
CliCommand::set_allow_cd(bool v, const string& init_cd_prompt)
{
_allow_cd = v;
if (init_cd_prompt.size())
_cd_prompt = init_cd_prompt;
}
//
// Return %XORP_OK, on success, otherwise %XORP_ERROR
//
int
CliCommand::add_command(CliCommand *child_command, string& error_msg)
{
list<CliCommand *>::iterator iter, insert_pos;
insert_pos = child_command_list().begin();
// Check if command already installed, as well as find the
// position to install the command (based on lexicographical ordering).
for (iter = child_command_list().begin();
iter != child_command_list().end();
++iter) {
CliCommand *cli_command = *iter;
if (cli_command->is_same_command(child_command->name())) {
// Command already installed
error_msg = c_format("Command '%s' already installed",
child_command->name().c_str());
XLOG_ERROR("%s", error_msg.c_str());
return (XORP_ERROR);
}
if (cli_command->name() < child_command->name()) {
insert_pos = iter;
++insert_pos;
}
}
if (insert_pos == child_command_list().end())
_child_command_list.push_back(child_command);
else
_child_command_list.insert(insert_pos, child_command);
child_command->set_root_command(this->root_command());
return(XORP_OK);
}
//
// Create a new command.
// Return the new child command on success, otherwise NULL.
// XXX: By default, we CANNOT "cd" to this command.
// XXX: If @is_multilevel_command is true, then @init_command_name can
// include more than one command levels in the middle.
// E.g. "show version pim". However, commands "show" and "show version" must
// have been installed first.
//
CliCommand *
CliCommand::add_command(const string& init_command_name,
const string& init_command_help,
bool is_multilevel_command,
string& error_msg)
{
CliCommand *parent_cli_command = NULL;
CliCommand *cli_command = NULL;
vector<string> command_tokens;
string token;
string token_line = init_command_name;
string command_name_string;
if (is_multilevel_command) {
// Create a vector of all takens in the command
for (token = pop_token(token_line);
! token.empty();
token = pop_token(token_line)) {
command_tokens.push_back(token);
}
} else {
if (token_line.empty()) {
error_msg = c_format("Empty token line for command %s",
init_command_name.c_str());
return (NULL);
}
command_tokens.push_back(token_line);
}
if (command_tokens.empty()) {
error_msg = c_format("Empty command tokens for command %s",
init_command_name.c_str());
return (NULL);
}
command_name_string = command_tokens[command_tokens.size() - 1];
// Traverse all tokens and find the parent command where to install
// the new command
parent_cli_command = this;
for (size_t i = 0; i < command_tokens.size() - 1; i++) {
parent_cli_command = parent_cli_command->command_find(command_tokens[i]);
if (parent_cli_command == NULL)
break;
}
if (parent_cli_command == NULL) {
error_msg = c_format("Cannot find parent command");
goto error_label_missing;
}
cli_command = new CliCommand(parent_cli_command,
command_name_string,
init_command_help);
if (parent_cli_command->add_command(cli_command, error_msg) < 0) {
delete cli_command;
goto error_label_failed;
}
cli_command->set_allow_cd(false, "");
return (cli_command);
error_label_missing:
error_msg = c_format("Error installing '%s' on non-existent node '%s': %s",
init_command_name.c_str(),
(this->name().size() > 0) ? this->name().c_str() : "<ROOT>",
error_msg.c_str());
XLOG_ERROR("%s", error_msg.c_str());
return (NULL); // Invalid path to the command
error_label_failed:
error_msg = c_format("Error installing '%s' on '%s': %s",
init_command_name.c_str(),
(this->name().size() > 0) ? this->name().c_str() : "<ROOT>",
error_msg.c_str());
XLOG_ERROR("%s", error_msg.c_str());
return (NULL); // Invalid path to the command
}
//
// Create a command and assign a processing callback to it.
// Return the new child command on success, otherwise NULL.
//
CliCommand *
CliCommand::add_command(const string& init_command_name,
const string& init_command_help,
bool is_multilevel_command,
const CLI_PROCESS_CALLBACK& init_cli_process_callback,
string& error_msg)
{
CliCommand *cli_command = add_command(init_command_name,
init_command_help,
is_multilevel_command,
error_msg);
if (cli_command == NULL)
return (NULL);
cli_command->set_cli_process_callback(init_cli_process_callback);
cli_command->set_allow_cd(false, "");
if (! init_cli_process_callback.is_empty()) {
// XXX: by default, enable pipe processing if there is a callback func
cli_command->set_can_pipe(true);
}
return (cli_command);
}
//
// Create a command and assign a processing and an interrupt callbacks to it.
// Return the new child command on success, otherwise NULL.
//
CliCommand *
CliCommand::add_command(const string& init_command_name,
const string& init_command_help,
bool is_multilevel_command,
const CLI_PROCESS_CALLBACK& init_cli_process_callback,
const CLI_INTERRUPT_CALLBACK& init_cli_interrupt_callback,
string& error_msg)
{
CliCommand *cli_command = add_command(init_command_name,
init_command_help,
is_multilevel_command,
init_cli_process_callback,
error_msg);
if (cli_command == NULL)
return (NULL);
cli_command->set_cli_interrupt_callback(init_cli_interrupt_callback);
return (cli_command);
}
//
// Return the new child command on success, otherwise NULL.
// Setup a command we can "cd" to. If @cd_prompt is not-NULL,
// then the CLI prompt will be set to @cd_prompt;
// otherwise, it will remain unchanged.
//
CliCommand *
CliCommand::add_command(const string& init_command_name,
const string& init_command_help,
const string& init_cd_prompt,
bool is_multilevel_command,
string& error_msg)
{
CliCommand *cli_command = add_command(init_command_name,
init_command_help,
is_multilevel_command,
error_msg);
if (cli_command == NULL)
return (NULL);
cli_command->set_allow_cd(true, init_cd_prompt);
return (cli_command);
}
//
// Delete a command, and all sub-commands below it
// Return: %XORP_OK on success, otherwise %XORP_ERROR
//
int
CliCommand::delete_command(CliCommand *child_command)
{
list<CliCommand *>::iterator iter;
iter = find(_child_command_list.begin(),
_child_command_list.end(),
child_command);
if (iter == _child_command_list.end())
return (XORP_ERROR);
_child_command_list.erase(iter);
delete child_command;
return (XORP_OK);
}
//
// Delete a command, and all sub-commands below it
// Return: %XORP_OK on success, otherwise %XORP_ERROR
// XXX: @delete_command_name can be the full path-name for that command
//
int
CliCommand::delete_command(const string& delete_command_name)
{
CliCommand *parent_cli_command = NULL;
CliCommand *delete_cli_command = NULL;
vector<string> command_tokens;
string token;
string token_line = delete_command_name;
// Create a vector of all takens in the command
for (token = pop_token(token_line);
! token.empty();
token = pop_token(token_line)) {
command_tokens.push_back(token);
}
if (command_tokens.empty())
return (XORP_ERROR);
// Traverse all tokens and find the command to delete
parent_cli_command = this;
delete_cli_command = NULL;
for (size_t i = 0; i < command_tokens.size(); i++) {
if (delete_cli_command != NULL)
parent_cli_command = delete_cli_command;
delete_cli_command = parent_cli_command->command_find(command_tokens[i]);
if (delete_cli_command == NULL)
break;
}
if (delete_cli_command == NULL)
goto error_label;
if (parent_cli_command->delete_command(delete_cli_command) < 0)
goto error_label;
return (XORP_OK);
error_label:
XLOG_ERROR("Error deleting %s on %s", delete_command_name.c_str(),
this->name().c_str());
return (XORP_ERROR);
}
// Recursively delete all the children of this command.
void
CliCommand::delete_all_commands()
{
list <CliCommand*>::iterator iter;
for (iter = _child_command_list.begin();
iter != _child_command_list.end(); ++iter) {
(*iter)->delete_all_commands();
delete *iter;
}
while (! _child_command_list.empty())
_child_command_list.pop_front();
}
/**
* CliCommand::create_default_cli_commands:
* @:
*
* Create the default CLI commands at each level of the command tree.
*
* Return value: %XORP_OK on success, otherwise %XORP_ERROR.
**/
int
CliCommand::create_default_cli_commands()
{
// TODO: add commands like "help", "list" (all available subcommands, etc
return (XORP_OK);
}
/**
* CliCommand::add_pipes:
* @:
*
* Create and add the default CLI pipe commands.
*
* Return value: %XORP_OK on success, otherwise %XORP_ERROR.
**/
int
CliCommand::add_pipes(string& error_msg)
{
CliPipe *cli_pipe;
CliCommand *com0;
com0 = new CliCommand(this, "|", "Pipe through a command");
// com0 = add_command("|", "Pipe through a command", false, error_msg);
if (com0 == NULL) {
return (XORP_ERROR);
}
set_cli_command_pipe(com0);
cli_pipe = new CliPipe("count");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("except");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("find");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("hold");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("match");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("no-more");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("resolve");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("save");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
cli_pipe = new CliPipe("trim");
if (com0->add_command(cli_pipe, error_msg) != XORP_OK) {
delete_pipes();
return (XORP_ERROR);
}
return (XORP_OK);
}
/**
* CliCommand::delete_pipes:
* @:
*
* Delete the default CLI pipe commands.
*
* Return value: %XORP_OK on success, otherwise %XORP_ERROR.
**/
int
CliCommand::delete_pipes()
{
if (_cli_command_pipe != NULL)
delete _cli_command_pipe;
return (XORP_OK);
// return (delete_command("|"));
}
//
// Attempts to complete the current command.
// Return: true if the attempt was successful, otherwise false.
//
bool
CliCommand::cli_attempt_command_completion_byname(void *obj,
WordCompletion *cpl,
void *data,
const char *line,
int word_end,
list<CliCommand *>& cli_command_match_list)
{
CliCommand *cli_command = reinterpret_cast<CliCommand*>(obj);
int word_start = 0; // XXX: complete from the beginning of 'line'
const char *type_suffix = NULL;
const char *cont_suffix = " "; // XXX: a space after a command is completed
string token, token_line;
const string name_string = cli_command->name();
bool is_command_completed; // 'true' if complete command typed
if ((cpl == NULL) || (line == NULL) || (word_end < 0)) {
return (false);
}
token_line = string(line, word_end);
token = pop_token(token_line);
if ((! cli_command->is_same_prefix(token))
&& (! cli_command->has_type_match_cb())) {
return (false);
}
if (token_line.length()
&& (is_token_separator(token_line[0]) || (token == "|")))
is_command_completed = true;
else
is_command_completed = false;
// Check if a potential sub-prefix
if (! is_command_completed) {
string name_complete;
if (cli_command->has_type_match_cb()) {
//
// XXX: Nothing to complete, we just need to print
// the help with the command type.
//
cli_command_match_list.push_back(cli_command);
return (true);
}
name_complete = name_string.substr(token.length());
if (cli_command->help_completion().size() > 0)
type_suffix = cli_command->help_completion().c_str();
// Add two empty spaces in front
string line_string = " ";
if (token.empty()) {
// XXX: ignore the rest of the empty spaces
word_end = 0;
} else {
line_string += line;
}
cpl_add_completion(cpl, line_string.c_str(), word_start, word_end + 2,
name_complete.c_str(),
type_suffix, cont_suffix);
cli_command_match_list.push_back(cli_command);
return (true);
}
// Must be a complete command
bool is_token_match = false;
if (cli_command->has_type_match_cb()) {
string errmsg;
is_token_match = cli_command->type_match_cb()->dispatch(token, errmsg);
} else {
is_token_match = cli_command->is_same_command(token);
}
if (! is_token_match) {
return (false);
}
bool is_child_completion = false;
if (cli_command->can_complete()
&& (! has_more_tokens(token_line))
&& (! cli_command->is_argument_expected())) {
// Add the appropriate completion info if the command can be run
string line_string1 = " ";
type_suffix = EXECUTE_THIS_COMMAND_STRING.c_str();
cpl_add_completion(cpl, line_string1.c_str(), word_start,
line_string1.size(),
"",
type_suffix, cont_suffix);
is_child_completion = true;
}
if (cli_command->can_pipe() && (cli_command->cli_command_pipe() != NULL)) {
// Add the pipe completions
if (cli_command->_cli_completion_func(cli_command->cli_command_pipe(),
cpl,
data,
token_line.c_str(),
token_line.length(),
cli_command_match_list)) {
is_child_completion = true;
}
}
// Add completions for the sub-commands (if any)
list<CliCommand *>::iterator iter;
for (iter = cli_command->child_command_list().begin();
iter != cli_command->child_command_list().end();
++iter) {
CliCommand *cli_command_child = *iter;
if (! cli_command_child->has_cli_completion_func())
continue;
if (cli_command_child->_cli_completion_func(cli_command_child,
cpl,
data,
token_line.c_str(),
token_line.length(),
cli_command_match_list)) {
is_child_completion = true;
}
}
return (is_child_completion);
}
CliCommand *
CliCommand::command_find(const string& token)
{
list<CliCommand *>::iterator iter;
for (iter = child_command_list().begin();
iter != child_command_list().end();
++iter) {
CliCommand *cli_command = *iter;
if (cli_command->has_type_match_cb()) {
string errmsg;
if (cli_command->type_match_cb()->dispatch(token, errmsg))
return (cli_command);
continue;
}
if (cli_command->is_same_command(token)) {
// XXX: assume that no two subcommands have the same name
return (cli_command);
}
}
return (NULL);
}
//
// Test if the command line is a prefix for a multi-level command.
// Note that if there is an exact match, then the return value is false.
//
bool
CliCommand::is_multi_command_prefix(const string& command_line)
{
string token;
string token_line = command_line;
CliCommand *parent_cli_command = this;
CliCommand *child_cli_command = NULL;
for (token = pop_token(token_line);
! token.empty();
token = pop_token(token_line)) {
child_cli_command = parent_cli_command->command_find(token);
if (child_cli_command != NULL) {
parent_cli_command = child_cli_command;
continue;
}
// Test if the token is a prefix for a child command
list<CliCommand *>::const_iterator iter;
for (iter = parent_cli_command->child_command_list().begin();
iter != parent_cli_command->child_command_list().end();
++iter) {
child_cli_command = *iter;
if (child_cli_command->is_same_prefix(token))
return true;
}
break;
}
return (false);
}
// Tests if the string in @token can be a prefix for this command
bool
CliCommand::is_same_prefix(const string& token)
{
size_t s = token.length();
if (s > _name.length())
return (false);
return (token.substr(0, s) == _name.substr(0, s));
}
// Tests if the string in @token matches this command
bool
CliCommand::is_same_command(const string& token)
{
return (token == _name);
}
bool
CliCommand::find_command_help(const char *line, int word_end,
set<string>& help_strings)
{
string token, token_line;
bool ret_value = false;
bool is_no_space_at_end;
if ((line == NULL) || (word_end < 0)) {
return (false);
}
token_line = string(line, word_end);
token = pop_token(token_line);
if ((! is_same_prefix(token)) && (! has_type_match_cb()))
return (false);
//
// Test if the token matches the command
//
bool is_token_match = false;
if (has_type_match_cb()) {
string errmsg;
is_token_match = type_match_cb()->dispatch(token, errmsg);
} else {
is_token_match = is_same_command(token);
}
if (token_line.length()
&& is_token_separator(token_line[0])
&& (! is_token_match)) {
// Not a match
return (false);
}
is_no_space_at_end = (token_line.empty()) ? (true) : (false);
// Get the token for the child's command (if any)
token = pop_token(token_line);
if ((token.length() == 0) && is_no_space_at_end) {
// The last token, and there is no space, so print my help.
help_strings.insert(c_format(" %-19s %s\r\n",
name().c_str(), help().c_str()));
return (true);
}
if ((token.length() == 0)
&& can_complete()
&& (! is_argument_expected())) {
// The last token, and there is space at the end,
// so print the "default" help.
help_strings.insert(c_format(" %-19s %s\r\n",
"<[Enter]>", "Execute this command"));
ret_value = true;
}
// Not the last token, so search down for help
list<CliCommand *>::iterator iter;
for (iter = child_command_list().begin();
iter != child_command_list().end();
++iter) {
CliCommand *cli_command = *iter;
string tmp_token_line = copy_token(token) + token_line;
ret_value |= cli_command->find_command_help(tmp_token_line.c_str(),
tmp_token_line.length(),
help_strings);
}
if (can_pipe() && (cli_command_pipe() != NULL)) {
// Add the pipe completions
string tmp_token_line = copy_token(token) + token_line;
ret_value |= cli_command_pipe()->find_command_help(
tmp_token_line.c_str(),
tmp_token_line.length(),
help_strings);
}
return (ret_value);
}
bool
CliCommand::can_complete()
{
if (has_cli_process_callback() || allow_cd())
return true;
return false;
}
CliCommand *
CliCommand::cli_command_pipe()
{
if (_root_command != this)
return (_root_command->cli_command_pipe());
else
return (_cli_command_pipe);
}
void
CliCommand::set_dynamic_children_callback(DYNAMIC_CHILDREN_CALLBACK v)
{
XLOG_ASSERT(!_global_name.empty());
_dynamic_children_callback = v;
_has_dynamic_children = true;
}
bool
CliCommand::has_dynamic_process_callback()
{
return (!_dynamic_process_callback.is_empty());
}
bool
CliCommand::has_dynamic_interrupt_callback()
{
return (!_dynamic_interrupt_callback.is_empty());
}
bool
CliCommand::has_cli_process_callback()
{
if (_has_dynamic_children) {
//
// Force the children to be evaluated, which forces the
// cli_process_callback to be set.
//
child_command_list();
}
return (!_cli_process_callback.is_empty());
}
bool
CliCommand::has_cli_interrupt_callback()
{
return (!_cli_interrupt_callback.is_empty());
}
bool
CliCommand::has_type_match_cb() const
{
return (! _type_match_cb.is_empty());
}
list<CliCommand *>&
CliCommand::child_command_list()
{
string error_msg;
if (_has_dynamic_children)
XLOG_ASSERT(_child_command_list.empty());
// Handle dynamic children generation
if (_child_command_list.empty() && _has_dynamic_children) {
// Now we've run this, we won't need to run it again.
_has_dynamic_children = false;
// Add dynamic children
XLOG_ASSERT(global_name().size() > 0);
map<string, CliCommandMatch> dynamic_children;
map<string, CliCommandMatch>::iterator iter;
dynamic_children = _dynamic_children_callback->dispatch(global_name());
CliCommand *new_cmd;
for (iter = dynamic_children.begin();
iter != dynamic_children.end();
++iter) {
const CliCommandMatch& ccm = iter->second;
const string& command_name = ccm.command_name();
const string& help_string = ccm.help_string();
bool is_executable = ccm.is_executable();
bool can_pipe = ccm.can_pipe();
bool default_nomore_mode = ccm.default_nomore_mode();
bool is_command_argument = ccm.is_command_argument();
bool is_argument_expected = ccm.is_argument_expected();
new_cmd = add_command(command_name, help_string, false, error_msg);
if (new_cmd == NULL) {
XLOG_FATAL("Cannot add command '%s' to parent '%s': %s",
command_name.c_str(), name().c_str(),
error_msg.c_str());
}
vector<string> child_global_name = global_name();
child_global_name.push_back(command_name);
new_cmd->set_global_name(child_global_name);
new_cmd->set_can_pipe(can_pipe);
new_cmd->set_default_nomore_mode(default_nomore_mode);
new_cmd->set_is_command_argument(is_command_argument);
new_cmd->set_is_argument_expected(is_argument_expected);
new_cmd->set_type_match_cb(ccm.type_match_cb());
new_cmd->set_dynamic_children_callback(_dynamic_children_callback);
new_cmd->set_dynamic_process_callback(_dynamic_process_callback);
new_cmd->set_dynamic_interrupt_callback(_dynamic_interrupt_callback);
if (is_executable) {
new_cmd->set_cli_process_callback(_dynamic_process_callback);
new_cmd->set_cli_interrupt_callback(_dynamic_interrupt_callback);
}
}
}
return (_child_command_list);
}
syntax highlighted by Code2HTML, v. 0.9.1