26 #include "panda_getopt_long.h"
38 #ifdef IOCTL_TERMINAL_WIDTH
41 #include <sys/ioctl.h>
43 #include <sys/ioctl.h>
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);
208 const char *source_date_epoch = getenv(
"SOURCE_DATE_EPOCH");
209 if (source_date_epoch ==
nullptr || source_date_epoch[0] == 0 ||
210 (current_time = (time_t)strtoll(source_date_epoch,
nullptr, 10)) <= 0) {
211 current_time = time(
nullptr);
212 if (current_time != (time_t)-1) {
213 today = localtime(¤t_time);
218 today = gmtime(¤t_time);
221 if (today ==
nullptr || 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";
297 for (i = 1; i < argc; i++) {
298 _program_args.push_back(argv[i]);
307 if (argc > 1 && strcmp(argv[1],
"-write-man") == 0) {
311 }
else if (argc == 3) {
312 if (strlen(argv[2]) == 1 && argv[2][0] ==
'-') {
316 pofstream man_out(argv[2], std::ios::out | std::ios::trunc);
318 cerr <<
"Failed to open output file " << argv[2] <<
"!\n";
324 cerr <<
"Invalid number of options for -write-man!\n";
333 string short_options;
341 OptionsByName::const_iterator oi;
342 int next_index = 256;
349 for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) {
350 const Option &opt = (*oi).second;
353 if (opt._option.length() == 1) {
356 index = (int)opt._option[0];
358 short_options += opt._option;
359 if (!opt._parm_name.empty()) {
361 short_options +=
':';
365 index = ++next_index;
370 gopt.name = (
char *)opt._option.c_str();
371 gopt.has_arg = (opt._parm_name.empty()) ?
372 no_argument : required_argument;
379 long_options.push_back(gopt);
381 options[index] = &opt;
387 memset(&gopt, 0,
sizeof(gopt));
388 long_options.push_back(gopt);
396 const struct option *long_opts = &long_options[0];
399 getopt_long_only(argc, argv, short_options.c_str(), long_opts,
nullptr);
400 while (flag != EOF) {
402 if (optarg !=
nullptr) {
415 remaining_args.push_back(arg);
421 Options::const_iterator ii;
422 ii = options.find(flag);
423 if (ii == options.end()) {
424 nout <<
"Internal error! Invalid option index returned.\n";
428 const Option &opt = *(*ii).second;
430 if (opt._option_function != (OptionDispatchFunction)
nullptr) {
431 okflag = (*opt._option_function)(opt._option, arg, opt._option_data);
433 if (opt._option_method != (OptionDispatchMethod)
nullptr) {
434 okflag = (*opt._option_method)(
this, opt._option, arg, opt._option_data);
436 if (opt._bool_var !=
nullptr) {
437 (*opt._bool_var) =
true;
448 getopt_long_only(argc, argv, short_options.c_str(), long_opts,
nullptr);
451 if (!handle_args(remaining_args)) {
456 if (!post_command_line()) {
471 Args::const_iterator ai;
472 for (ai = _program_args.begin(); ai != _program_args.end(); ++ai) {
473 const string &arg = (*ai);
477 string::const_iterator si;
478 for (si = arg.begin(); legal && si != arg.end(); ++si) {
500 command +=
" " + arg;
502 command +=
" '" + arg +
"'";
518 nout <<
"Unexpected arguments on command line:\n";
519 Args::const_iterator ai;
520 for (ai = args.begin(); ai != args.end(); ++ai) {
521 nout << (*ai) <<
" ";
537 post_command_line() {
548 set_program_brief(
const string &brief) {
558 set_program_description(
const string &description) {
559 _description = description;
582 add_runline(
const string &runline) {
583 _runlines.push_back(runline);
593 _options_by_name.clear();
618 add_option(
const string &option,
const string &parm_name,
619 int index_group,
const string &description,
620 OptionDispatchFunction option_function,
621 bool *bool_var,
void *option_data) {
623 opt._option = option;
624 opt._parm_name = parm_name;
625 opt._index_group = index_group;
626 opt._sequence = ++_next_sequence;
627 opt._description = description;
628 opt._option_function = option_function;
629 opt._option_method = (OptionDispatchMethod)
nullptr;
630 opt._bool_var = bool_var;
631 opt._option_data = option_data;
633 _options_by_name[option] = opt;
634 _sorted_options =
false;
636 if (bool_var !=
nullptr) {
654 add_option(
const string &option,
const string &parm_name,
655 int index_group,
const string &description,
656 OptionDispatchMethod option_method,
657 bool *bool_var,
void *option_data) {
659 opt._option = option;
660 opt._parm_name = parm_name;
661 opt._index_group = index_group;
662 opt._sequence = ++_next_sequence;
663 opt._description = description;
664 opt._option_function = (OptionDispatchFunction)
nullptr;
665 opt._option_method = option_method;
666 opt._bool_var = bool_var;
667 opt._option_data = option_data;
669 _options_by_name[option] = opt;
670 _sorted_options =
false;
672 if (bool_var !=
nullptr) {
682 redescribe_option(
const string &option,
const string &description) {
683 OptionsByName::iterator oi = _options_by_name.find(option);
684 if (oi == _options_by_name.end()) {
687 (*oi).second._description = description;
696 remove_option(
const string &option) {
697 OptionsByName::iterator oi = _options_by_name.find(option);
698 if (oi == _options_by_name.end()) {
701 _options_by_name.erase(oi);
702 _sorted_options =
false;
712 add_path_replace_options() {
714 (
"pr",
"path_replace", 40,
715 "Sometimes references to other files (textures, external references) "
716 "are stored with a full path that is appropriate for some other system, "
717 "but does not exist here. This option may be used to specify how "
718 "those invalid paths map to correct paths. Generally, this is of "
719 "the form 'orig_prefix=replacement_prefix', which indicates a "
720 "particular initial sequence of characters that should be replaced "
721 "with a new sequence; e.g. '/c/home/models=/beta/fish'. "
722 "If the replacement prefix does not begin with a slash, the file "
723 "will then be searched for along the search path specified by -pp. "
724 "You may use standard filename matching characters ('*', '?', etc.) in "
725 "the original prefix, and '**' as a component by itself stands for "
726 "any number of components.\n\n"
728 "This option may be repeated as necessary; each file will be tried "
729 "against each specified method, in the order in which they appear in "
730 "the command line, until the file is found. If the file is not found, "
731 "the last matching prefix is used anyway.",
732 &ProgramBase::dispatch_path_replace,
nullptr, _path_replace.p());
735 (
"pp",
"dirname", 40,
736 "Adds the indicated directory name to the list of directories to "
737 "search for filenames referenced by the source file. This is used "
738 "only for relative paths, or for paths that are made relative by a "
739 "-pr replacement string that doesn't begin with a leading slash. "
740 "The model-path is always implicitly searched anyway.",
741 &ProgramBase::dispatch_search_path,
nullptr, &(_path_replace->_path));
750 add_path_store_options() {
753 _path_replace->_path_store = PS_relative;
756 (
"ps",
"path_store", 40,
757 "Specifies the way an externally referenced file is to be "
758 "represented in the resulting output file. This "
759 "assumes the named filename actually exists; "
760 "see -pr to indicate how to deal with external "
761 "references that have bad pathnames. "
762 "This option will not help you to find a missing file, but simply "
763 "controls how filenames are represented in the output.\n\n"
765 "The option may be one of: rel, abs, rel_abs, strip, or keep. If "
766 "either rel or rel_abs is specified, the files are made relative to "
767 "the directory specified by -pd. The default is rel.",
768 &ProgramBase::dispatch_path_store, &_got_path_store,
769 &(_path_replace->_path_store));
772 (
"pd",
"path_directory", 40,
773 "Specifies the name of a directory to make paths relative to, if "
774 "'-ps rel' or '-ps rel_abs' is specified. If this is omitted, the "
775 "directory name is taken from the name of the output file.",
776 &ProgramBase::dispatch_filename, &_got_path_directory,
777 &(_path_replace->_path_directory));
780 (
"pc",
"target_directory", 40,
781 "Copies textures and other dependent files into the indicated "
782 "directory. If a relative pathname is specified, it is relative "
783 "to the directory specified with -pd, above.",
784 &ProgramBase::dispatch_filename, &(_path_replace->_copy_files),
785 &(_path_replace->_copy_into_directory));
796 dispatch_none(
const string &,
const string &,
void *) {
809 dispatch_true(
const string &,
const string &,
void *var) {
810 bool *bp = (
bool *)var;
824 dispatch_false(
const string &,
const string &,
void *var) {
825 bool *bp = (
bool *)var;
837 dispatch_count(
const string &,
const string &,
void *var) {
838 int *ip = (
int *)var;
849 dispatch_int(
const string &opt,
const string &arg,
void *var) {
850 int *ip = (
int *)var;
853 nout <<
"Invalid integer parameter for -" << opt <<
": "
866 dispatch_int_pair(
const string &opt,
const string &arg,
void *var) {
867 int *ip = (
int *)var;
873 if (words.size() == 2) {
881 <<
" requires a pair of integers separated by a comma.\n";
893 dispatch_int_quad(
const string &opt,
const string &arg,
void *var) {
894 int *ip = (
int *)var;
900 if (words.size() == 4) {
910 <<
" requires a quad of integers separated by a comma.\n";
922 dispatch_double(
const string &opt,
const string &arg,
void *var) {
923 double *ip = (
double *)var;
926 nout <<
"Invalid numeric parameter for -" << opt <<
": "
939 dispatch_double_pair(
const string &opt,
const string &arg,
void *var) {
940 double *ip = (
double *)var;
946 if (words.size() == 2) {
954 <<
" requires a pair of numbers separated by a comma.\n";
966 dispatch_double_triple(
const string &opt,
const string &arg,
void *var) {
967 double *ip = (
double *)var;
973 if (words.size() == 3) {
982 <<
" requires three numbers separated by commas.\n";
994 dispatch_double_quad(
const string &opt,
const string &arg,
void *var) {
995 double *ip = (
double *)var;
1000 bool okflag =
false;
1001 if (words.size() == 4) {
1011 <<
" requires four numbers separated by commas.\n";
1024 dispatch_color(
const string &opt,
const string &arg,
void *var) {
1025 PN_stdfloat *ip = (PN_stdfloat *)var;
1027 vector_string words;
1030 bool okflag =
false;
1031 switch (words.size()) {
1034 string_to_stdfloat(words[0], ip[0]) &&
1035 string_to_stdfloat(words[1], ip[1]) &&
1036 string_to_stdfloat(words[2], ip[2]) &&
1037 string_to_stdfloat(words[3], ip[3]);
1042 string_to_stdfloat(words[0], ip[0]) &&
1043 string_to_stdfloat(words[1], ip[1]) &&
1044 string_to_stdfloat(words[2], ip[2]);
1050 string_to_stdfloat(words[0], ip[0]) &&
1051 string_to_stdfloat(words[1], ip[3]);
1058 string_to_stdfloat(words[0], ip[0]);
1067 <<
" requires one through four numbers separated by commas.\n";
1079 dispatch_string(
const string &,
const string &arg,
void *var) {
1080 string *ip = (
string *)var;
1095 dispatch_vector_string(
const string &,
const string &arg,
void *var) {
1096 vector_string *ip = (vector_string *)var;
1097 (*ip).push_back(arg);
1110 dispatch_vector_string_comma(
const string &,
const string &arg,
void *var) {
1111 vector_string *ip = (vector_string *)var;
1113 vector_string words;
1116 vector_string::const_iterator wi;
1117 for (wi = words.begin(); wi != words.end(); ++wi) {
1118 (*ip).push_back(*wi);
1130 dispatch_filename(
const string &opt,
const string &arg,
void *var) {
1132 nout <<
"-" << opt <<
" requires a filename parameter.\n";
1150 dispatch_search_path(
const string &opt,
const string &arg,
void *var) {
1152 nout <<
"-" << opt <<
" requires a search path parameter.\n";
1168 dispatch_coordinate_system(
const string &opt,
const string &arg,
void *var) {
1169 CoordinateSystem *ip = (CoordinateSystem *)var;
1170 (*ip) = parse_coordinate_system_string(arg);
1172 if ((*ip) == CS_invalid) {
1173 nout <<
"Invalid coordinate system for -" << opt <<
": " << arg <<
"\n"
1174 <<
"Valid coordinate system strings are any of 'y-up', 'z-up', "
1175 "'y-up-left', or 'z-up-left'.\n";
1188 dispatch_units(
const string &opt,
const string &arg,
void *var) {
1192 if ((*ip) == DU_invalid) {
1193 nout <<
"Invalid units for -" << opt <<
": " << arg <<
"\n"
1194 <<
"Valid units are mm, cm, m, km, yd, ft, in, nmi, and mi.\n";
1207 dispatch_image_type(
const string &opt,
const string &arg,
void *var) {
1214 if ((*ip) ==
nullptr) {
1215 nout <<
"Invalid image type for -" << opt <<
": " << arg <<
"\n"
1216 <<
"The following image types are known:\n";
1217 reg->
write(nout, 2);
1230 dispatch_path_replace(
const string &opt,
const string &arg,
void *var) {
1232 size_t equals = arg.find(
'=');
1233 if (equals == string::npos) {
1234 nout <<
"Invalid path replacement string for -" << opt <<
": " << arg <<
"\n"
1235 <<
"String should be of the form 'old-prefix=new-prefix'.\n";
1238 ip->
add_pattern(arg.substr(0, equals), arg.substr(equals + 1));
1249 dispatch_path_store(
const string &opt,
const string &arg,
void *var) {
1253 if ((*ip) == PS_invalid) {
1254 nout <<
"Invalid path store for -" << opt <<
": " << arg <<
"\n"
1255 <<
"Valid path store strings are any of 'rel', 'abs', "
1256 <<
"'rel_abs', 'strip', or 'keep'.\n";
1268 handle_help_option(
const string &,
const string &,
void *data) {
1297 format_text(std::ostream &out,
bool &last_newline,
1298 const string &prefix,
int indent_width,
1299 const string &text,
int line_width) {
1300 indent_width = min(indent_width, line_width - 20);
1301 int indent_amount = indent_width;
1302 bool initial_break =
false;
1304 if (!prefix.empty()) {
1306 indent_amount = indent_width - prefix.length();
1307 if ((
int)prefix.length() + 1 > indent_width) {
1309 initial_break =
true;
1310 indent_amount = indent_width;
1317 while (p < text.length() && isspace(text[p])) {
1318 if (text[p] ==
'\r' ||
1319 (p > 0 && text[p] ==
'\n' && text[p - 1] ==
'\n') ||
1320 (p == 0 && text[p] ==
'\n' && last_newline)) {
1321 if (!initial_break) {
1324 initial_break =
true;
1326 indent_amount = indent_width;
1328 }
else if (text[p] ==
'\n') {
1330 indent_amount = indent_width;
1332 }
else if (text[p] ==
' ') {
1339 last_newline = (!text.empty() && text[text.length() - 1] ==
'\n');
1341 while (p < text.length()) {
1344 size_t par = text.find_first_of(
"\n\r", p);
1345 bool is_paragraph_break =
false;
1346 if (par == string::npos) {
1347 par = text.length();
1355 indent(out, indent_amount);
1357 size_t eol = p + (line_width - indent_width);
1366 size_t min_eol = max((
int)p, (
int)eol - 25);
1368 while (q > min_eol && !isspace(text[q])) {
1372 while (q > min_eol && isspace(text[q])) {
1386 out << text.substr(p, eol - p) <<
"\n";
1390 while (p < text.length() && isspace(text[p])) {
1391 if (text[p] ==
'\r' ||
1392 (p > 0 && text[p] ==
'\n' && text[p - 1] ==
'\n')) {
1393 is_paragraph_break =
true;
1398 if (eol == par && is_paragraph_break) {
1401 if (p >= text.length()) {
1404 last_newline =
false;
1408 indent_amount = indent_width;
1419 if (!_sorted_options) {
1420 _options_by_index.clear();
1422 OptionsByName::const_iterator oi;
1423 for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) {
1424 _options_by_index.push_back(&(*oi).second);
1427 sort(_options_by_index.begin(), _options_by_index.end(),
1428 SortOptionsByIndex());
1429 _sorted_options =
true;
1437 get_terminal_width() {
1438 if (!_got_terminal_width) {
1439 _got_terminal_width =
true;
1440 _got_option_indent =
false;
1442 #ifdef IOCTL_TERMINAL_WIDTH
1443 if (use_terminal_width) {
1444 struct winsize size;
1445 int result = ioctl(STDIN_FILENO, TIOCGWINSZ, (
char *)&size);
1446 if (result < 0 || size.ws_col < 10) {
1449 _terminal_width = default_terminal_width;
1453 _terminal_width = size.ws_col - min(8, (
int)(size.ws_col * 0.1));
1459 _terminal_width = default_terminal_width;
This is a convenience class to specialize ConfigVariable as a boolean type.
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 append_directory(const Filename &directory)
Adds a new directory to the end of the search list.
The name of a file, such as a texture file or an Egg file.
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,...
std::string get_basename_wo_extension() const
Returns the basename part of the filename, without the file extension.
static Notify * ptr()
Returns the pointer to the global Notify object.
void set_ostream_ptr(std::ostream *ostream_ptr, bool delete_later)
Changes the ostream that all subsequent Notify messages will be written to.
This class maintains the set of all known PNMFileTypes in the universe.
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
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.
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...
This is the base class of a family of classes that represent particular image file types that PNMImag...
get_version_string
Returns the current version of Panda, expressed as a string, e.g.
This encapsulates the user's command-line request to replace existing, incorrect pathnames to models ...
void add_pattern(const std::string &orig_prefix, const std::string &replacement_prefix)
Adds the indicated original/replace pattern to the specification.
This is intended to be the base class for most general-purpose utility programs in the PANDATOOL tree...
void write_man_page(std::ostream &out)
Generates a man page in nroff syntax based on the description and options.
std::string get_exec_command() const
Returns the command that invoked this program, as a shell-friendly string, suitable for pasting into ...
void show_usage()
Writes the usage line(s) to stderr.
void show_text(const std::string &text)
Formats the indicated text to stderr with the known _terminal_width.
void show_options()
Describes each of the available options to stderr.
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_description()
Writes the program description to stderr.
A special ostream that formats all of its output through ProgramBase::show_text().
This is our own Panda specialization on the default STL map.
This is our own Panda specialization on the default STL vector.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
DistanceUnit
This enumerated type lists all the kinds of units we're likely to come across in model conversion pro...
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
PathStore
This enumerated type lists the methods by which a filename path might be mangled before storing in a ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void preprocess_argv(int &argc, char **&argv)
Processes the argc, argv pair as needed before passing it to getopt().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.