612 lines
19 KiB
C++
612 lines
19 KiB
C++
/********************************************************
|
|
* ██████╗ ██████╗████████╗██╗
|
|
* ██╔════╝ ██╔════╝╚══██╔══╝██║
|
|
* ██║ ███╗██║ ██║ ██║
|
|
* ██║ ██║██║ ██║ ██║
|
|
* ╚██████╔╝╚██████╗ ██║ ███████╗
|
|
* ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝
|
|
* Geophysical Computational Tools & Library (GCTL)
|
|
*
|
|
* Copyright (c) 2023 Yi Zhang (yizhang-geo@zju.edu.cn)
|
|
*
|
|
* GCTL is distributed under a dual licensing scheme. You can redistribute
|
|
* it and/or modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation, either version 2
|
|
* of the License, or (at your option) any later version. You should have
|
|
* received a copy of the GNU Lesser General Public License along with this
|
|
* program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* If the terms and conditions of the LGPL v.2. would prevent you from using
|
|
* the GCTL, please consider the option to obtain a commercial license for a
|
|
* fee. These licenses are offered by the GCTL's original author. As a rule,
|
|
* licenses are provided "as-is", unlimited in time for a one time fee. Please
|
|
* send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget
|
|
* to include some description of your company and the realm of its activities.
|
|
* Also add information on how to contact you by electronic and paper mail.
|
|
******************************************************/
|
|
|
|
#include "dsviewer.h"
|
|
// We use the editline library to handle all inputs
|
|
#include "editline.h"
|
|
|
|
cmd_pair curr_cmd; // Executing command
|
|
gctl::dsv_io tc; // Grid object
|
|
|
|
extern "C" {
|
|
|
|
/*
|
|
* Strip whitespace from the start and end of STRING. Return a pointer
|
|
* into STRING.
|
|
*/
|
|
char *stripwhite(char *string)
|
|
{
|
|
char *s, *t;
|
|
|
|
for (s = string; isspace(*s); s++) ;
|
|
|
|
if (*s == 0)
|
|
return s;
|
|
|
|
t = s + strlen(s) - 1;
|
|
while (t > s && isspace(*t))
|
|
t--;
|
|
*++t = '\0';
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Generator function for command completion. STATE lets us
|
|
know whether to start from scratch; without any state
|
|
(i.e. STATE == 0), then we start at the top of the list. */
|
|
char *command_generator(const char *text, int state)
|
|
{
|
|
static int list_index, len;
|
|
|
|
/* If this is a new word to complete, initialize now. This
|
|
includes saving the length of TEXT for efficiency, and
|
|
initializing the index variable to 0. */
|
|
if (!state)
|
|
{
|
|
list_index = 0;
|
|
len = strlen(text);
|
|
}
|
|
|
|
/* Return the next name which partially matches from the command list. */
|
|
while (list_index < CMD_NUM)
|
|
{
|
|
if (std::string(text) == commands[list_index].name.substr(0, len))
|
|
{
|
|
//const char* name = commands[list_index].name.data();
|
|
return strdup(commands[list_index].name.data());
|
|
}
|
|
else list_index++;
|
|
}
|
|
|
|
/* If no names matched, then return NULL. */
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* Attempt to complete on the contents of TEXT. START and END
|
|
* bound the region of rl_line_buffer that contains the word to
|
|
* complete. TEXT is the word to complete. We can use the entire
|
|
* contents of rl_line_buffer in case we want to do some simple
|
|
* parsing. Return the array of matches, or NULL if there aren't any.
|
|
*/
|
|
char **dsviewer_completion(const char *text, int start, int end)
|
|
{
|
|
char **matches = nullptr;
|
|
|
|
/* If this word is at the start of the line, then it is a command
|
|
to complete. Otherwise it is the name of a file in the current
|
|
directory. */
|
|
if (start == 0) matches = rl_completion_matches(text, command_generator);
|
|
|
|
return matches;
|
|
}
|
|
|
|
void initialize_readline(void)
|
|
{
|
|
/* Allow conditional parsing of the ~/.inputrc file. */
|
|
rl_readline_name = "dsviewer";
|
|
|
|
/* Tell the completer that we want a crack first. */
|
|
rl_attempted_completion_function = dsviewer_completion;
|
|
}
|
|
|
|
} // End C section
|
|
|
|
void display_cmds()
|
|
{
|
|
std::clog << "Command:\n";
|
|
for (size_t i = 0; i < CMD_NUM - 1; i++)
|
|
{
|
|
std::clog << std::setw(12) << commands[i].name << ":\t" << commands[i].brief << "\n";
|
|
}
|
|
|
|
std::clog << "\nEnter \"<command>?\" to see detailed instructions.\n";
|
|
|
|
std::clog << "\nCmdFile:\n";
|
|
std::clog << "Each line of the input file(s) will be parsed as a command.\nEmpty lines and lines start with '#' will be skipped.\n";
|
|
|
|
std::clog << "\nHereDoc:\n";
|
|
std::clog << "You can use HereDoc to input commands. A simple example is:\n";
|
|
std::clog << "dsviewer << EOF\n";
|
|
std::clog << "open file1.csv\n";
|
|
std::clog << "info\n";
|
|
std::clog << "EOF\n";
|
|
return;
|
|
}
|
|
|
|
void display_help(std::string input_cmd)
|
|
{
|
|
std::string install_dir = GCTL_INSTALL_PREFIX;
|
|
std::ifstream helpin;
|
|
open_infile(helpin, install_dir + "/sbin/share/dsviewer", ".md");
|
|
|
|
std::string tmp_l, tmp_help;
|
|
std::vector<std::string> cmds;
|
|
std::vector<std::string> helps;
|
|
while (getline(helpin, tmp_l))
|
|
{
|
|
if (tmp_l.substr(0, 4) == "####")
|
|
{
|
|
cmds.push_back(tmp_l.substr(5));
|
|
tmp_help = "";
|
|
while (getline(helpin, tmp_l))
|
|
{
|
|
if (tmp_l == "") break;
|
|
else tmp_help += tmp_l + "\n";
|
|
}
|
|
helps.push_back(tmp_help);
|
|
}
|
|
}
|
|
helpin.close();
|
|
|
|
std::string cmd_str;
|
|
for (size_t j = 0; j < cmds.size(); j++)
|
|
{
|
|
parse_string_to_value(cmds[j], ' ', true, cmd_str);
|
|
if (input_cmd == cmd_str)
|
|
{
|
|
replace_all(tmp_l, cmds[j], "\\", "");
|
|
std::cout << GCTL_BOLDGREEN << tmp_l << GCTL_RESET << "\n";
|
|
std::cout << helps[j] << "\n";
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void exec_cmd(std::string cmd)
|
|
{
|
|
std::string cmd_name;
|
|
parse_string_to_value(cmd, ' ', true, cmd_name);
|
|
|
|
// show instruction if there is a question mark at end of the command
|
|
if (cmd_name.back() == '?')
|
|
{
|
|
cmd_name = cmd_name.substr(0, cmd_name.length() - 1);
|
|
display_help(cmd_name);
|
|
return;
|
|
}
|
|
|
|
// set default command to null
|
|
curr_cmd = commands[CMD_NUM - 1];
|
|
for (size_t i = 0; i < CMD_NUM - 1; i++)
|
|
{
|
|
if (cmd_name == commands[i].name)
|
|
{
|
|
curr_cmd = commands[i]; break;
|
|
}
|
|
}
|
|
|
|
if (curr_cmd.func_p == nullptr) throw gctl::runtime_error("Invalid command: " + cmd_name);
|
|
|
|
std::vector<std::string> cmd_units;
|
|
parse_string_with_quotes(cmd, cmd_units);
|
|
|
|
return curr_cmd.func_p(cmd_units);
|
|
}
|
|
|
|
// This function is defined to avoid potential breakdown while running in script mode.
|
|
void quit(const std::vector<std::string> &cmd_units)
|
|
{
|
|
return;
|
|
}
|
|
|
|
void info(const std::vector<std::string> &cmd_units)
|
|
{
|
|
std::smatch ret;
|
|
std::regex pata("att"), patt("tag"), patc("col"), patr("row"), path("hdr");
|
|
|
|
// info [att|tag|hdr|col|row]
|
|
int info_code = 0;
|
|
if (cmd_units.size() > 1)
|
|
{
|
|
if (regex_search(cmd_units[1], ret, pata)) info_code = AttInfo;
|
|
if (regex_search(cmd_units[1], ret, patt)) info_code = info_code|TagInfo;
|
|
if (regex_search(cmd_units[1], ret, patc)) info_code = info_code|ColInfo;
|
|
if (regex_search(cmd_units[1], ret, patr)) info_code = info_code|RowInfo;
|
|
if (regex_search(cmd_units[1], ret, path)) info_code = info_code|HeadInfo;
|
|
tc.info(info_code);
|
|
}
|
|
else tc.info(ColInfo);
|
|
return;
|
|
}
|
|
|
|
void head(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// head [number]
|
|
int h = 10;
|
|
if (cmd_units.size() > 1) h = atoi(cmd_units[1].c_str());
|
|
if (h <= 0) throw std::runtime_error("head: invalid number.");
|
|
|
|
_1s_vector cnames = tc.column_names();
|
|
display_vector(cnames, std::cout, tc.delimeter());
|
|
|
|
_1s_array line;
|
|
for (size_t i = 1; i <= h; i++)
|
|
{
|
|
tc.get_row(line, i);
|
|
line.show(std::cout, tc.delimeter());
|
|
}
|
|
return;
|
|
}
|
|
|
|
void tail(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// tail [number]
|
|
int h = 10;
|
|
if (cmd_units.size() > 1) h = atoi(cmd_units[1].c_str());
|
|
if (h <= 0) throw std::runtime_error("tail: invalid number.");
|
|
|
|
_1s_vector cnames = tc.column_names();
|
|
display_vector(cnames, std::cout, tc.delimeter());
|
|
|
|
_1s_array line;
|
|
for (size_t i = tc.row_number() - h + 1; i <= tc.row_number(); i++)
|
|
{
|
|
tc.get_row(line, i);
|
|
line.show(std::cout, tc.delimeter());
|
|
}
|
|
return;
|
|
}
|
|
|
|
void set_enable(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// enable table|column|row [<name1>,<name2>...]
|
|
if (cmd_units.size() < 2) throw std::runtime_error("enable: insufficient parameters.");
|
|
|
|
if (cmd_units[1] == "table") tc.table_output(Enable);
|
|
else if (cmd_units[1] == "column")
|
|
{
|
|
for (size_t i = 2; i < cmd_units.size(); i++)
|
|
{
|
|
tc.column_output(cmd_units[i], Enable);
|
|
}
|
|
}
|
|
else if (cmd_units[1] == "row")
|
|
{
|
|
for (size_t i = 2; i < cmd_units.size(); i++)
|
|
{
|
|
tc.row_output(cmd_units[i], Enable);
|
|
}
|
|
}
|
|
else throw std::runtime_error("enable: invalid parameters.");
|
|
return;
|
|
}
|
|
|
|
void set_disable(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// disable table|column|row [<name1>,<name2>...]
|
|
if (cmd_units.size() < 2) throw std::runtime_error("disable: insufficient parameters.");
|
|
|
|
if (cmd_units[1] == "table") tc.table_output(Disable);
|
|
else if (cmd_units[1] == "column")
|
|
{
|
|
for (size_t i = 2; i < cmd_units.size(); i++)
|
|
{
|
|
tc.column_output(cmd_units[i], Disable);
|
|
}
|
|
}
|
|
else if (cmd_units[1] == "row")
|
|
{
|
|
for (size_t i = 2; i < cmd_units.size(); i++)
|
|
{
|
|
tc.row_output(cmd_units[i], Disable);
|
|
}
|
|
}
|
|
else throw std::runtime_error("disable: invalid parameters.");
|
|
return;
|
|
}
|
|
|
|
void load_file(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// load <file> [nohead|column|row|both] [head_num] [<delimeter>] [att_sym] [tag_sym]
|
|
if (cmd_units.size() < 2) throw std::runtime_error("open: insufficient parameters.");
|
|
|
|
gctl::array<std::string> copy_str(5, "null");
|
|
for (size_t i = 0; i < GCTL_MIN(cmd_units.size() - 2, 5); i++)
|
|
{
|
|
copy_str[i] = cmd_units[i + 2];
|
|
}
|
|
|
|
int ht = NoHead;
|
|
if (copy_str[0] == "nohead") ht = NoHead;
|
|
else if (copy_str[0] == "column") ht = ColHead;
|
|
else if (copy_str[0] == "row") ht = RowHead;
|
|
else if (copy_str[0] == "both") ht = RowHead|ColHead;
|
|
|
|
int hnum = 0;
|
|
if (copy_str[1] != "null") hnum = atoi(copy_str[1].c_str());
|
|
if (hnum != 0) tc.head_number(hnum);
|
|
|
|
if (copy_str[2] != "null") tc.delimeter(copy_str[2][0]);
|
|
if (copy_str[3] != "null") tc.annotation_symbol(copy_str[3][0]);
|
|
if (copy_str[4] != "null") tc.tag_symbol(copy_str[4][0]);
|
|
|
|
std::string naked_name, exten_name;
|
|
parse_filename(cmd_units[1], naked_name, exten_name);
|
|
|
|
if (exten_name == ".csv") tc.load_csv(naked_name);
|
|
else tc.load_text(naked_name, exten_name, ht);
|
|
return;
|
|
}
|
|
|
|
void save_file(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// save <file> [<delimeter>] [att_sym] [tag_sym]
|
|
if (cmd_units.size() < 2) throw std::runtime_error("save: insufficient parameters.");
|
|
|
|
gctl::array<std::string> copy_str(3, "null");
|
|
for (size_t i = 0; i < GCTL_MIN(cmd_units.size() - 2, 3); i++)
|
|
{
|
|
copy_str[i] = cmd_units[i + 2];
|
|
}
|
|
|
|
if (copy_str[0] != "null") tc.delimeter(copy_str[0][0]);
|
|
if (copy_str[1] != "null") tc.annotation_symbol(copy_str[1][0]);
|
|
if (copy_str[2] != "null") tc.tag_symbol(copy_str[2][0]);
|
|
|
|
std::string naked_name, exten_name;
|
|
parse_filename(cmd_units[1], naked_name, exten_name);
|
|
|
|
if (exten_name == ".csv") tc.save_csv(naked_name);
|
|
else tc.save_text(naked_name, exten_name);
|
|
return;
|
|
}
|
|
|
|
void statistic(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// stats <column> <column>...
|
|
if (cmd_units.size() < 2) throw std::runtime_error("stats: insufficient parameters.");
|
|
|
|
_1d_array data;
|
|
for (size_t i = 1; i < cmd_units.size(); i++)
|
|
{
|
|
tc.get_column(data, cmd_units[i]);
|
|
std::clog << "column: " << cmd_units[i]
|
|
<< " | " << data.min() << "/" << data.mean() << "/" << data.max()
|
|
<< " | STD: " << data.std() << "\n";
|
|
}
|
|
return;
|
|
}
|
|
|
|
void set_type(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// type row|col int|float|string <name>
|
|
if (cmd_units.size() < 4) throw std::runtime_error("type: insufficient parameters.");
|
|
if (cmd_units[1] == "row")
|
|
{
|
|
if (cmd_units[2] == "int") tc.row_type(Int, cmd_units[3]);
|
|
else if (cmd_units[2] == "float") tc.row_type(Float, cmd_units[3]);
|
|
else if (cmd_units[2] == "string") tc.row_type(String, cmd_units[3]);
|
|
else throw std::runtime_error("type: invalid parameters.");
|
|
}
|
|
else if (cmd_units[1] == "col")
|
|
{
|
|
if (cmd_units[2] == "int") tc.column_type(Int, cmd_units[3]);
|
|
else if (cmd_units[2] == "float") tc.column_type(Float, cmd_units[3]);
|
|
else if (cmd_units[2] == "string") tc.column_type(String, cmd_units[3]);
|
|
else throw std::runtime_error("type: invalid parameters.");
|
|
}
|
|
else throw std::runtime_error("type: invalid parameters.");
|
|
return;
|
|
}
|
|
|
|
void set_titles(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// title <row|col> <t1>,<t2>,<t3>,... [<id1>,<id2>,<id3>,...]
|
|
if (cmd_units.size() < 3) throw std::runtime_error("title: insufficient parameters.");
|
|
if (cmd_units[1] == "row")
|
|
{
|
|
std::vector<std::string> row_titles;
|
|
std::vector<int> row_ids;
|
|
parse_string_to_vector(cmd_units[2], ',', row_titles);
|
|
if (cmd_units.size() >= 4)
|
|
{
|
|
parse_string_to_vector(cmd_units[3], ',', row_ids);
|
|
if (row_titles.size() != row_ids.size()) throw std::runtime_error("title: invalid parameters.");
|
|
tc.row_names(row_titles, row_ids);
|
|
}
|
|
else tc.row_names(row_titles);
|
|
}
|
|
else if (cmd_units[1] == "col")
|
|
{
|
|
std::vector<std::string> col_titles;
|
|
std::vector<int> col_ids;
|
|
parse_string_to_vector(cmd_units[2], ',', col_titles);
|
|
if (cmd_units.size() >= 4)
|
|
{
|
|
parse_string_to_vector(cmd_units[3], ',', col_ids);
|
|
if (col_titles.size()!= col_ids.size()) throw std::runtime_error("title: invalid parameters.");
|
|
tc.column_names(col_titles, col_ids);
|
|
}
|
|
else tc.column_names(col_titles);
|
|
}
|
|
else throw std::runtime_error("title: invalid parameters.");
|
|
return;
|
|
}
|
|
|
|
void math_func(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// math <func> <col1>,<col2>,<col3>...
|
|
if (cmd_units.size() < 3) throw std::runtime_error("math: insufficient parameters.");
|
|
|
|
std::vector<std::string> col_names;
|
|
parse_string_to_vector(cmd_units[2], ',', col_names);
|
|
|
|
tc.cal_column(cmd_units[1], col_names, 12);
|
|
return;
|
|
}
|
|
|
|
void rand_data(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// random normal|uniform <p1> <p2> [<colname>]
|
|
if (cmd_units.size() < 4) throw std::runtime_error("random: insufficient parameters.");
|
|
|
|
random_type_e rd_type = RdNormal;
|
|
if (cmd_units[1] == "normal") rd_type = RdNormal;
|
|
else if (cmd_units[1] == "uniform") rd_type = RdUniform;
|
|
else throw std::runtime_error("random: invalid parameters.");
|
|
|
|
double p1 = atof(cmd_units[2].c_str());
|
|
double p2 = atof(cmd_units[3].c_str());
|
|
array<double> rd_data(tc.row_number());
|
|
rd_data.random_float(p1, p2, rd_type);
|
|
|
|
if (cmd_units.size() >= 5) tc.fill_column(rd_data, cmd_units[4], 12);
|
|
else
|
|
{
|
|
std::string col_name = "RdData";
|
|
tc.add_column(col_name);
|
|
tc.fill_column(rd_data, col_name, 12);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void filt_data(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// filter row|col regex|math <expression> <name1>,<name2>,<name3>...
|
|
if (cmd_units.size() < 5) throw std::runtime_error("filter: insufficient parameters.");
|
|
std::vector<std::string> tar_names;
|
|
parse_string_to_vector(cmd_units[4], ',', tar_names);
|
|
|
|
table_headtype_e thead;
|
|
if (cmd_units[1] == "row") thead = RowHead;
|
|
else if (cmd_units[1] == "col") thead = ColHead;
|
|
else throw std::runtime_error("filter: invalid parameters.");
|
|
|
|
if (cmd_units[2] == "regex") tc.filter(cmd_units[3], tar_names[0], thead);
|
|
else if (cmd_units[2] == "math") tc.filter(cmd_units[3], tar_names, thead);
|
|
else throw std::runtime_error("filter: invalid parameters.");
|
|
return;
|
|
}
|
|
|
|
void insert_data(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// insert row|col [<new-name>] [<insert-name>]
|
|
if (cmd_units.size() < 2) throw std::runtime_error("insert: insufficient parameters.");
|
|
|
|
if (cmd_units[1] == "row")
|
|
{
|
|
if (cmd_units.size() == 2) tc.add_row();
|
|
else if (cmd_units.size() == 3) tc.add_row(cmd_units[2]);
|
|
else if (cmd_units.size() == 4) tc.add_row(cmd_units[2], cmd_units[3]);
|
|
}
|
|
else if (cmd_units[1] == "col")
|
|
{
|
|
if (cmd_units.size() == 2) tc.add_column();
|
|
else if (cmd_units.size() == 3) tc.add_column(cmd_units[2]);
|
|
else if (cmd_units.size() == 4) tc.add_column(cmd_units[2], cmd_units[3]);
|
|
}
|
|
else throw std::runtime_error("insert: invalid parameters.");
|
|
return;
|
|
}
|
|
|
|
void set_data(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// set row|col \<name\> \<value\>
|
|
if (cmd_units.size() < 4) throw std::runtime_error("set: insufficient parameters.");
|
|
|
|
if (cmd_units[1] == "row")
|
|
{
|
|
_1s_array row_data(tc.row_number(), cmd_units[3]);
|
|
tc.fill_row(row_data, cmd_units[2]);
|
|
}
|
|
else if (cmd_units[1] == "col")
|
|
{
|
|
_1s_array col_data(tc.col_number(), cmd_units[3]);
|
|
tc.fill_column(col_data, cmd_units[2]);
|
|
}
|
|
else throw std::runtime_error("set: invalid parameters.");
|
|
return;
|
|
}
|
|
|
|
void display_table(const std::vector<std::string> &cmd_units)
|
|
{
|
|
// view
|
|
tc.display();
|
|
return;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
if (argc >= 2)
|
|
{
|
|
std::string tmp_l;
|
|
std::ifstream cmdin;
|
|
|
|
// Run commands from files. Each line is a command.
|
|
// You can give more than one file
|
|
for (size_t i = 1; i < argc; i++) try
|
|
{
|
|
open_infile(cmdin, argv[i]);
|
|
while (getline(cmdin, tmp_l) && tmp_l != "" && tmp_l[0] != '#')
|
|
{
|
|
exec_cmd(tmp_l);
|
|
}
|
|
cmdin.close();
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
GCTL_ShowWhatError(e.what(), GCTL_ERROR_ERROR, 0, "run dsviewer in the interactive mode for instructions.", 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setlocale(LC_CTYPE, "");
|
|
initialize_readline(); /* Bind our completer. */
|
|
|
|
display_logo();
|
|
std::clog << "dsviewer - read, manipulate and write dsv/csv files.\n";
|
|
std::clog << "Usage: dsviewer [<cmdfile1> <cmdfile2>...]\n";
|
|
std::clog << "Enter '?' to see all available commands.\n";
|
|
|
|
std::string cmd_str;
|
|
char *c_line = (char *)NULL;
|
|
char *c_line_s = (char *)NULL;
|
|
bool quit = false;
|
|
|
|
while (!quit) try
|
|
{
|
|
c_line = readline(">> ");
|
|
if (!c_line) break;
|
|
|
|
c_line_s = stripwhite(c_line);
|
|
cmd_str = c_line_s;
|
|
|
|
if (cmd_str == "quit") quit = true;
|
|
else if (cmd_str == "?") display_cmds();
|
|
else exec_cmd(cmd_str);
|
|
|
|
free(c_line);
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
GCTL_ShowWhatError(e.what(), GCTL_ERROR_ERROR, 0, "Enter 'help' for instructions.", 0);
|
|
}
|
|
}
|
|
return 0;
|
|
} |