gctl/lib/utility/getopt_help.cpp

799 lines
20 KiB
C++
Raw Permalink Normal View History

2024-09-10 15:45:07 +08:00
/********************************************************
*
*
*
*
*
*
* 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 "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<char> tar_val, std::initializer_list<std::string*> 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<char>::iterator icl;
std::initializer_list<std::string*>::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;
}