update shell with new function binding strategy and new help desk print-out

This commit is contained in:
tangxifan 2020-01-22 16:05:14 -07:00
parent 7073e4d082
commit f8c5c1a117
4 changed files with 243 additions and 34 deletions

View File

@ -37,10 +37,11 @@ namespace minishell {
* // Add execute function to the command 'read_arch'
* // This is the function to be called when the command is used
* // Assume that you have a function read_arch() which does the job of read architecture
* // Note that the function read_arch() should return a void with only two arguments
* // void read_arch(T, const CommandContext&);
* // Note that the function read_arch() should return a void with only three arguments
* // void read_arch(T, const Command&, const CommandContext&);
* // The first argument is the template type you set when define the shell
* // The second argument is a read-only object storing the parsing results for the command
* // The second argument is a read-only object storing the options for the command
* // The third argument is a read-only object storing the parsing results for the command
* shell.set_command_execute_function(cmd_read_arch, &read_arch);
*
* // Run a shell
@ -53,6 +54,15 @@ class Shell {
typedef vtr::vector<ShellCommandId, ShellCommandId>::const_iterator shell_command_iterator;
/* Create range */
typedef vtr::Range<shell_command_iterator> shell_command_range;
/* Enumeration of command types in a shell
* Built-in commands have their own execute functions inside the shell
*/
enum e_exec_func_type {
STANDARD,
SHORT,
BUILTIN,
NUM_EXEC_FUNC_TYPES
};
public: /* Constructor */
Shell<T>(const char* name);
public: /* Public accessors */
@ -61,26 +71,47 @@ class Shell {
shell_command_range commands() const;
ShellCommandId command(const std::string& name) const;
std::string command_description(const ShellCommandId& cmd_id) const;
ShellCommandClassId command_class(const ShellCommandId& cmd_id) const;
std::string command_class_name(const ShellCommandClassId& cmd_class_id) const;
/* We force a read-only return objects for command and command context
* because users should NOT modify any of them!
*/
const Command& command(const ShellCommandId& cmd_id) const;
const CommandContext& command_context(const ShellCommandId& cmd_id) const;
std::vector<ShellCommandId> command_dependency(const ShellCommandId& cmd_id) const;
std::vector<ShellCommandId> commands_by_class(const ShellCommandClassId& cmd_class_id) const;
public: /* Public mutators */
void add_title(const char* title);
ShellCommandId add_command(const Command& cmd, const char* descr);
void set_command_class(const ShellCommandId& cmd_id, const ShellCommandClassId& cmd_class_id);
/* Link the execute function to a command
* We support here three types of functions to be executed in the shell
* 1. Standard function, including the data exchange <T> and commands
* 2. Short function, including only the data exchange <T>
* 3. Built-in function, including only the shell envoriment variables
* Users just need to specify the function object and its type will be automatically inferred
*/
void set_command_execute_function(const ShellCommandId& cmd_id,
std::function<void(T&, const Command&, const CommandContext&)> exec_func);
void set_command_execute_function(const ShellCommandId& cmd_id,
std::function<void(T&)> exec_func);
void set_command_execute_function(const ShellCommandId& cmd_id,
std::function<void()> exec_func);
void set_command_dependency(const ShellCommandId& cmd_id,
const std::vector<ShellCommandId> cmd_dependency);
ShellCommandClassId add_command_class(const char* name);
public: /* Public validators */
bool valid_command_id(const ShellCommandId& cmd_id) const;
bool valid_command_class_id(const ShellCommandClassId& cmd_class_id) const;
public: /* Public executors */
/* Start the interactive mode, where users will type-in command by command */
void run_interactive_mode(T& context);
/* Start the script mode, where users provide a file which includes all the commands to run */
void run_script_mode(const char* script_file_name, T& context);
/* Print all the commands by their classes. This is actually the help desk */
void print_commands() const;
/* Quit the shell */
void exit() const;
private: /* Private executors */
/* Execute a command, the command line is the user's input to launch a command
* The common_context is the data structure to exchange data between commands
@ -93,6 +124,12 @@ class Shell {
/* Title of the shell, this will appear in the interactive mode as an introduction */
std::string title_;
/* Unique ids for each class of command */
vtr::vector<ShellCommandClassId, ShellCommandClassId> command_class_ids_;
/* Names for each class of command */
vtr::vector<ShellCommandClassId, std::string> command_class_names_;
/* Unique ids for each command */
vtr::vector<ShellCommandId, ShellCommandId> command_ids_;
@ -105,8 +142,23 @@ class Shell {
/* Description of the command, this is going to be printed out in the help desk */
vtr::vector<ShellCommandId, std::string> command_description_;
/* Function pointers to execute each command */
vtr::vector<ShellCommandId, std::function<void(T&, const Command&, const CommandContext&)>> command_execute_functions_;
/* Class ids for each command */
vtr::vector<ShellCommandId, ShellCommandClassId> command_classes_;
/* Function pointers to execute each command
* We support here three types of functions to be executed in the shell
* 1. Standard function, including the data exchange <T> and commands
* 2. Short function, including only the data exchange <T>
* 3. Built-in function, including only the shell envoriment variables
*/
vtr::vector<ShellCommandId, std::function<void(T&, const Command&, const CommandContext&)>> command_standard_execute_functions_;
vtr::vector<ShellCommandId, std::function<void(T&)>> command_short_execute_functions_;
vtr::vector<ShellCommandId, std::function<void()>> command_builtin_execute_functions_;
/* Type of execute functions for each command.
* This is supposed to be an internal data ONLY
*/
vtr::vector<ShellCommandId, e_exec_func_type> command_execute_function_types_;
/* Dependency graph for different commands,
* This helps the shell interface to check if a command need other commands to be run before its execution
@ -115,11 +167,13 @@ class Shell {
/* Fast name look-up */
std::map<std::string, ShellCommandId> command_name2ids_;
std::map<std::string, ShellCommandClassId> command_class2ids_;
vtr::vector<ShellCommandClassId, std::vector<ShellCommandId>> commands_by_classes_;
};
} /* End namespace minshell */
/* Include the implementation functions in the header file */
/* Include the template implementation functions in the header file */
#include "shell.tpp"
#endif

View File

@ -2,6 +2,7 @@
* Member functions for class Shell
********************************************************************/
#include <fstream>
#include <algorithm>
/* Headers from vtrutil library */
#include "vtr_log.h"
@ -63,6 +64,18 @@ std::string Shell<T>::command_description(const ShellCommandId& cmd_id) const {
return command_description_[cmd_id];
}
template<class T>
ShellCommandClassId Shell<T>::command_class(const ShellCommandId& cmd_id) const {
VTR_ASSERT(true == valid_command_id(cmd_id));
return command_classes_[cmd_id];
}
template<class T>
std::string Shell<T>::command_class_name(const ShellCommandClassId& cmd_class_id) const {
VTR_ASSERT(true == valid_command_class_id(cmd_class_id));
return command_class_names_[cmd_class_id];
}
template<class T>
const Command& Shell<T>::command(const ShellCommandId& cmd_id) const {
VTR_ASSERT(true == valid_command_id(cmd_id));
@ -81,6 +94,12 @@ std::vector<ShellCommandId> Shell<T>::command_dependency(const ShellCommandId& c
return command_dependencies_[cmd_id];
}
template<class T>
std::vector<ShellCommandId> Shell<T>::commands_by_class(const ShellCommandClassId& cmd_class_id) const {
VTR_ASSERT(true == valid_command_class_id(cmd_class_id));
return commands_by_classes_[cmd_class_id];
}
/************************************************************************
* Public mutators
***********************************************************************/
@ -104,7 +123,11 @@ ShellCommandId Shell<T>::add_command(const Command& cmd, const char* descr) {
commands_.emplace_back(cmd);
command_contexts_.push_back(CommandContext(cmd));
command_description_.push_back(descr);
command_execute_functions_.emplace_back();
command_classes_.push_back(ShellCommandClassId::INVALID());
command_execute_function_types_.emplace_back();
command_standard_execute_functions_.emplace_back();
command_short_execute_functions_.emplace_back();
command_builtin_execute_functions_.emplace_back();
command_dependencies_.emplace_back();
/* Register the name in the name2id map */
@ -113,11 +136,41 @@ ShellCommandId Shell<T>::add_command(const Command& cmd, const char* descr) {
return shell_cmd;
}
template<class T>
void Shell<T>::set_command_class(const ShellCommandId& cmd_id, const ShellCommandClassId& cmd_class_id) {
VTR_ASSERT(true == valid_command_id(cmd_id));
VTR_ASSERT(true == valid_command_class_id(cmd_class_id));
command_classes_[cmd_id] = cmd_class_id;
/* Update the fast look-up to spot commands in a class */
std::vector<ShellCommandId>::iterator it = std::find(commands_by_classes_[cmd_class_id].begin(), commands_by_classes_[cmd_class_id].end(), cmd_id);
/* The command does not exist in the class, add it */
if (it == commands_by_classes_[cmd_class_id].end()) {
commands_by_classes_[cmd_class_id].push_back(cmd_id);
}
}
template<class T>
void Shell<T>::set_command_execute_function(const ShellCommandId& cmd_id,
std::function<void(T&, const Command&, const CommandContext&)> exec_func) {
VTR_ASSERT(true == valid_command_id(cmd_id));
command_execute_functions_[cmd_id] = exec_func;
command_execute_function_types_[cmd_id] = STANDARD;
command_standard_execute_functions_[cmd_id] = exec_func;
}
template<class T>
void Shell<T>::set_command_execute_function(const ShellCommandId& cmd_id,
std::function<void(T&)> exec_func) {
VTR_ASSERT(true == valid_command_id(cmd_id));
command_execute_function_types_[cmd_id] = SHORT;
command_short_execute_functions_[cmd_id] = exec_func;
}
template<class T>
void Shell<T>::set_command_execute_function(const ShellCommandId& cmd_id,
std::function<void()> exec_func) {
VTR_ASSERT(true == valid_command_id(cmd_id));
command_execute_function_types_[cmd_id] = BUILTIN;
command_builtin_execute_functions_[cmd_id] = exec_func;
}
template<class T>
@ -131,6 +184,29 @@ void Shell<T>::set_command_dependency(const ShellCommandId& cmd_id,
command_dependencies_[cmd_id] = dependent_cmds;
}
/* Add a command with it description */
template<class T>
ShellCommandClassId Shell<T>::add_command_class(const char* name) {
/* Ensure that the name is unique in the command list */
std::map<std::string, ShellCommandClassId>::const_iterator name_it = command_class2ids_.find(std::string(name));
if (name_it != command_class2ids_.end()) {
return ShellCommandClassId::INVALID();
}
/* This is a legal name. we can create a new id */
ShellCommandClassId cmd_class = ShellCommandClassId(command_class_ids_.size());
command_class_ids_.push_back(cmd_class);
command_class_names_.push_back(std::string(name));
/* Register the name in the name2id map */
command_class2ids_[std::string(name)] = cmd_class;
/* Register in the fast look-up for commands by classes */
commands_by_classes_.emplace_back();
return cmd_class;
}
/************************************************************************
* Public executors
***********************************************************************/
@ -196,12 +272,51 @@ void Shell<T>::run_script_mode(const char* script_file_name, T& context) {
if (cmd_end_pos != std::string::npos) {
cmd_part = line.substr(0, cmd_end_pos);
}
/* Process the command */
/* Process the command only when the line is not empty */
if (!cmd_part.empty()) {
execute_command(cmd_part.c_str(), context);
}
}
fp.close();
}
template <class T>
void Shell<T>::print_commands() const {
/* Print the commands by their classes */
for (const ShellCommandClassId& cmd_class : command_class_ids_) {
/* Print the class name */
VTR_LOG("%s:\n", command_class_names_[cmd_class].c_str());
size_t cnt = 0;
for (const ShellCommandId& cmd : commands_by_classes_[cmd_class]) {
/* Print the command names in this class
* but limited4 command per line for a clean layout
*/
VTR_LOG("%s", commands_[cmd].name().c_str());
cnt++;
if (4 == cnt) {
VTR_LOG("\n");
cnt = 0;
} else {
VTR_LOG("\t");
}
}
/* Put a new line in the end as a splitter */
VTR_LOG("\n");
}
/* Put a new line in the end as a splitter */
VTR_LOG("\n");
}
template <class T>
void Shell<T>::exit() const {
VTR_LOG("Thank you for using %s!\n",
name().c_str());
std::exit(0);
}
/************************************************************************
* Private executors
***********************************************************************/
@ -220,6 +335,8 @@ void Shell<T>::execute_command(const char* cmd_line,
return;
}
/* TODO: Check the dependency graph to see if all the prequistics have been met */
/* Find the command! Parse the options */
if (false == parse_command(tokens, commands_[cmd_id], command_contexts_[cmd_id])) {
/* Echo the command */
@ -230,8 +347,24 @@ void Shell<T>::execute_command(const char* cmd_line,
/* Parse succeed. Let user to confirm selected options */
print_command_context(commands_[cmd_id], command_contexts_[cmd_id]);
/* Execute the command! */
command_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]);
/* Execute the command depending on the type of function ! */
switch (command_execute_function_types_[cmd_id]) {
case STANDARD:
command_standard_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]);
break;
case SHORT:
command_short_execute_functions_[cmd_id](common_context);
break;
case BUILTIN:
command_builtin_execute_functions_[cmd_id]();
break;
default:
/* This is not allowed! Error out */
VTR_LOG("Invalid type of execute function for command '%s'!\n",
commands_[cmd_id].name().c_str());
/* Exit the shell using the exit() function inside this class! */
exit();
}
}
/************************************************************************
@ -242,4 +375,9 @@ bool Shell<T>::valid_command_id(const ShellCommandId& cmd_id) const {
return ( size_t(cmd_id) < command_ids_.size() ) && ( cmd_id == command_ids_[cmd_id] );
}
template<class T>
bool Shell<T>::valid_command_class_id(const ShellCommandClassId& cmd_class_id) const {
return ( size_t(cmd_class_id) < command_class_ids_.size() ) && ( cmd_class_id == command_class_ids_[cmd_class_id] );
}
} /* End namespace minshell */

