/******************************************************** * ██████╗ ██████╗████████╗██╗ * ██╔════╝ ██╔════╝╚══██╔══╝██║ * ██║ ███╗██║ ██║ ██║ * ██║ ██║██║ ██║ ██║ * ╚██████╔╝╚██████╗ ██║ ███████╗ * ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ * 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 "getopt_help.h" /** * @brief Display a string massage using the standard outputs. * * @param[in] f_space The front space before the printing. * @param[in] b_space The end space before the printing. * @param[in] hang_space The hang space starting from the second line. * @param[in] wth window size of the terminal * @param[in] msg The message * @param[in] stdout Output stream. The default is the standard error output */ void display_line(int f_space, int b_space, int hang_space, int wth, std::string msg, std::ostream& sout=std::clog) { int line_length = f_space + b_space; std::string segment; std::stringstream ss_message; ss_message.clear(); ss_message.str(msg); while (ss_message >> segment) { if ((line_length+segment.length()) <= wth) { if (line_length == f_space+b_space) { for (int i = 0; i < f_space; i++) sout << " "; sout << segment << " "; line_length += segment.length()+1; } else { sout << segment << " "; line_length += segment.length()+1; } } else { sout << std::endl; for (int i = 0; i < f_space+hang_space; i++) sout << " "; sout << segment << " "; line_length = (segment.length()+1+hang_space+f_space+b_space); } } sout << std::endl; return; } /** * @brief display formatted help information in terminal * * This function will display formated help information in the terminal using * the standard error output. We choose the error output to avoid unexpected * redirection and make sure the information will appear in the terminal. * * @param[in] proname The program's name. * @param[in] brief The brief information show right after the program's name. * @param[in] exp_longopts The pointer of expanded longopts */ void gctl::getopt_long_help(const option *longopts, const option_info *expd_longopts, const char* proname, const char* brief, std::ostream& sout, std::string extra_usage) { // Default layout. One can only change this by recompile the library. int front_space = 0, back_space = 10; int line_length; std::string m1, m2, m3; std::string segment; std::stringstream ss_message; int width; //获取终端窗口的行列数 #ifdef _WINDOWS CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); width = csbi.srWindow.Right - csbi.srWindow.Left; #else struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); width = w.ws_col; #endif // We firstly display the program's name which is assumed to be the first argument. m1 = proname; m1 += " - "; m2 = brief; m1 += m2; display_line(front_space, back_space, 4, width, m1, sout); sout << std::endl; // display usage int loop_index = 0; m2 = proname; m1 = "Usage: " + m2; while(1) { if (longopts[loop_index].name != 0 && expd_longopts[loop_index].format != 0) { if (longopts[loop_index].val >= 65 && longopts[loop_index].val <= 122) m2 = (char)longopts[loop_index].val; else { m2 = "-"; m2 += longopts[loop_index].name; } m3 = expd_longopts[loop_index].format; if (!expd_longopts[loop_index].manda) m1 += " [-" + m2 + m3 +"]"; else { #ifdef _WINDOWS m1 += " -" + m2 + m3; #else m1 += GCTL_BOLD; m1 += " -" + m2 + m3; m1 += GCTL_RESET; #endif } loop_index++; } else if (longopts[loop_index].name != 0) { if (longopts[loop_index].val >= 65 && longopts[loop_index].val <= 122) m2 = (char)longopts[loop_index].val; else { m2 = "-"; m2 += longopts[loop_index].name; } if (!expd_longopts[loop_index].manda) m1 += " [-" + m2 + "]"; else { #ifdef _WINDOWS m1 += " -" + m2; #else m1 += GCTL_BOLD; m1 += " -" + m2; m1 += GCTL_RESET; #endif } loop_index++; } else break; } m1 += extra_usage; display_line(front_space, back_space, 7, width, m1, sout); sout << std::endl; //找到最长的选项作为排版的依据 int temp_len, remain_len, max_opt_length = -1; loop_index = 0; while(1) { if (longopts[loop_index].name != 0) { temp_len = strlen(longopts[loop_index].name); max_opt_length = std::max(max_opt_length, temp_len + 1); loop_index++; } else break; } m1 = "Options:"; display_line(front_space, back_space, 0, width, m1, sout); // display usage loop_index = 0; while(1) { if (longopts[loop_index].name != 0) { for (int j = 0; j < front_space+2; j++) sout << " "; temp_len = strlen(longopts[loop_index].name) + 1; remain_len = max_opt_length - temp_len; if (longopts[loop_index].val >= 65 && longopts[loop_index].val <= 122) { if (expd_longopts[loop_index].manda) { #ifdef _WINDOWS sout << "-" << (char)longopts[loop_index].val << " --" << longopts[loop_index].name << " "; #else sout << GCTL_BOLD << "-" << (char)longopts[loop_index].val << " --" << longopts[loop_index].name << GCTL_RESET << " "; #endif } else sout << "-" << (char)longopts[loop_index].val << " --" << longopts[loop_index].name << " "; } else { if (expd_longopts[loop_index].manda) { #ifdef _WINDOWS sout << " --" << longopts[loop_index].name << " "; #else sout << GCTL_BOLD << " --" << longopts[loop_index].name << GCTL_RESET << " "; #endif } else sout << " --" << longopts[loop_index].name << " "; } for (int j = 0; j < remain_len; j++) sout << " "; ss_message.clear(); ss_message.str(expd_longopts[loop_index].info); line_length = front_space + back_space + max_opt_length + 14; while(ss_message >> segment) { if ((line_length+segment.length()+1) <= width) { sout << segment << " "; line_length += (segment.length()+1); } else { sout << std::endl; for (int j = 0; j < front_space+max_opt_length+13; j++) sout << " "; sout << segment << " "; line_length = (segment.length()+13+max_opt_length+front_space+back_space); } } sout << std::endl; loop_index++; } else break; } sout << std::endl; m1 = "This page is generated by the GCTL library automatically."; display_line(front_space, back_space, 4, width, m1, sout); return; } /** * @brief Display the help information of one option indicated by its id. * * @param[in] val The option's value * @param exp_longopts The expanded longopts */ void gctl::getopt_long_option_info(int val, const option *longopts, const option_info *expd_longopts, std::ostream& sout) { int width; //获取终端窗口的行列数 #ifdef _WINDOWS CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); width = csbi.srWindow.Right - csbi.srWindow.Left; #else struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); width = w.ws_col; #endif std::string m1, m2; int loop_index = 0; while (1) { if (longopts[loop_index].name != 0) { if (longopts[loop_index].val == val) { m1 = "Option: -"; m2 = (char)longopts[loop_index].val; m1 += m2; if (expd_longopts[loop_index].format != 0) { m2 = expd_longopts[loop_index].format; m1 += m2 + " "; } else m1 += " "; m2 = expd_longopts[loop_index].info; m1 += m2; display_line(0, 10, 4, width, m1, sout); } loop_index++; } else break; } return; } /** * 以下为类的方法定义 */ gctl::flags_parser::flags_parser() { configured_ = false; failed_mandatory_ = false; pro_name_ = pro_info_ = ""; nofound_return_ = "NULL"; noarg_return_ = "NO_ARG"; } gctl::flags_parser::~flags_parser(){} void gctl::flags_parser::add_opt(int s_name, const char* f_name, int has_arg, int *flag, const char* info, const char* format, bool manda) { option p = {f_name, has_arg, flag, s_name}; option_info f = {info, format, manda}; rec_opts_.push_back(p); rec_info_.push_back(f); configured_ = false; return; } void gctl::flags_parser::configure(int argc, char **argv) { if (rec_opts_.empty()) throw length_error("No option registered yet. From flags_parser::configure(...)"); if (pro_name_ == "" || pro_info_ == "") throw invalid_argument("The program's name or description is not set. From flags_parser::configure(...)"); // 拷贝命令行参数个数与地址 argc_ = argc; argv_ = &argv[0]; // 先在记录的option与option_info后添加一个全为0的对象 option p = {0, 0, 0, 0}; option_info f = {0, 0, 0}; rec_opts_.push_back(p); rec_info_.push_back(f); // 解析参数符号串 arg_format_ = ""; // 最后一个参数是空的 不要 for (int i = 0; i < rec_opts_.size()-1; i++) { arg_format_ += (char) rec_opts_[i].val; if (rec_opts_[i].has_arg == 1) arg_format_ += ":"; else if (rec_opts_[i].has_arg == 2) arg_format_ += "::"; } configured_ = true; return; } bool gctl::flags_parser::pass_mandatory() { return !failed_mandatory_; } int gctl::flags_parser::set_opt(const int s_name) { std::string err_str; if (!configured_) throw runtime_error("flags_parser is not configured. From flags_parser::set_opt(...)"); int is_set = 0; // 下面这段检查是针对程序编写中的可能出现的错误调用 bool is_manda = false; bool registered = false; for (int i = 0; i < rec_opts_.size()-1; i++) { if (s_name == rec_opts_[i].val) { registered = true; if (rec_info_[i].manda) is_manda = true; break; } } if (!registered) { err_str = "Option -"; err_str += (char) s_name; err_str += " is not registered. Use flags_parser::add_opt() to register before use. From flags_parser::set_opt(...)"; throw domain_error(err_str);; } optind = 0; // 重置已分析参数个数 再从头分析一遍 while (1) { int optIndex = 0; int curr = getopt_long(argc_, argv_, arg_format_.c_str(), rec_opts_.data(), &optIndex); if (curr == -1 && is_manda) { err_str = "Mandatory option -"; err_str += (char) s_name; err_str += " is not found and returned -1. From flags_parser::set_opt()."; GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0); show_option_info(s_name); is_set = -1; failed_mandatory_ = true; break; } else if (curr == -1) break; if (curr == s_name) { is_set = 1; break; } else if (curr == '?') { err_str = "Invalid option found and returned 0. From flags_parser::set_opt()."; GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0); break; } } return is_set; } int gctl::flags_parser::set_opt(const char* f_name) { std::string err_str; if (!configured_) { err_str = "flags_parser is not configured. From flags_parser::set_opt(...)"; throw err_str; } // 下面这段检查是针对程序编写中的可能出现的错误调用 int s_name = '?'; for (int i = 0; i < rec_opts_.size()-1; i++) { if (!strcmp(f_name, rec_opts_[i].name)) { s_name = rec_opts_[i].val; break; } } if (s_name == '?') { err_str = "Option --"; err_str += f_name; err_str += " is not registered. Use flags_parser::add_opt() to register before use. From flags_parser::set_opt(...)"; throw err_str; } return set_opt(s_name); } std::string gctl::flags_parser::get_arg(const int s_name) { std::string err_str; if (!configured_) { err_str = "flags_parser is not configured. From flags_parser::get_arg(...)"; throw err_str; } std::string out_opt = nofound_return_; // 下面这段检查是针对程序编写中的可能出现的错误调用 bool registered = false; bool no_arg = true; bool is_manda = false; for (int i = 0; i < rec_opts_.size()-1; i++) { if (s_name == rec_opts_[i].val) { registered = true; if (rec_opts_[i].has_arg != 0) no_arg = false; if (rec_info_[i].manda) is_manda = true; break; } } if (!registered) { err_str = "Option -"; err_str += (char) s_name; err_str += " is not registered. Use flags_parser::add_opt() to register before use. From flags_parser::get_arg(...)"; throw err_str; } if (no_arg) { err_str = "Option -"; err_str += (char) s_name; err_str += " has no argument. From flags_parser::get_arg(...)"; throw err_str; } // 下面是检查命令行参数拾取中的可能出现的错误输入 optind = 0; // 重置已分析参数个数 再从头分析一遍 while (1) { int optIndex = 0; int curr = getopt_long(argc_, argv_, arg_format_.c_str(), rec_opts_.data(), &optIndex); if (curr == -1 && is_manda) { err_str = "Mandatory option -"; err_str += (char) s_name; err_str += " is not found and returned NULL. From flags_parser::get_arg(...)"; GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0); show_option_info(s_name); failed_mandatory_ = true; break; } else if (curr == -1) break; if (curr == s_name) { /** * 注意 如果s_name对应的选项的参数是可选的(optional_argument) 则其在调用get_arg()时可能没有参数 * 此时optarg2将指向一个空指针 不能进行赋值操作 最终的函数返回值为NULL 即没有拾取到参数 */ if (optarg != NULL) out_opt = optarg; else out_opt = noarg_return_; break; } else if (curr == '?') { err_str = "Invalid option found and returned NULL. From flags_parser::get_arg(...)"; GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0); break; } } return out_opt; } std::string gctl::flags_parser::get_arg(const char* f_name) { std::string err_str; if (!configured_) { err_str = "flags_parser is not configured. From flags_parser::get_arg(...)"; throw err_str; } int s_name = '?'; for (int i = 0; i < rec_opts_.size()-1; i++) { if (!strcmp(f_name, rec_opts_[i].name)) { s_name = rec_opts_[i].val; break; } } if (s_name == '?') { err_str = "Option --"; err_str += f_name; err_str += " is not registered. From flags_parser::get_arg(...)"; throw err_str; } return get_arg(s_name); } void gctl::flags_parser::get_argv(std::initializer_list tar_val, std::initializer_list ret_str) { std::string err_str; if (!configured_) { err_str = "flags_parser is not configured. From flags_parser::get_argv(...)"; throw err_str; } if (tar_val.size() != ret_str.size()) { err_str = "target and result sizes don't match. From flags_parser::get_argv(...)"; throw err_str; } // 下面这段检查是针对程序编写中的可能出现的错误调用 bool registered, no_arg; std::initializer_list::iterator icl; std::initializer_list::iterator isl; char s_name; for (icl = tar_val.begin(); icl != tar_val.end(); ++icl) { s_name = *icl; registered = false; no_arg = true; for (int i = 0; i < rec_opts_.size()-1; i++) { if (s_name == rec_opts_[i].val) { registered = true; if (rec_opts_[i].has_arg != 0) no_arg = false; break; } } if (!registered) { err_str = "Option -"; err_str += (char) s_name; err_str += " is not registered. From flags_parser::get_argv(...)"; throw err_str; } if (no_arg) { err_str = "Option -"; err_str += (char) s_name; err_str += " has no argument. From flags_parser::get_argv(...)"; throw err_str; } } for (isl = ret_str.begin(); isl != ret_str.end(); ++isl) { (*isl)->assign(nofound_return_); } // 下面是检查命令行参数拾取中的可能出现的错误输入 bool is_manda, end_analysis = false; isl = ret_str.begin(); for (icl = tar_val.begin(); icl != tar_val.end(); ++icl) { s_name = *icl; is_manda = false; for (int i = 0; i < rec_opts_.size()-1; i++) { if (s_name == rec_opts_[i].val) { if (rec_info_[i].manda) is_manda = true; break; } } optind = 0; // 重置已分析参数个数 再从头分析一遍 while (1) { int optIndex = 0; int curr = getopt_long(argc_, argv_, arg_format_.c_str(), rec_opts_.data(), &optIndex); if (curr == -1 && is_manda) { err_str = "Mandatory option -"; err_str += (char) s_name; err_str += " is not found and returned NULL. From flags_parser::get_argv(...)"; GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0); show_option_info(s_name); failed_mandatory_ = true; break; } else if (curr == -1) break; if (curr == s_name) { /** * 注意 如果s_name对应的选项的参数是可选的(optional_argument) 则其在调用get_arg()时可能没有参数 * 此时optarg2将指向一个空指针 不能进行赋值操作 最终的函数返回值为NULL 即没有拾取到参数 */ if (optarg != NULL) (*isl)->assign(optarg); else (*isl)->assign(noarg_return_); break; } else if (curr == '?') { err_str = "Invalid option found and returned NULL. From flags_parser::get_argv(...)"; GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0); end_analysis = true; break; } } isl++; if (end_analysis) break; } return; } void gctl::flags_parser::show_help_page(std::ostream& sout, std::string extra_usage) { std::string err_str; if (!configured_) { err_str = "flags_parser is not configured. From flags_parser::show_help_page(...)"; throw err_str; } gctl::display_logo(sout); gctl::getopt_long_help(rec_opts_.data(), rec_info_.data(), pro_name_.c_str(), pro_info_.c_str(), sout, extra_usage); return; } void gctl::flags_parser::show_option_info(int s_name, std::ostream& sout) { std::string err_str; if (!configured_) { err_str = "flags_parser is not configured. From flags_parser::show_option_info(...)"; throw err_str; } gctl::getopt_long_option_info(s_name, rec_opts_.data(), rec_info_.data(), sout); return; } void gctl::flags_parser::show_option_info(const char* f_name, std::ostream& sout) { std::string err_str; if (!configured_) { err_str = "flags_parser is not configured. From flags_parser::show_option_info(...)"; throw err_str; } int s_name = '?'; for (int i = 0; i < rec_opts_.size()-1; i++) { if (!strcmp(f_name, rec_opts_[i].name)) { s_name = rec_opts_[i].val; break; } } if (s_name == '?') { err_str = "Option --"; err_str += f_name; err_str += " is not registered. From flags_parser::show_option_info(...)"; throw err_str; } gctl::getopt_long_option_info(s_name, rec_opts_.data(), rec_info_.data(), sout); return; } void gctl::flags_parser::set_proname(const char* proname) { pro_name_ = proname; return; } void gctl::flags_parser::set_proinfo(const char* proinfo) { pro_info_ = proinfo; return; } void gctl::flags_parser::set_nofound_return(std::string in_str) { nofound_return_ = in_str; return; } void gctl::flags_parser::set_noarg_return(std::string in_str) { noarg_return_ = in_str; return; } void gctl::flags_parser::show_arg_format(std::ostream& sout) { sout << arg_format_ << std::endl; return; }