/******************************************************** * ██████╗ ██████╗████████╗██╗ * ██╔════╝ ██╔════╝╚══██╔══╝██║ * ██║ ███╗██║ ██║ ██║ * ██║ ██║██║ ██║ ██║ * ╚██████╔╝╚██████╗ ██║ ███████╗ * ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ * 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 . * * 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 \"?\" 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 cmds; std::vector 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 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 &cmd_units) { return; } void info(const std::vector &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 &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 &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 &cmd_units) { // enable column|row ... if (cmd_units.size() < 3) throw std::runtime_error("enable: insufficient parameters."); if (cmd_units[1] == "column") { for (size_t i = 2; i < cmd_units.size(); i++) { if (tc.name_index(cmd_units[i]) < 0) tc.column_output(atoi(cmd_units[i].c_str()), Enable); else tc.column_output(cmd_units[i], Enable); } } else if (cmd_units[1] == "row") { for (size_t i = 2; i < cmd_units.size(); i++) { if (tc.name_index(cmd_units[i], true) < 0) tc.row_output(atoi(cmd_units[i].c_str()), Enable); else tc.row_output(cmd_units[i], Enable); } } else throw std::runtime_error("enable: invalid parameters."); return; } void set_disable(const std::vector &cmd_units) { // disable column|row ... if (cmd_units.size() < 3) throw std::runtime_error("disable: insufficient parameters."); if (cmd_units[1] == "column") { for (size_t i = 2; i < cmd_units.size(); i++) { if (tc.name_index(cmd_units[i]) < 0) tc.column_output(atoi(cmd_units[i].c_str()), Disable); else tc.column_output(cmd_units[i], Enable); } } else if (cmd_units[1] == "row") { for (size_t i = 2; i < cmd_units.size(); i++) { if (tc.name_index(cmd_units[i], true) < 0) tc.row_output(atoi(cmd_units[i].c_str()), Disable); else tc.row_output(cmd_units[i], Enable); } } else throw std::runtime_error("disable: invalid parameters."); return; } void load_file(const std::vector &cmd_units) { // load [nohead|column|row|both] [head_num] [] [att_sym] [tag_sym] if (cmd_units.size() < 2) throw std::runtime_error("open: insufficient parameters."); gctl::array 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 &cmd_units) { // save [] [att_sym] [tag_sym] if (cmd_units.size() < 2) throw std::runtime_error("save: insufficient parameters."); gctl::array 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 &cmd_units) { // stats ... 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 &cmd_units) { // type row|col int|float|string 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 &cmd_units) { // title ,,,... [,,,...] if (cmd_units.size() < 3) throw std::runtime_error("title: insufficient parameters."); if (cmd_units[1] == "row") { std::vector row_titles; std::vector 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 col_titles; std::vector 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 &cmd_units) { // math ,,... if (cmd_units.size() < 3) throw std::runtime_error("math: insufficient parameters."); std::vector 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 &cmd_units) { // random normal|uniform [] 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 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 &cmd_units) { // filter row|col regex|math ,,... if (cmd_units.size() < 5) throw std::runtime_error("filter: insufficient parameters."); std::vector 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 &cmd_units) { // insert row|col [] [] 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 display_table(const std::vector &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 [ ...]\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; }