View File

@ -10,8 +10,10 @@ namespace minishell {
* A strong id for the options used by a command
********************************************************************/
struct shell_command_id_tag;
struct shell_command_class_id_tag;
typedef vtr::StrongId<shell_command_id_tag> ShellCommandId;
typedef vtr::StrongId<shell_command_class_id_tag> ShellCommandClassId;
} /* End namespace minshell */

View File

@ -12,23 +12,6 @@ using namespace minishell;
class ShellContext {
};
static
void shell_cmd_help_executor(ShellContext& context,
const Command& cmd,
const CommandContext& cmd_context) {
VTR_LOG("Help desk:\n");
VTR_LOG("Available commands:\n");
VTR_LOG("help\texit\n");
}
static
void shell_cmd_exit_executor(ShellContext& context,
const Command& cmd,
const CommandContext& cmd_context) {
VTR_LOG("Thank you for using!\n");
exit(1);
}
int main(int argc, char** argv) {
/* Create the command to launch shell in different modes */
Command start_cmd("test_shell");
@ -52,15 +35,45 @@ int main(int argc, char** argv) {
* 2. exit
*/
Shell<ShellContext> shell("test_shell");
shell.add_title("This is a simple test shell\nAuthor: Xifan Tang\n");
std::string shell_title;
Command shell_cmd_help("help");
ShellCommandId shell_cmd_help_id = shell.add_command(shell_cmd_help, "Launch help desk");
shell.set_command_execute_function(shell_cmd_help_id, shell_cmd_help_executor);
shell_title += std::string("The MIT License\n");
shell_title += std::string("\n");
shell_title += std::string("Copyright (c) 2018 LNIS - The University of Utah\n");
shell_title += std::string("\n");
shell_title += std::string("Permission is hereby granted, free of charge, to any person obtaining a copy\n");
shell_title += std::string("of this software and associated documentation files (the \"Software\"), to deal\n");
shell_title += std::string("in the Software without restriction, including without limitation the rights\n");
shell_title += std::string("to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n");
shell_title += std::string("copies of the Software, and to permit persons to whom the Software is\n");
shell_title += std::string("furnished to do so, subject to the following conditions:\n");
shell_title += std::string("\n");
shell_title += std::string("The above copyright notice and this permission notice shall be included in\n");
shell_title += std::string("all copies or substantial portions of the Software.\n");
shell_title += std::string("\n");
shell_title += std::string("THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n");
shell_title += std::string("IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n");
shell_title += std::string("FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n");
shell_title += std::string("AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n");
shell_title += std::string("LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n");
shell_title += std::string("OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n");
shell_title += std::string("THE SOFTWARE.\n");
shell.add_title(shell_title.c_str());
/* Add a new class of commands */
ShellCommandClassId basic_cmd_class = shell.add_command_class("Basic");
Command shell_cmd_exit("exit");
ShellCommandId shell_cmd_exit_id = shell.add_command(shell_cmd_exit, "Exit the shell");
shell.set_command_execute_function(shell_cmd_exit_id, shell_cmd_exit_executor);
shell.set_command_class(shell_cmd_exit_id, basic_cmd_class);
shell.set_command_execute_function(shell_cmd_exit_id, [shell](){shell.exit();});
/* Note: help must be the last to add because the linking to execute function will do a snapshot on the shell */
Command shell_cmd_help("help");
ShellCommandId shell_cmd_help_id = shell.add_command(shell_cmd_help, "Launch help desk");
shell.set_command_class(shell_cmd_help_id, basic_cmd_class);
shell.set_command_execute_function(shell_cmd_help_id, [shell](){shell.print_commands();});
/* Create the data base for the shell */
ShellContext shell_context;
@ -88,6 +101,8 @@ int main(int argc, char** argv) {
shell_context);
return 0;
}
/* Reach here there is something wrong, show the help desk */
print_command_options(start_cmd);
}
return 0;