diff --git a/CMakeLists.txt b/CMakeLists.txt index 158865e..13ffd60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,9 @@ option(GCTL_EEMD "Use the EEMD library" ON) option(GCTL_OPENBLAS "Use the Openblas library" OFF) option(GCTL_CHECK_BOUNDER "Check array's index" OFF) option(GCTL_CHECK_SIZE "Check array's size" OFF) +# 传递安装地址给编译期宏变量 +option(GCTL_INSTALL_PREFIX "Pass the install directory." ON) +set(DIR_VAR ${CMAKE_INSTALL_PREFIX}) message(STATUS "Platform: " ${CMAKE_HOST_SYSTEM_NAME}) message(STATUS "Install prefix: " ${CMAKE_INSTALL_PREFIX}) @@ -79,4 +82,5 @@ configure_file( # 添加库源文件地址 add_subdirectory(lib) -add_subdirectory(example) \ No newline at end of file +add_subdirectory(example) +add_subdirectory(tool) \ No newline at end of file diff --git a/config.h.in b/config.h.in index a1b5b98..8ff890e 100644 --- a/config.h.in +++ b/config.h.in @@ -1,3 +1,4 @@ +#cmakedefine GCTL_INSTALL_PREFIX "${DIR_VAR}" #cmakedefine GCTL_OPENMP #cmakedefine GCTL_NETCDF #cmakedefine GCTL_FFTW3 diff --git a/lib/gctl_config.h b/lib/gctl_config.h index 155a01f..5971345 100644 --- a/lib/gctl_config.h +++ b/lib/gctl_config.h @@ -1,3 +1,4 @@ +#define GCTL_INSTALL_PREFIX "/opt/stow/gctl" #define GCTL_OPENMP #define GCTL_NETCDF #define GCTL_FFTW3 diff --git a/lib/utility/stream.cpp b/lib/utility/stream.cpp index 722ebb1..a30fd4d 100644 --- a/lib/utility/stream.cpp +++ b/lib/utility/stream.cpp @@ -427,4 +427,11 @@ void gctl::parse_string_with_quotes(std::string in_str, std::vector } } return; +} + +void gctl::parse_filename(std::string filename, std::string &naked_name, std::string &exten_name) +{ + naked_name = filename.substr(0, filename.rfind(".")); + exten_name = filename.substr(filename.find_last_of('.')); + return; } \ No newline at end of file diff --git a/lib/utility/stream.h b/lib/utility/stream.h index 345fe96..b9b15d0 100644 --- a/lib/utility/stream.h +++ b/lib/utility/stream.h @@ -188,6 +188,15 @@ namespace gctl * @param str_vec 输出字符串向量 */ void parse_string_with_quotes(std::string in_str, std::vector &str_vec); + + /** + * @brief 解析文件路径 + * + * @param in_str 输入字符串 + * @param naked_name 不含后缀的文件名 + * @param exten_name 文件后缀名 + */ + void parse_filename(std::string filename, std::string &naked_name, std::string &exten_name); } #endif // _GCTL_STREAM_H \ No newline at end of file diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt new file mode 100644 index 0000000..fcace63 --- /dev/null +++ b/tool/CMakeLists.txt @@ -0,0 +1,2 @@ +# add directories to compile the tool +add_subdirectory(dsviewer) \ No newline at end of file diff --git a/tool/dsviewer/CMakeLists.txt b/tool/dsviewer/CMakeLists.txt new file mode 100644 index 0000000..98bcc09 --- /dev/null +++ b/tool/dsviewer/CMakeLists.txt @@ -0,0 +1,24 @@ +set(TOOL_NAME dsviewer) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) + +find_package(PkgConfig) +pkg_search_module(EDITLINE REQUIRED libeditline) +include_directories(${EDITLINE_INCLUDE_DIRS}) +link_directories(${EDITLINE_LIBRARY_DIRS}) + +aux_source_directory(. TOOL_SRC) +add_executable(${TOOL_NAME} ${TOOL_SRC}) + +set_target_properties(${TOOL_NAME} PROPERTIES INSTALL_RPATH /usr/local/lib) +set_target_properties(${TOOL_NAME} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON) + +target_link_libraries(${TOOL_NAME} PRIVATE gctl) +target_link_libraries(${TOOL_NAME} PRIVATE ${EDITLINE_LIBRARIES}) + +install(TARGETS ${TOOL_NAME} RUNTIME DESTINATION sbin) + +file(GLOB HELP_DOC *.md) +install(FILES ${HELP_DOC} DESTINATION sbin/share) diff --git a/tool/dsviewer/dsviewer.cpp b/tool/dsviewer/dsviewer.cpp new file mode 100644 index 0000000..899f43a --- /dev/null +++ b/tool/dsviewer/dsviewer.cpp @@ -0,0 +1,345 @@ +/******************************************************** + * ██████╗ ██████╗████████╗██╗ + * ██╔════╝ ██╔════╝╚══██╔══╝██║ + * ██║ ███╗██║ ██║ ██║ + * ██║ ██║██║ ██║ ██║ + * ╚██████╔╝╚██████╗ ██║ ███████╗ + * ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ + * 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::text_content 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 **gridmanager_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 = "gridmanager"; + + /* Tell the completer that we want a crack first. */ + rl_attempted_completion_function = gridmanager_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"; + 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) +{ + if (cmd_units.size() == 1) // cmd_units[0] == info + { + tc.info(); + return; + } + return; +} + +void load_file(const std::vector &cmd_units) +{ + // load [nohead|hashead] [] [tag_sym] [att_sym] [head_num] + 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]; + } + + text_head_type_e ht = NoColumnName; + if (copy_str[0] == "nohead") ht = NoColumnName; + else if (copy_str[0] == "hashead") ht = HasColumnName; + + if (copy_str[1] != "null") tc.set_delimeter(copy_str[1][0]); + if (copy_str[2] != "null") tc.set_tag_symbol(copy_str[2][0]); + if (copy_str[3] != "null") tc.set_annotation_symbol(copy_str[3][0]); + + int hnum = 0; + if (copy_str[4] != "null") hnum = atoi(copy_str[4].c_str()); + if (hnum != 0) tc.set_head_number(hnum); + + 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 [] [tag_sym] [att_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.set_delimeter(copy_str[0][0]); + if (copy_str[1] != "null") tc.set_tag_symbol(copy_str[1][0]); + if (copy_str[2] != "null") tc.set_annotation_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(cmd_units[i], data); + std::clog << "column: " << cmd_units[i] + << " | " << data.min() << "/" << data.mean() << "/" << data.max() + << " | STD: " << data.std() << "\n"; + } + 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 << "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; +} \ No newline at end of file diff --git a/tool/dsviewer/dsviewer.h b/tool/dsviewer/dsviewer.h new file mode 100644 index 0000000..495e933 --- /dev/null +++ b/tool/dsviewer/dsviewer.h @@ -0,0 +1,62 @@ +/******************************************************** + * ██████╗ ██████╗████████╗██╗ + * ██╔════╝ ██╔════╝╚══██╔══╝██║ + * ██║ ███╗██║ ██║ ██║ + * ██║ ██║██║ ██║ ██║ + * ╚██████╔╝╚██████╗ ██║ ███████╗ + * ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ + * 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. + ******************************************************/ + +#ifndef GCTL_DSVIEWER_H +#define GCTL_DSVIEWER_H + +#include "../../lib/gctl_config.h" +#include "../../lib/io.h" + +using namespace gctl; + +// Function pointer for commands +typedef void (*cmd_func_ptr)(const std::vector &cmd_units); + +struct cmd_pair +{ + std::string name; + cmd_func_ptr func_p; + std::string brief; +}; + +void quit(const std::vector &cmd_units); +void info(const std::vector &cmd_units); +void load_file(const std::vector &cmd_units); +void save_file(const std::vector &cmd_units); +void statistic(const std::vector &cmd_units); + +#define CMD_NUM 6 +const cmd_pair commands[CMD_NUM] = { + {"quit", quit, "Quit the program."}, + {"info", info, "Show the table information."}, + {"open", load_file, "Open a dsv/csv file."}, + {"save", save_file, "Save the table to a file."}, + {"stats", statistic, "Calculate statistics of the selected columns."}, + {"null", nullptr, "null"} +}; + +#endif // GCTL_DSVIEWER_H \ No newline at end of file diff --git a/tool/dsviewer/dsviewer.md b/tool/dsviewer/dsviewer.md new file mode 100644 index 0000000..c43636d --- /dev/null +++ b/tool/dsviewer/dsviewer.md @@ -0,0 +1,14 @@ +#### quit +Does what it says. + +#### info +Show the table information. + +#### open \ [hashead|nohead] [\] [\] [\] +Open a dsv/csv file + +#### save +Save table to a dsv/csv file + +#### stats \ \... +Show statistics of a data column or columns. \ No newline at end of file