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) {
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...
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...
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.
float length() const
Returns the length of the vector, by the Pythagorean theorem.
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.
void show_options()
Describes each of the available options to stderr.
A special ostream that formats all of its output through ProgramBase::show_text().
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.
string get_exec_command() const
Returns the command that invoked this program, as a shell-friendly string, suitable for pasting into ...
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).