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 <<
"\" \"" 214 << PandaSystem::get_version_string() <<
"\" Panda3D\n";
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;
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
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.
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
void write(std::ostream &out, int indent_level=0) const
Writes a list of supported image file types to the indicated output stream, one per line.
This is a convenience class to specialize ConfigVariable as a boolean type.
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void add_pattern(const std::string &orig_prefix, const std::string &replacement_prefix)
Adds the indicated original/replace pattern to the specification.
static Notify * ptr()
Returns the pointer to the global Notify object.
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
DistanceUnit
This enumerated type lists all the kinds of units we're likely to come across in model conversion pro...
This is our own Panda specialization on the default STL vector.
void show_description()
Writes the program description to stderr.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of a file, such as a texture file or an Egg file.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
DistanceUnit string_distance_unit(const string &str)
Converts from a string, as might be input by the user, to one of the known DistanceUnit types.
std::string get_exec_command() const
Returns the command that invoked this program, as a shell-friendly string, suitable for pasting into ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void show_options()
Describes each of the available options to stderr.
A special ostream that formats all of its output through ProgramBase::show_text().
void write_man_page(std::ostream &out)
Generates a man page in nroff syntax based on the description and options.
PNMFileType * get_type_from_extension(const std::string &filename) const
Tries to determine what the PNMFileType is likely to be for a particular image file based on its exte...
void preprocess_argv(int &argc, char **&argv)
Processes the argc, argv pair as needed before passing it to getopt().
PathStore
This enumerated type lists the methods by which a filename path might be mangled before storing in a ...
void tokenize(const string &str, vector_string &words, const string &delimiters, bool discard_repeated_delimiters)
Chops the source string up into pieces delimited by any of the characters specified in delimiters.
This class maintains the set of all known PNMFileTypes in the universe.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This encapsulates the user's command-line request to replace existing, incorrect pathnames to models ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PathStore string_path_store(const std::string &str)
Stores from a string, as might be input by the user, to one of the known PathStore types.
void show_text(const std::string &text)
Formats the indicated text to stderr with the known _terminal_width.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes,...
void set_ostream_ptr(std::ostream *ostream_ptr, bool delete_later)
Changes the ostream that all subsequent Notify messages will be written to.