15 #include "programBase.h" 16 #include "wordWrapStream.h" 18 #include "pnmFileTypeRegistry.h" 20 #include "dSearchPath.h" 21 #include "coordinateSystem.h" 23 #include "config_dconfig.h" 24 #include "string_utils.h" 25 #include "vector_string.h" 26 #include "configVariableInt.h" 27 #include "configVariableBool.h" 28 #include "panda_getopt_long.h" 29 #include "preprocess_argv.h" 30 #include "pandaSystem.h" 40 #ifdef IOCTL_TERMINAL_WIDTH 43 #include <sys/ioctl.h> 45 #include <sys/ioctl.h> 47 #endif // IOCTL_TERMINAL_WIDTH 49 bool ProgramBase::SortOptionsByIndex::
50 operator () (
const Option *a,
const Option *b)
const {
51 if (a->_index_group != b->_index_group) {
52 return a->_index_group < b->_index_group;
54 return a->_sequence < b->_sequence;
61 static void flush_nout() {
66 (
"default-terminal-width", 72,
67 PRC_DESC(
"Specify the column at which to wrap output lines " 68 "from pandatool-based programs, if it cannot be determined " 72 (
"use-terminal-width",
true,
73 PRC_DESC(
"True to try to determine the terminal width automatically from " 74 "the operating system, if supported; false to use the width " 75 "specified by default-terminal-width even if the operating system " 76 "appears to report a valid width."));
84 ProgramBase(
const string &name) : _name(name) {
96 _path_replace->_path_store = PS_absolute;
97 _got_path_store =
false;
98 _got_path_directory =
false;
101 _sorted_options =
false;
102 _last_newline =
false;
103 _got_terminal_width =
false;
104 _got_option_indent =
false;
106 add_option(
"h",
"", 100,
107 "Display this help page.",
108 &ProgramBase::handle_help_option, NULL, (
void *)
this);
133 nout << _description <<
"\n";
143 nout <<
"\rUsage:\n";
144 Runlines::const_iterator ri;
147 for (ri = _runlines.begin(); ri != _runlines.end(); ++ri) {
161 if (!_got_option_indent) {
162 get_terminal_width();
163 _option_indent = min(15, (
int)(_terminal_width * 0.25));
164 _got_option_indent =
true;
167 nout <<
"Options:\n";
168 OptionsByIndex::const_iterator oi;
169 for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) {
170 const Option &opt = *(*oi);
171 string prefix =
" -" + opt._option +
" " + opt._parm_name;
172 show_text(prefix, _option_indent, opt._description +
"\r");
183 show_text(
const string &prefix,
int indent_width,
string text) {
184 get_terminal_width();
189 format_text(cerr, _last_newline,
190 prefix, indent_width, text, _terminal_width);
203 out <<
".\\\" Automatically generated by " << prog <<
" -write-man\n";
208 string::const_iterator si;
209 for (si = _name.begin(); si != _name.end(); ++si) {
210 out << (char)toupper(*si);
216 time_t current_time = time(NULL);
218 if (current_time != (time_t) -1) {
219 tm *today = localtime(¤t_time);
220 if (today == NULL || 0 == strftime(date_str, 256,
"%d %B %Y", today)) {
225 out <<
" 1 \"" << date_str <<
"\" \"" 229 if (_brief.empty()) {
230 out << _name <<
"\n";
232 out << _name <<
" \\- " << _brief <<
"\n";
235 out <<
".SH SYNOPSIS\n";
236 Runlines::const_iterator ri = _runlines.begin();
237 if (ri != _runlines.end()) {
238 out <<
"\\fB" << prog <<
"\\fR " << *ri <<
"\n";
242 for (; ri != _runlines.end(); ++ri) {
244 out <<
"\\fB" << prog <<
"\\fR " << *ri <<
"\n";
247 out <<
".SH DESCRIPTION\n";
248 string::const_iterator di;
250 for (di = _description.begin(); di != _description.end(); ++di) {
254 }
else if (prev ==
'\n' && (*di) ==
'\n') {
264 out <<
".SH OPTIONS\n";
266 OptionsByIndex::const_iterator oi;
267 for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) {
268 const Option &opt = *(*oi);
271 if (opt._parm_name.empty()) {
272 out <<
".B \\-" << opt._option <<
"\n";
274 out <<
".BI \"\\-" << opt._option <<
" \" \"" << opt._parm_name <<
"\"\n";
276 out << opt._description <<
"\n";
291 preprocess_argv(argc, argv);
301 for (i = 1; i < argc; i++) {
302 _program_args.push_back(argv[i]);
311 if (argc > 1 && strcmp(argv[1],
"-write-man") == 0) {
315 }
else if (argc == 3) {
316 if (strlen(argv[2]) == 1 && argv[2][0] ==
'-') {
320 pofstream man_out(argv[2], ios::out | ios::trunc);
322 cerr <<
"Failed to open output file " << argv[2] <<
"!\n";
328 cerr <<
"Invalid number of options for -write-man!\n";
337 string short_options;
345 OptionsByName::const_iterator oi;
346 int next_index = 256;
353 for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) {
354 const Option &opt = (*oi).second;
357 if (opt._option.length() == 1) {
360 index = (int)opt._option[0];
362 short_options += opt._option;
363 if (!opt._parm_name.empty()) {
365 short_options +=
':';
370 index = ++next_index;
375 gopt.name = (
char *)opt._option.c_str();
376 gopt.has_arg = (opt._parm_name.empty()) ?
377 no_argument : required_argument;
378 gopt.flag = (
int *)NULL;
384 long_options.push_back(gopt);
386 options[index] = &opt;
392 memset(&gopt, 0,
sizeof(gopt));
393 long_options.push_back(gopt);
402 const struct option *long_opts = &long_options[0];
405 getopt_long_only(argc, argv, short_options.c_str(), long_opts, NULL);
406 while (flag != EOF) {
408 if (optarg != NULL) {
421 remaining_args.push_back(arg);
427 Options::const_iterator ii;
428 ii = options.find(flag);
429 if (ii == options.end()) {
430 nout <<
"Internal error! Invalid option index returned.\n";
434 const Option &opt = *(*ii).second;
436 if (opt._option_function != (OptionDispatchFunction)NULL) {
437 okflag = (*opt._option_function)(opt._option, arg, opt._option_data);
439 if (opt._option_method != (OptionDispatchMethod)NULL) {
440 okflag = (*opt._option_method)(
this, opt._option, arg, opt._option_data);
442 if (opt._bool_var != (
bool *)NULL) {
443 (*opt._bool_var) =
true;
454 getopt_long_only(argc, argv, short_options.c_str(), long_opts, NULL);
457 if (!handle_args(remaining_args)) {
462 if (!post_command_line()) {
480 Args::const_iterator ai;
481 for (ai = _program_args.begin(); ai != _program_args.end(); ++ai) {
482 const string &arg = (*ai);
486 string::const_iterator si;
487 for (si = arg.begin(); legal && si != arg.end(); ++si) {
509 command +=
" " + arg;
511 command +=
" '" + arg +
"'";
530 nout <<
"Unexpected arguments on command line:\n";
531 Args::const_iterator ai;
532 for (ai = args.begin(); ai != args.end(); ++ai) {
533 nout << (*ai) <<
" ";
553 post_command_line() {
568 set_program_brief(
const string &brief) {
582 set_program_description(
const string &description) {
583 _description = description;
611 add_runline(
const string &runline) {
612 _runlines.push_back(runline);
626 _options_by_name.clear();
659 add_option(
const string &option,
const string &parm_name,
660 int index_group,
const string &description,
661 OptionDispatchFunction option_function,
662 bool *bool_var,
void *option_data) {
664 opt._option = option;
665 opt._parm_name = parm_name;
666 opt._index_group = index_group;
667 opt._sequence = ++_next_sequence;
668 opt._description = description;
669 opt._option_function = option_function;
670 opt._option_method = (OptionDispatchMethod)NULL;
671 opt._bool_var = bool_var;
672 opt._option_data = option_data;
674 _options_by_name[option] = opt;
675 _sorted_options =
false;
677 if (bool_var != (
bool *)NULL) {
700 add_option(
const string &option,
const string &parm_name,
701 int index_group,
const string &description,
702 OptionDispatchMethod option_method,
703 bool *bool_var,
void *option_data) {
705 opt._option = option;
706 opt._parm_name = parm_name;
707 opt._index_group = index_group;
708 opt._sequence = ++_next_sequence;
709 opt._description = description;
710 opt._option_function = (OptionDispatchFunction)NULL;
711 opt._option_method = option_method;
712 opt._bool_var = bool_var;
713 opt._option_data = option_data;
715 _options_by_name[option] = opt;
716 _sorted_options =
false;
718 if (bool_var != (
bool *)NULL) {
731 redescribe_option(
const string &option,
const string &description) {
732 OptionsByName::iterator oi = _options_by_name.find(option);
733 if (oi == _options_by_name.end()) {
736 (*oi).second._description = description;
747 remove_option(
const string &option) {
748 OptionsByName::iterator oi = _options_by_name.find(option);
749 if (oi == _options_by_name.end()) {
752 _options_by_name.erase(oi);
753 _sorted_options =
false;
766 add_path_replace_options() {
768 (
"pr",
"path_replace", 40,
769 "Sometimes references to other files (textures, external references) " 770 "are stored with a full path that is appropriate for some other system, " 771 "but does not exist here. This option may be used to specify how " 772 "those invalid paths map to correct paths. Generally, this is of " 773 "the form 'orig_prefix=replacement_prefix', which indicates a " 774 "particular initial sequence of characters that should be replaced " 775 "with a new sequence; e.g. '/c/home/models=/beta/fish'. " 776 "If the replacement prefix does not begin with a slash, the file " 777 "will then be searched for along the search path specified by -pp. " 778 "You may use standard filename matching characters ('*', '?', etc.) in " 779 "the original prefix, and '**' as a component by itself stands for " 780 "any number of components.\n\n" 782 "This option may be repeated as necessary; each file will be tried " 783 "against each specified method, in the order in which they appear in " 784 "the command line, until the file is found. If the file is not found, " 785 "the last matching prefix is used anyway.",
786 &ProgramBase::dispatch_path_replace, NULL, _path_replace.p());
789 (
"pp",
"dirname", 40,
790 "Adds the indicated directory name to the list of directories to " 791 "search for filenames referenced by the source file. This is used " 792 "only for relative paths, or for paths that are made relative by a " 793 "-pr replacement string that doesn't begin with a leading slash. " 794 "The model-path is always implicitly searched anyway.",
795 &ProgramBase::dispatch_search_path, NULL, &(_path_replace->_path));
807 add_path_store_options() {
810 _path_replace->_path_store = PS_relative;
813 (
"ps",
"path_store", 40,
814 "Specifies the way an externally referenced file is to be " 815 "represented in the resulting output file. This " 816 "assumes the named filename actually exists; " 817 "see -pr to indicate how to deal with external " 818 "references that have bad pathnames. " 819 "This option will not help you to find a missing file, but simply " 820 "controls how filenames are represented in the output.\n\n" 822 "The option may be one of: rel, abs, rel_abs, strip, or keep. If " 823 "either rel or rel_abs is specified, the files are made relative to " 824 "the directory specified by -pd. The default is rel.",
825 &ProgramBase::dispatch_path_store, &_got_path_store,
826 &(_path_replace->_path_store));
829 (
"pd",
"path_directory", 40,
830 "Specifies the name of a directory to make paths relative to, if " 831 "'-ps rel' or '-ps rel_abs' is specified. If this is omitted, the " 832 "directory name is taken from the name of the output file.",
833 &ProgramBase::dispatch_filename, &_got_path_directory,
834 &(_path_replace->_path_directory));
837 (
"pc",
"target_directory", 40,
838 "Copies textures and other dependent files into the indicated " 839 "directory. If a relative pathname is specified, it is relative " 840 "to the directory specified with -pd, above.",
841 &ProgramBase::dispatch_filename, &(_path_replace->_copy_files),
842 &(_path_replace->_copy_into_directory));
857 dispatch_none(
const string &,
const string &,
void *) {
873 dispatch_true(
const string &,
const string &,
void *var) {
874 bool *bp = (
bool *)var;
891 dispatch_false(
const string &,
const string &,
void *var) {
892 bool *bp = (
bool *)var;
907 dispatch_count(
const string &,
const string &,
void *var) {
908 int *ip = (
int *)var;
922 dispatch_int(
const string &opt,
const string &arg,
void *var) {
923 int *ip = (
int *)var;
925 if (!string_to_int(arg, *ip)) {
926 nout <<
"Invalid integer parameter for -" << opt <<
": " 942 dispatch_int_pair(
const string &opt,
const string &arg,
void *var) {
943 int *ip = (
int *)var;
946 tokenize(arg, words,
",");
949 if (words.size() == 2) {
951 string_to_int(words[0], ip[0]) &&
952 string_to_int(words[1], ip[1]);
957 <<
" requires a pair of integers separated by a comma.\n";
972 dispatch_int_quad(
const string &opt,
const string &arg,
void *var) {
973 int *ip = (
int *)var;
976 tokenize(arg, words,
",");
979 if (words.size() == 4) {
981 string_to_int(words[0], ip[0]) &&
982 string_to_int(words[1], ip[1]) &&
983 string_to_int(words[1], ip[2]) &&
984 string_to_int(words[1], ip[3]);
989 <<
" requires a quad of integers separated by a comma.\n";
1004 dispatch_double(
const string &opt,
const string &arg,
void *var) {
1005 double *ip = (
double *)var;
1007 if (!string_to_double(arg, *ip)) {
1008 nout <<
"Invalid numeric parameter for -" << opt <<
": " 1024 dispatch_double_pair(
const string &opt,
const string &arg,
void *var) {
1025 double *ip = (
double *)var;
1027 vector_string words;
1028 tokenize(arg, words,
",");
1030 bool okflag =
false;
1031 if (words.size() == 2) {
1033 string_to_double(words[0], ip[0]) &&
1034 string_to_double(words[1], ip[1]);
1039 <<
" requires a pair of numbers separated by a comma.\n";
1054 dispatch_double_triple(
const string &opt,
const string &arg,
void *var) {
1055 double *ip = (
double *)var;
1057 vector_string words;
1058 tokenize(arg, words,
",");
1060 bool okflag =
false;
1061 if (words.size() == 3) {
1063 string_to_double(words[0], ip[0]) &&
1064 string_to_double(words[1], ip[1]) &&
1065 string_to_double(words[2], ip[2]);
1070 <<
" requires three numbers separated by commas.\n";
1085 dispatch_double_quad(
const string &opt,
const string &arg,
void *var) {
1086 double *ip = (
double *)var;
1088 vector_string words;
1089 tokenize(arg, words,
",");
1091 bool okflag =
false;
1092 if (words.size() == 4) {
1094 string_to_double(words[0], ip[0]) &&
1095 string_to_double(words[1], ip[1]) &&
1096 string_to_double(words[2], ip[2]) &&
1097 string_to_double(words[3], ip[3]);
1102 <<
" requires four numbers separated by commas.\n";
1117 dispatch_color(
const string &opt,
const string &arg,
void *var) {
1118 PN_stdfloat *ip = (PN_stdfloat *)var;
1120 vector_string words;
1121 tokenize(arg, words,
",");
1123 bool okflag =
false;
1124 switch (words.size()) {
1127 string_to_stdfloat(words[0], ip[0]) &&
1128 string_to_stdfloat(words[1], ip[1]) &&
1129 string_to_stdfloat(words[2], ip[2]) &&
1130 string_to_stdfloat(words[3], ip[3]);
1135 string_to_stdfloat(words[0], ip[0]) &&
1136 string_to_stdfloat(words[1], ip[1]) &&
1137 string_to_stdfloat(words[2], ip[2]);
1143 string_to_stdfloat(words[0], ip[0]) &&
1144 string_to_stdfloat(words[1], ip[3]);
1151 string_to_stdfloat(words[0], ip[0]);
1160 <<
" requires one through four numbers separated by commas.\n";
1175 dispatch_string(
const string &,
const string &arg,
void *var) {
1176 string *ip = (
string *)var;
1195 dispatch_vector_string(
const string &,
const string &arg,
void *var) {
1196 vector_string *ip = (vector_string *)var;
1197 (*ip).push_back(arg);
1212 dispatch_vector_string_comma(
const string &,
const string &arg,
void *var) {
1213 vector_string *ip = (vector_string *)var;
1215 vector_string words;
1216 tokenize(arg, words,
",");
1218 vector_string::const_iterator wi;
1219 for (wi = words.begin(); wi != words.end(); ++wi) {
1220 (*ip).push_back(*wi);
1234 dispatch_filename(
const string &opt,
const string &arg,
void *var) {
1236 nout <<
"-" << opt <<
" requires a filename parameter.\n";
1257 dispatch_search_path(
const string &opt,
const string &arg,
void *var) {
1259 nout <<
"-" << opt <<
" requires a search path parameter.\n";
1278 dispatch_coordinate_system(
const string &opt,
const string &arg,
void *var) {
1279 CoordinateSystem *ip = (CoordinateSystem *)var;
1280 (*ip) = parse_coordinate_system_string(arg);
1282 if ((*ip) == CS_invalid) {
1283 nout <<
"Invalid coordinate system for -" << opt <<
": " << arg <<
"\n" 1284 <<
"Valid coordinate system strings are any of 'y-up', 'z-up', " 1285 "'y-up-left', or 'z-up-left'.\n";
1301 dispatch_units(
const string &opt,
const string &arg,
void *var) {
1302 DistanceUnit *ip = (DistanceUnit *)var;
1303 (*ip) = string_distance_unit(arg);
1305 if ((*ip) == DU_invalid) {
1306 nout <<
"Invalid units for -" << opt <<
": " << arg <<
"\n" 1307 <<
"Valid units are mm, cm, m, km, yd, ft, in, nmi, and mi.\n";
1323 dispatch_image_type(
const string &opt,
const string &arg,
void *var) {
1331 nout <<
"Invalid image type for -" << opt <<
": " << arg <<
"\n" 1332 <<
"The following image types are known:\n";
1333 reg->
write(nout, 2);
1349 dispatch_path_replace(
const string &opt,
const string &arg,
void *var) {
1351 size_t equals = arg.find(
'=');
1352 if (equals == string::npos) {
1353 nout <<
"Invalid path replacement string for -" << opt <<
": " << arg <<
"\n" 1354 <<
"String should be of the form 'old-prefix=new-prefix'.\n";
1357 ip->
add_pattern(arg.substr(0, equals), arg.substr(equals + 1));
1371 dispatch_path_store(
const string &opt,
const string &arg,
void *var) {
1372 PathStore *ip = (PathStore *)var;
1373 (*ip) = string_path_store(arg);
1375 if ((*ip) == PS_invalid) {
1376 nout <<
"Invalid path store for -" << opt <<
": " << arg <<
"\n" 1377 <<
"Valid path store strings are any of 'rel', 'abs', " 1378 <<
"'rel_abs', 'strip', or 'keep'.\n";
1392 handle_help_option(
const string &,
const string &,
void *data) {
1427 format_text(ostream &out,
bool &last_newline,
1428 const string &prefix,
int indent_width,
1429 const string &text,
int line_width) {
1430 indent_width = min(indent_width, line_width - 20);
1431 int indent_amount = indent_width;
1432 bool initial_break =
false;
1434 if (!prefix.empty()) {
1436 indent_amount = indent_width - prefix.length();
1437 if ((
int)prefix.length() + 1 > indent_width) {
1439 initial_break =
true;
1440 indent_amount = indent_width;
1447 while (p < text.length() && isspace(text[p])) {
1448 if (text[p] ==
'\r' ||
1449 (p > 0 && text[p] ==
'\n' && text[p - 1] ==
'\n') ||
1450 (p == 0 && text[p] ==
'\n' && last_newline)) {
1451 if (!initial_break) {
1454 initial_break =
true;
1456 indent_amount = indent_width;
1458 }
else if (text[p] ==
'\n') {
1460 indent_amount = indent_width;
1462 }
else if (text[p] ==
' ') {
1469 last_newline = (!text.empty() && text[text.length() - 1] ==
'\n');
1471 while (p < text.length()) {
1474 size_t par = text.find_first_of(
"\n\r", p);
1475 bool is_paragraph_break =
false;
1476 if (par == string::npos) {
1477 par = text.length();
1485 indent(out, indent_amount);
1487 size_t eol = p + (line_width - indent_width);
1496 size_t min_eol = max((
int)p, (
int)eol - 25);
1498 while (q > min_eol && !isspace(text[q])) {
1502 while (q > min_eol && isspace(text[q])) {
1516 out << text.substr(p, eol - p) <<
"\n";
1520 while (p < text.length() && isspace(text[p])) {
1521 if (text[p] ==
'\r' ||
1522 (p > 0 && text[p] ==
'\n' && text[p - 1] ==
'\n')) {
1523 is_paragraph_break =
true;
1528 if (eol == par && is_paragraph_break) {
1531 if (p >= text.length()) {
1534 last_newline =
false;
1538 indent_amount = indent_width;
1552 if (!_sorted_options) {
1553 _options_by_index.clear();
1555 OptionsByName::const_iterator oi;
1556 for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) {
1557 _options_by_index.push_back(&(*oi).second);
1560 sort(_options_by_index.begin(), _options_by_index.end(),
1561 SortOptionsByIndex());
1562 _sorted_options =
true;
1573 get_terminal_width() {
1574 if (!_got_terminal_width) {
1575 _got_terminal_width =
true;
1576 _got_option_indent =
false;
1578 #ifdef IOCTL_TERMINAL_WIDTH 1579 if (use_terminal_width) {
1580 struct winsize size;
1581 int result = ioctl(STDIN_FILENO, TIOCGWINSZ, (
char *)&size);
1582 if (result < 0 || size.ws_col < 10) {
1585 _terminal_width = default_terminal_width;
1589 _terminal_width = size.ws_col - min(8, (
int)(size.ws_col * 0.1));
1593 #endif // IOCTL_TERMINAL_WIDTH 1595 _terminal_width = default_terminal_width;
This is intended to be the base class for most general-purpose utility programs in the PANDATOOL tree...
This is our own Panda specialization on the default STL map.
static string get_version_string()
Returns the current version of Panda, expressed as a string, e.g.
virtual void parse_command_line(int argc, char **argv)
Dispatches on each of the options on the command line, and passes the remaining parameters to handle_...
void show_usage()
Writes the usage line(s) to stderr.
This is a convenience class to specialize ConfigVariable as a boolean type.
This is the base class of a family of classes that represent particular image file types that PNMImag...
void append_directory(const Filename &directory)
Adds a new directory to the end of the search list.
static Notify * ptr()
Returns the pointer to the global Notify object.
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
This is our own Panda specialization on the default STL vector.
void show_description()
Writes the program description to stderr.
The name of a file, such as a texture file or an Egg file.
void show_text(const string &text)
Formats the indicated text to stderr with the known _terminal_width.
void set_ostream_ptr(ostream *ostream_ptr, bool delete_later)
Changes the ostream that all subsequent Notify messages will be written to.
string get_exec_command() const
Returns the command that invoked this program, as a shell-friendly string, suitable for pasting into ...
void show_options()
Describes each of the available options to stderr.
A special ostream that formats all of its output through ProgramBase::show_text().
PNMFileType * get_type_from_extension(const string &filename) const
Tries to determine what the PNMFileType is likely to be for a particular image file based on its exte...
string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
This class maintains the set of all known PNMFileTypes in the universe.
This encapsulates the user's command-line request to replace existing, incorrect pathnames to models ...
This is a convenience class to specialize ConfigVariable as an integer type.
This class stores a list of directories that can be searched, in order, to locate a particular file...
void add_pattern(const string &orig_prefix, const string &replacement_prefix)
Adds the indicated original/replace pattern to the specification.
void write_man_page(ostream &out)
Generates a man page in nroff syntax based on the description and options.
void write(ostream &out, int indent_level=0) const
Writes a list of supported image file types to the indicated output stream, one per line...
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).