26 #include "panda_getopt_long.h"
38 #ifdef IOCTL_TERMINAL_WIDTH
41 #include <sys/ioctl.h>
43 #include <sys/ioctl.h>
45 #endif // IOCTL_TERMINAL_WIDTH
53 bool ProgramBase::SortOptionsByIndex::
54 operator () (
const Option *a,
const Option *b)
const {
55 if (a->_index_group != b->_index_group) {
56 return a->_index_group < b->_index_group;
58 return a->_sequence < b->_sequence;
64 static void flush_nout() {
69 (
"default-terminal-width", 72,
70 PRC_DESC(
"Specify the column at which to wrap output lines "
71 "from pandatool-based programs, if it cannot be determined "
75 (
"use-terminal-width",
true,
76 PRC_DESC(
"True to try to determine the terminal width automatically from "
77 "the operating system, if supported; false to use the width "
78 "specified by default-terminal-width even if the operating system "
79 "appears to report a valid width."));
85 ProgramBase(
const string &name) : _name(name) {
97 _path_replace->_path_store = PS_absolute;
98 _got_path_store =
false;
99 _got_path_directory =
false;
102 _sorted_options =
false;
103 _last_newline =
false;
104 _got_terminal_width =
false;
105 _got_option_indent =
false;
107 add_option(
"h",
"", 100,
108 "Display this help page.",
109 &ProgramBase::handle_help_option,
nullptr, (
void *)
this);
130 nout << _description <<
"\n";
138 nout <<
"\rUsage:\n";
139 Runlines::const_iterator ri;
142 for (ri = _runlines.begin(); ri != _runlines.end(); ++ri) {
154 if (!_got_option_indent) {
155 get_terminal_width();
156 _option_indent = min(15, (
int)(_terminal_width * 0.25));
157 _got_option_indent =
true;
160 nout <<
"Options:\n";
161 OptionsByIndex::const_iterator oi;
162 for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) {
163 const Option &opt = *(*oi);
164 string prefix =
" -" + opt._option +
" " + opt._parm_name;
165 show_text(prefix, _option_indent, opt._description +
"\r");
174 show_text(
const string &prefix,
int indent_width,
string text) {
175 get_terminal_width();
180 format_text(cerr, _last_newline,
181 prefix, indent_width, text, _terminal_width);
191 out <<
".\\\" Automatically generated by " << prog <<
" -write-man\n";
196 string::const_iterator si;
197 for (si = _name.begin(); si != _name.end(); ++si) {
198 out << (char)toupper(*si);
204 time_t current_time = time(
nullptr);
206 if (current_time != (time_t) -1) {
207 tm *today = localtime(¤t_time);
208 if (today ==
nullptr || 0 == strftime(date_str, 256,
"%d %B %Y", today)) {
213 out <<
" 1 \"" << date_str <<
"\" \""
217 if (_brief.empty()) {
218 out << _name <<
"\n";
220 out << _name <<
" \\- " << _brief <<
"\n";
223 out <<
".SH SYNOPSIS\n";
224 Runlines::const_iterator ri = _runlines.begin();
225 if (ri != _runlines.end()) {
226 out <<
"\\fB" << prog <<
"\\fR " << *ri <<
"\n";
230 for (; ri != _runlines.end(); ++ri) {
232 out <<
"\\fB" << prog <<
"\\fR " << *ri <<
"\n";
235 out <<
".SH DESCRIPTION\n";
236 string::const_iterator di;
238 for (di = _description.begin(); di != _description.end(); ++di) {
242 }
else if (prev ==
'\n' && (*di) ==
'\n') {
252 out <<
".SH OPTIONS\n";
254 OptionsByIndex::const_iterator oi;
255 for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) {
256 const Option &opt = *(*oi);
259 if (opt._parm_name.empty()) {
260 out <<
".B \\-" << opt._option <<
"\n";
262 out <<
".BI \"\\-" << opt._option <<
" \" \"" << opt._parm_name <<
"\"\n";
264 out << opt._description <<
"\n";
285 for (i = 1; i < argc; i++) {
286 _program_args.push_back(argv[i]);
295 if (argc > 1 && strcmp(argv[1],
"-write-man") == 0) {
299 }
else if (argc == 3) {
300 if (strlen(argv[2]) == 1 && argv[2][0] ==
'-') {
304 pofstream man_out(argv[2], std::ios::out | std::ios::trunc);
306 cerr <<
"Failed to open output file " << argv[2] <<
"!\n";
312 cerr <<
"Invalid number of options for -write-man!\n";
321 string short_options;
329 OptionsByName::const_iterator oi;
330 int next_index = 256;
337 for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) {
338 const Option &opt = (*oi).second;
341 if (opt._option.length() == 1) {
344 index = (int)opt._option[0];
346 short_options += opt._option;
347 if (!opt._parm_name.empty()) {
349 short_options +=
':';
353 index = ++next_index;
358 gopt.name = (
char *)opt._option.c_str();
359 gopt.has_arg = (opt._parm_name.empty()) ?
360 no_argument : required_argument;
367 long_options.push_back(gopt);
369 options[index] = &opt;
375 memset(&gopt, 0,
sizeof(gopt));
376 long_options.push_back(gopt);
384 const struct option *long_opts = &long_options[0];
387 getopt_long_only(argc, argv, short_options.c_str(), long_opts,
nullptr);
388 while (flag != EOF) {
390 if (optarg !=
nullptr) {
403 remaining_args.push_back(arg);
409 Options::const_iterator ii;
410 ii = options.find(flag);
411 if (ii == options.end()) {
412 nout <<
"Internal error! Invalid option index returned.\n";
416 const Option &opt = *(*ii).second;
418 if (opt._option_function != (OptionDispatchFunction)
nullptr) {
419 okflag = (*opt._option_function)(opt._option, arg, opt._option_data);
421 if (opt._option_method != (OptionDispatchMethod)
nullptr) {
422 okflag = (*opt._option_method)(
this, opt._option, arg, opt._option_data);
424 if (opt._bool_var !=
nullptr) {
425 (*opt._bool_var) =
true;
436 getopt_long_only(argc, argv, short_options.c_str(), long_opts,
nullptr);
439 if (!handle_args(remaining_args)) {
444 if (!post_command_line()) {
459 Args::const_iterator ai;
460 for (ai = _program_args.begin(); ai != _program_args.end(); ++ai) {
461 const string &arg = (*ai);
465 string::const_iterator si;
466 for (si = arg.begin(); legal && si != arg.end(); ++si) {
488 command +=
" " + arg;
490 command +=
" '" + arg +
"'";
506 nout <<
"Unexpected arguments on command line:\n";
507 Args::const_iterator ai;
508 for (ai = args.begin(); ai != args.end(); ++ai) {
509 nout << (*ai) <<
" ";
525 post_command_line() {
536 set_program_brief(
const string &brief) {
546 set_program_description(
const string &description) {
547 _description = description;
570 add_runline(
const string &runline) {
571 _runlines.push_back(runline);
581 _options_by_name.clear();
606 add_option(
const string &option,
const string &parm_name,
607 int index_group,
const string &description,
608 OptionDispatchFunction option_function,
609 bool *bool_var,
void *option_data) {
611 opt._option = option;
612 opt._parm_name = parm_name;
613 opt._index_group = index_group;
614 opt._sequence = ++_next_sequence;
615 opt._description = description;
616 opt._option_function = option_function;
617 opt._option_method = (OptionDispatchMethod)
nullptr;
618 opt._bool_var = bool_var;
619 opt._option_data = option_data;
621 _options_by_name[option] = opt;
622 _sorted_options =
false;
624 if (bool_var !=
nullptr) {
642 add_option(
const string &option,
const string &parm_name,
643 int index_group,
const string &description,
644 OptionDispatchMethod option_method,
645 bool *bool_var,
void *option_data) {
647 opt._option = option;
648 opt._parm_name = parm_name;
649 opt._index_group = index_group;
650 opt._sequence = ++_next_sequence;
651 opt._description = description;
652 opt._option_function = (OptionDispatchFunction)
nullptr;
653 opt._option_method = option_method;
654 opt._bool_var = bool_var;
655 opt._option_data = option_data;
657 _options_by_name[option] = opt;
658 _sorted_options =
false;
660 if (bool_var !=
nullptr) {
670 redescribe_option(
const string &option,
const string &description) {
671 OptionsByName::iterator oi = _options_by_name.find(option);
672 if (oi == _options_by_name.end()) {
675 (*oi).second._description = description;
684 remove_option(
const string &option) {
685 OptionsByName::iterator oi = _options_by_name.find(option);
686 if (oi == _options_by_name.end()) {
689 _options_by_name.erase(oi);
690 _sorted_options =
false;
700 add_path_replace_options() {
702 (
"pr",
"path_replace", 40,
703 "Sometimes references to other files (textures, external references) "
704 "are stored with a full path that is appropriate for some other system, "
705 "but does not exist here. This option may be used to specify how "
706 "those invalid paths map to correct paths. Generally, this is of "
707 "the form 'orig_prefix=replacement_prefix', which indicates a "
708 "particular initial sequence of characters that should be replaced "
709 "with a new sequence; e.g. '/c/home/models=/beta/fish'. "
710 "If the replacement prefix does not begin with a slash, the file "
711 "will then be searched for along the search path specified by -pp. "
712 "You may use standard filename matching characters ('*', '?', etc.) in "
713 "the original prefix, and '**' as a component by itself stands for "
714 "any number of components.\n\n"
716 "This option may be repeated as necessary; each file will be tried "
717 "against each specified method, in the order in which they appear in "
718 "the command line, until the file is found. If the file is not found, "
719 "the last matching prefix is used anyway.",
720 &ProgramBase::dispatch_path_replace,
nullptr, _path_replace.p());
723 (
"pp",
"dirname", 40,
724 "Adds the indicated directory name to the list of directories to "
725 "search for filenames referenced by the source file. This is used "
726 "only for relative paths, or for paths that are made relative by a "
727 "-pr replacement string that doesn't begin with a leading slash. "
728 "The model-path is always implicitly searched anyway.",
729 &ProgramBase::dispatch_search_path,
nullptr, &(_path_replace->_path));
738 add_path_store_options() {
741 _path_replace->_path_store = PS_relative;
744 (
"ps",
"path_store", 40,
745 "Specifies the way an externally referenced file is to be "
746 "represented in the resulting output file. This "
747 "assumes the named filename actually exists; "
748 "see -pr to indicate how to deal with external "
749 "references that have bad pathnames. "
750 "This option will not help you to find a missing file, but simply "
751 "controls how filenames are represented in the output.\n\n"
753 "The option may be one of: rel, abs, rel_abs, strip, or keep. If "
754 "either rel or rel_abs is specified, the files are made relative to "
755 "the directory specified by -pd. The default is rel.",
756 &ProgramBase::dispatch_path_store, &_got_path_store,
757 &(_path_replace->_path_store));
760 (
"pd",
"path_directory", 40,
761 "Specifies the name of a directory to make paths relative to, if "
762 "'-ps rel' or '-ps rel_abs' is specified. If this is omitted, the "
763 "directory name is taken from the name of the output file.",
764 &ProgramBase::dispatch_filename, &_got_path_directory,
765 &(_path_replace->_path_directory));
768 (
"pc",
"target_directory", 40,
769 "Copies textures and other dependent files into the indicated "
770 "directory. If a relative pathname is specified, it is relative "
771 "to the directory specified with -pd, above.",
772 &ProgramBase::dispatch_filename, &(_path_replace->_copy_files),
773 &(_path_replace->_copy_into_directory));
784 dispatch_none(
const string &,
const string &,
void *) {
797 dispatch_true(
const string &,
const string &,
void *var) {
798 bool *bp = (
bool *)var;
812 dispatch_false(
const string &,
const string &,
void *var) {
813 bool *bp = (
bool *)var;
825 dispatch_count(
const string &,
const string &,
void *var) {
826 int *ip = (
int *)var;
837 dispatch_int(
const string &opt,
const string &arg,
void *var) {
838 int *ip = (
int *)var;
841 nout <<
"Invalid integer parameter for -" << opt <<
": "
854 dispatch_int_pair(
const string &opt,
const string &arg,
void *var) {
855 int *ip = (
int *)var;
861 if (words.size() == 2) {
869 <<
" requires a pair of integers separated by a comma.\n";
881 dispatch_int_quad(
const string &opt,
const string &arg,
void *var) {
882 int *ip = (
int *)var;
888 if (words.size() == 4) {
898 <<
" requires a quad of integers separated by a comma.\n";
910 dispatch_double(
const string &opt,
const string &arg,
void *var) {
911 double *ip = (
double *)var;
914 nout <<
"Invalid numeric parameter for -" << opt <<
": "
927 dispatch_double_pair(
const string &opt,
const string &arg,
void *var) {
928 double *ip = (
double *)var;
934 if (words.size() == 2) {
942 <<
" requires a pair of numbers separated by a comma.\n";
954 dispatch_double_triple(
const string &opt,
const string &arg,
void *var) {
955 double *ip = (
double *)var;
961 if (words.size() == 3) {
970 <<
" requires three numbers separated by commas.\n";
982 dispatch_double_quad(
const string &opt,
const string &arg,
void *var) {
983 double *ip = (
double *)var;
989 if (words.size() == 4) {
999 <<
" requires four numbers separated by commas.\n";
1012 dispatch_color(
const string &opt,
const string &arg,
void *var) {
1013 PN_stdfloat *ip = (PN_stdfloat *)var;
1015 vector_string words;
1018 bool okflag =
false;
1019 switch (words.size()) {
1022 string_to_stdfloat(words[0], ip[0]) &&
1023 string_to_stdfloat(words[1], ip[1]) &&
1024 string_to_stdfloat(words[2], ip[2]) &&
1025 string_to_stdfloat(words[3], ip[3]);
1030 string_to_stdfloat(words[0], ip[0]) &&
1031 string_to_stdfloat(words[1], ip[1]) &&
1032 string_to_stdfloat(words[2], ip[2]);
1038 string_to_stdfloat(words[0], ip[0]) &&
1039 string_to_stdfloat(words[1], ip[3]);
1046 string_to_stdfloat(words[0], ip[0]);
1055 <<
" requires one through four numbers separated by commas.\n";
1067 dispatch_string(
const string &,
const string &arg,
void *var) {
1068 string *ip = (
string *)var;
1083 dispatch_vector_string(
const string &,
const string &arg,
void *var) {
1084 vector_string *ip = (vector_string *)var;
1085 (*ip).push_back(arg);
1098 dispatch_vector_string_comma(
const string &,
const string &arg,
void *var) {
1099 vector_string *ip = (vector_string *)var;
1101 vector_string words;
1104 vector_string::const_iterator wi;
1105 for (wi = words.begin(); wi != words.end(); ++wi) {
1106 (*ip).push_back(*wi);
1118 dispatch_filename(
const string &opt,
const string &arg,
void *var) {
1120 nout <<
"-" << opt <<
" requires a filename parameter.\n";
1138 dispatch_search_path(
const string &opt,
const string &arg,
void *var) {
1140 nout <<
"-" << opt <<
" requires a search path parameter.\n";
1156 dispatch_coordinate_system(
const string &opt,
const string &arg,
void *var) {
1157 CoordinateSystem *ip = (CoordinateSystem *)var;
1158 (*ip) = parse_coordinate_system_string(arg);
1160 if ((*ip) == CS_invalid) {
1161 nout <<
"Invalid coordinate system for -" << opt <<
": " << arg <<
"\n"
1162 <<
"Valid coordinate system strings are any of 'y-up', 'z-up', "
1163 "'y-up-left', or 'z-up-left'.\n";
1176 dispatch_units(
const string &opt,
const string &arg,
void *var) {
1180 if ((*ip) == DU_invalid) {
1181 nout <<
"Invalid units for -" << opt <<
": " << arg <<
"\n"
1182 <<
"Valid units are mm, cm, m, km, yd, ft, in, nmi, and mi.\n";
1195 dispatch_image_type(
const string &opt,
const string &arg,
void *var) {
1202 if ((*ip) ==
nullptr) {
1203 nout <<
"Invalid image type for -" << opt <<
": " << arg <<
"\n"
1204 <<
"The following image types are known:\n";
1205 reg->
write(nout, 2);
1218 dispatch_path_replace(
const string &opt,
const string &arg,
void *var) {
1220 size_t equals = arg.find(
'=');
1221 if (equals == string::npos) {
1222 nout <<
"Invalid path replacement string for -" << opt <<
": " << arg <<
"\n"
1223 <<
"String should be of the form 'old-prefix=new-prefix'.\n";
1226 ip->
add_pattern(arg.substr(0, equals), arg.substr(equals + 1));
1237 dispatch_path_store(
const string &opt,
const string &arg,
void *var) {
1241 if ((*ip) == PS_invalid) {
1242 nout <<
"Invalid path store for -" << opt <<
": " << arg <<
"\n"
1243 <<
"Valid path store strings are any of 'rel', 'abs', "
1244 <<
"'rel_abs', 'strip', or 'keep'.\n";
1256 handle_help_option(
const string &,
const string &,
void *data) {
1285 format_text(std::ostream &out,
bool &last_newline,
1286 const string &prefix,
int indent_width,
1287 const string &text,
int line_width) {
1288 indent_width = min(indent_width, line_width - 20);
1289 int indent_amount = indent_width;
1290 bool initial_break =
false;
1292 if (!prefix.empty()) {
1294 indent_amount = indent_width - prefix.length();
1295 if ((
int)prefix.length() + 1 > indent_width) {
1297 initial_break =
true;
1298 indent_amount = indent_width;
1305 while (p < text.length() && isspace(text[p])) {
1306 if (text[p] ==
'\r' ||
1307 (p > 0 && text[p] ==
'\n' && text[p - 1] ==
'\n') ||
1308 (p == 0 && text[p] ==
'\n' && last_newline)) {
1309 if (!initial_break) {
1312 initial_break =
true;
1314 indent_amount = indent_width;
1316 }
else if (text[p] ==
'\n') {
1318 indent_amount = indent_width;
1320 }
else if (text[p] ==
' ') {
1327 last_newline = (!text.empty() && text[text.length() - 1] ==
'\n');
1329 while (p < text.length()) {
1332 size_t par = text.find_first_of(
"\n\r", p);
1333 bool is_paragraph_break =
false;
1334 if (par == string::npos) {
1335 par = text.length();
1343 indent(out, indent_amount);
1345 size_t eol = p + (line_width - indent_width);
1354 size_t min_eol = max((
int)p, (
int)eol - 25);
1356 while (q > min_eol && !isspace(text[q])) {
1360 while (q > min_eol && isspace(text[q])) {
1374 out << text.substr(p, eol - p) <<
"\n";
1378 while (p < text.length() && isspace(text[p])) {
1379 if (text[p] ==
'\r' ||
1380 (p > 0 && text[p] ==
'\n' && text[p - 1] ==
'\n')) {
1381 is_paragraph_break =
true;
1386 if (eol == par && is_paragraph_break) {
1389 if (p >= text.length()) {
1392 last_newline =
false;
1396 indent_amount = indent_width;
1407 if (!_sorted_options) {
1408 _options_by_index.clear();
1410 OptionsByName::const_iterator oi;
1411 for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) {
1412 _options_by_index.push_back(&(*oi).second);
1415 sort(_options_by_index.begin(), _options_by_index.end(),
1416 SortOptionsByIndex());
1417 _sorted_options =
true;
1425 get_terminal_width() {
1426 if (!_got_terminal_width) {
1427 _got_terminal_width =
true;
1428 _got_option_indent =
false;
1430 #ifdef IOCTL_TERMINAL_WIDTH
1431 if (use_terminal_width) {
1432 struct winsize size;
1433 int result = ioctl(STDIN_FILENO, TIOCGWINSZ, (
char *)&size);
1434 if (result < 0 || size.ws_col < 10) {
1437 _terminal_width = default_terminal_width;
1441 _terminal_width = size.ws_col - min(8, (
int)(size.ws_col * 0.1));
1445 #endif // IOCTL_TERMINAL_WIDTH
1447 _terminal_width = default_terminal_width;