/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file programBase.cxx * @author drose * @date 2000-02-13 */ #include "programBase.h" #include "wordWrapStream.h" #include "pnmFileTypeRegistry.h" #include "indent.h" #include "dSearchPath.h" #include "coordinateSystem.h" #include "dconfig.h" #include "config_dconfig.h" #include "string_utils.h" #include "vector_string.h" #include "configVariableInt.h" #include "configVariableBool.h" #include "panda_getopt_long.h" #include "preprocess_argv.h" #include "pandaSystem.h" #include #include #include // This manifest is defined if we are running on a system (e.g. most any // Unix) that allows us to determine the width of the terminal screen via an // ioctl() call. It's just handy to know for formatting output nicely for the // user. #ifdef IOCTL_TERMINAL_WIDTH #include #ifndef TIOCGWINSZ #include #elif __APPLE__ #include #endif // TIOCGWINSZ #endif // IOCTL_TERMINAL_WIDTH using std::cerr; using std::cout; using std::max; using std::min; using std::string; bool ProgramBase::SortOptionsByIndex:: operator () (const Option *a, const Option *b) const { if (a->_index_group != b->_index_group) { return a->_index_group < b->_index_group; } return a->_sequence < b->_sequence; } // This should be called at program termination just to make sure Notify gets // properly flushed before we exit, if someone calls exit(). It's probably // not necessary, but why not be phobic about it? static void flush_nout() { nout << std::flush; } static ConfigVariableInt default_terminal_width ("default-terminal-width", 72, PRC_DESC("Specify the column at which to wrap output lines " "from pandatool-based programs, if it cannot be determined " "automatically.")); static ConfigVariableBool use_terminal_width ("use-terminal-width", true, PRC_DESC("True to try to determine the terminal width automatically from " "the operating system, if supported; false to use the width " "specified by default-terminal-width even if the operating system " "appears to report a valid width.")); /** * */ ProgramBase:: ProgramBase(const string &name) : _name(name) { // Set up Notify to write output to our own formatted stream. Notify::ptr()->set_ostream_ptr(new WordWrapStream(this), true); // And we'll want to be sure to flush that in all normal exit cases. atexit(&flush_nout); _path_replace = new PathReplace; // If a program never adds the path store options, the default path store is // PS_absolute. This is the most robust solution for programs that read // files but do not need to write them. _path_replace->_path_store = PS_absolute; _got_path_store = false; _got_path_directory = false; _next_sequence = 0; _sorted_options = false; _last_newline = false; _got_terminal_width = false; _got_option_indent = false; add_option("h", "", 100, "Display this help page.", &ProgramBase::handle_help_option, nullptr, (void *)this); // It's nice to start with a blank line. nout << "\r"; } /** * */ ProgramBase:: ~ProgramBase() { // Reset Notify in case any messages get sent after our destruction--our // stream is no longer valid. Notify::ptr()->set_ostream_ptr(nullptr, false); } /** * Writes the program description to stderr. */ void ProgramBase:: show_description() { nout << _description << "\n"; } /** * Writes the usage line(s) to stderr. */ void ProgramBase:: show_usage() { nout << "\rUsage:\n"; Runlines::const_iterator ri; string prog = " " + _program_name.get_basename_wo_extension(); for (ri = _runlines.begin(); ri != _runlines.end(); ++ri) { show_text(prog, prog.length() + 1, *ri); } nout << "\r"; } /** * Describes each of the available options to stderr. */ void ProgramBase:: show_options() { sort_options(); if (!_got_option_indent) { get_terminal_width(); _option_indent = min(15, (int)(_terminal_width * 0.25)); _got_option_indent = true; } nout << "Options:\n"; OptionsByIndex::const_iterator oi; for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) { const Option &opt = *(*oi); string prefix = " -" + opt._option + " " + opt._parm_name; show_text(prefix, _option_indent, opt._description + "\r"); } } /** * Formats the indicated text and its prefix for output to stderr with the * known _terminal_width. */ void ProgramBase:: show_text(const string &prefix, int indent_width, string text) { get_terminal_width(); // This is correct! It goes go to cerr, not to nout. Sending it to nout // would be cyclic, since nout is redefined to map back through this // function. format_text(cerr, _last_newline, prefix, indent_width, text, _terminal_width); } /** * Generates a man page in nroff syntax based on the description and options. * This is useful when creating a man page for this utility. */ void ProgramBase:: write_man_page(std::ostream &out) { string prog = _program_name.get_basename_wo_extension(); out << ".\\\" Automatically generated by " << prog << " -write-man\n"; // Format the man page title as the uppercase version of the program name, // as per the UNIX manual conventions. out << ".TH "; string::const_iterator si; for (si = _name.begin(); si != _name.end(); ++si) { out << (char)toupper(*si); } // Generate a date string for inclusion into the footer. char date_str[256]; date_str[0] = 0; time_t current_time = time(nullptr); if (current_time != (time_t) -1) { tm *today = localtime(¤t_time); if (today == nullptr || 0 == strftime(date_str, 256, "%d %B %Y", today)) { date_str[0] = 0; } } out << " 1 \"" << date_str << "\" \"" << PandaSystem::get_version_string() << "\" Panda3D\n"; out << ".SH NAME\n"; if (_brief.empty()) { out << _name << "\n"; } else { out << _name << " \\- " << _brief << "\n"; } out << ".SH SYNOPSIS\n"; Runlines::const_iterator ri = _runlines.begin(); if (ri != _runlines.end()) { out << "\\fB" << prog << "\\fR " << *ri << "\n"; ++ri; } for (; ri != _runlines.end(); ++ri) { out << ".br\n"; out << "\\fB" << prog << "\\fR " << *ri << "\n"; } out << ".SH DESCRIPTION\n"; string::const_iterator di; char prev = 0; for (di = _description.begin(); di != _description.end(); ++di) { if ((*di) == '-') { // We have to escape hyphens. out << "\\-"; } else if (prev == '\n' && (*di) == '\n') { // Indicate the start of a new paragraph. out << ".PP\n"; } else { out << (char)(*di); } prev = (*di); } out << "\n"; out << ".SH OPTIONS\n"; sort_options(); OptionsByIndex::const_iterator oi; for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) { const Option &opt = *(*oi); out << ".TP\n"; if (opt._parm_name.empty()) { out << ".B \\-" << opt._option << "\n"; } else { out << ".BI \"\\-" << opt._option << " \" \"" << opt._parm_name << "\"\n"; } out << opt._description << "\n"; } } /** * Dispatches on each of the options on the command line, and passes the * remaining parameters to handle_args(). If an error on the command line is * detected, will automatically call show_usage() and exit(1). */ void ProgramBase:: parse_command_line(int argc, char **argv) { preprocess_argv(argc, argv); // Setting this variable to zero reinitializes the options parser This is // only necessary for processing multiple command lines in the same program // (mainly the MaxToEgg converter plugin) extern int optind; optind = 0; _program_name = Filename::from_os_specific(argv[0]); int i; for (i = 1; i < argc; i++) { _program_args.push_back(argv[i]); } if (_name.empty()) { _name = _program_name.get_basename_wo_extension(); } // Catch a special hidden option: -write-man, which causes the tool to // generate a manual page. if (argc > 1 && strcmp(argv[1], "-write-man") == 0) { if (argc == 2) { write_man_page(cout); } else if (argc == 3) { if (strlen(argv[2]) == 1 && argv[2][0] == '-') { write_man_page(cout); } else { pofstream man_out(argv[2], std::ios::out | std::ios::trunc); if (!man_out) { cerr << "Failed to open output file " << argv[2] << "!\n"; } write_man_page(man_out); man_out.close(); } } else { cerr << "Invalid number of options for -write-man!\n"; exit(1); } exit(0); } // Build up the long options list and the short options string for // getopt_long_only(). pvector long_options; string short_options; // We also need to build a temporary map of int index numbers to Option // pointers. We'll pass these index numbers to GNU's getopt_long() so we // can tell one option from another. typedef pmap Options; Options options; OptionsByName::const_iterator oi; int next_index = 256; // Let's prefix the option string with "-" to tell getopt that we want it to // tell us the post-option arguments, instead of trying to meddle with ARGC // and ARGV (which we aren't using directly). short_options = "-"; for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) { const Option &opt = (*oi).second; int index; if (opt._option.length() == 1) { // This is a "short" option; its option string consists of only one // letter. Its index is the letter itself. index = (int)opt._option[0]; short_options += opt._option; if (!opt._parm_name.empty()) { // This option takes an argument. short_options += ':'; } } else { // This is a "long" option; we'll assign it the next available index. index = ++next_index; } // Now add it to the GNU data structures. struct option gopt; gopt.name = (char *)opt._option.c_str(); gopt.has_arg = (opt._parm_name.empty()) ? no_argument : required_argument; gopt.flag = nullptr; // Return an index into the _options_by_index array, offset by 256 so we // don't confuse it with '?'. gopt.val = index; long_options.push_back(gopt); options[index] = &opt; } // Finally, add one more structure, all zeroes, to indicate the end of the // options. struct option gopt; memset(&gopt, 0, sizeof(gopt)); long_options.push_back(gopt); // We'll use this vector to save the non-option arguments. Generally, these // will all be at the end, but with the GNU extensions, they need not be. Args remaining_args; // Now call getopt_long() to actually parse the arguments. extern char *optarg; const struct option *long_opts = &long_options[0]; int flag = getopt_long_only(argc, argv, short_options.c_str(), long_opts, nullptr); while (flag != EOF) { string arg; if (optarg != nullptr) { arg = optarg; } switch (flag) { case '?': // Invalid option or parameter. show_usage(); exit(1); case '\x1': // A special return value from getopt() indicating a non-option // argument. remaining_args.push_back(arg); break; default: { // A normal option. Figure out which one it is. Options::const_iterator ii; ii = options.find(flag); if (ii == options.end()) { nout << "Internal error! Invalid option index returned.\n"; abort(); } const Option &opt = *(*ii).second; bool okflag = true; if (opt._option_function != (OptionDispatchFunction)nullptr) { okflag = (*opt._option_function)(opt._option, arg, opt._option_data); } if (opt._option_method != (OptionDispatchMethod)nullptr) { okflag = (*opt._option_method)(this, opt._option, arg, opt._option_data); } if (opt._bool_var != nullptr) { (*opt._bool_var) = true; } if (!okflag) { show_usage(); exit(1); } } } flag = getopt_long_only(argc, argv, short_options.c_str(), long_opts, nullptr); } if (!handle_args(remaining_args)) { show_usage(); exit(1); } if (!post_command_line()) { show_usage(); exit(1); } } /** * Returns the command that invoked this program, as a shell-friendly string, * suitable for pasting into the comments of output files. */ string ProgramBase:: get_exec_command() const { string command; command = _program_name.get_basename_wo_extension(); Args::const_iterator ai; for (ai = _program_args.begin(); ai != _program_args.end(); ++ai) { const string &arg = (*ai); // First, check to see if the string is shell-acceptable. bool legal = true; string::const_iterator si; for (si = arg.begin(); legal && si != arg.end(); ++si) { switch (*si) { case ' ': case '\n': case '\t': case '*': case '?': case '\\': case '(': case ')': case '|': case '&': case '<': case '>': case '"': case ';': case '$': legal = false; } } if (legal) { command += " " + arg; } else { command += " '" + arg + "'"; } } return command; } /** * Does something with the additional arguments on the command line (after all * the -options have been parsed). Returns true if the arguments are good, * false otherwise. */ bool ProgramBase:: handle_args(ProgramBase::Args &args) { if (!args.empty()) { nout << "Unexpected arguments on command line:\n"; Args::const_iterator ai; for (ai = args.begin(); ai != args.end(); ++ai) { nout << (*ai) << " "; } nout << "\r"; return false; } return true; } /** * This is called after the command line has been completely processed, and it * gives the program a chance to do some last-minute processing and validation * of the options and arguments. It should return true if everything is fine, * false if there is an error. */ bool ProgramBase:: post_command_line() { return true; } /** * Sets a brief synopsis of the program's function. This is currently only * used for generating the synopsis of the program's man page. * * This should be of the format: "perform operation foo on bar files" */ void ProgramBase:: set_program_brief(const string &brief) { _brief = brief; } /** * Sets the description of the program that will be reported by show_usage(). * The description should be one long string of text. Embedded newline * characters are interpreted as paragraph breaks and printed as blank lines. */ void ProgramBase:: set_program_description(const string &description) { _description = description; } /** * Removes all of the runlines that were previously added, presumably before * adding some new ones. */ void ProgramBase:: clear_runlines() { _runlines.clear(); } /** * Adds an additional line to the list of lines that will be displayed to * describe briefly how the program is to be run. Each line should be * something like "[opts] arg1 arg2", that is, it does *not* include the name * of the program, but it includes everything that should be printed after the * name of the program. * * Normally there is only one runline for a given program, but it is possible * to define more than one. */ void ProgramBase:: add_runline(const string &runline) { _runlines.push_back(runline); } /** * Removes all of the options that were previously added, presumably before * adding some new ones. Normally you wouldn't want to do this unless you * want to completely replace all of the options defined by base classes. */ void ProgramBase:: clear_options() { _options_by_name.clear(); } /** * Adds (or redefines) a command line option. When parse_command_line() is * executed it will look for these options (followed by a hyphen) on the * command line; when a particular option is found it will call the indicated * option_function, supplying the provided option_data. This allows the user * to define a function that does some special behavior for any given option, * or to use any of a number of generic pre-defined functions to fill in data * for each option. * * Each option may or may not take a parameter. If parm_name is nonempty, it * is assumed that the option does take a parameter (and parm_name contains * the name that will be printed by show_options()). This parameter will be * supplied as the second parameter to the dispatch function. If parm_name is * empty, it is assumed that the option does not take a parameter. There is * no provision for optional parameters. * * The options are listed first in order by their index_group number, and then * in the order that add_option() was called. This provides a mechanism for * listing the options defined in derived classes before those of the base * classes. */ void ProgramBase:: add_option(const string &option, const string &parm_name, int index_group, const string &description, OptionDispatchFunction option_function, bool *bool_var, void *option_data) { Option opt; opt._option = option; opt._parm_name = parm_name; opt._index_group = index_group; opt._sequence = ++_next_sequence; opt._description = description; opt._option_function = option_function; opt._option_method = (OptionDispatchMethod)nullptr; opt._bool_var = bool_var; opt._option_data = option_data; _options_by_name[option] = opt; _sorted_options = false; if (bool_var != nullptr) { (*bool_var) = false; } } /** * This is another variant on add_option(), above, except that it receives a * pointer to a "method", which is really just another static (or global) * function, whose first parameter is a ProgramBase *. * * We can't easily add a variant that accepts a real method, because the C++ * syntax for methods requires us to know exactly what class object the method * is defined for, and we want to support adding pointers for methods that are * defined in other classes. So we have this hacky thing, which requires the * "method" to be declared static, and receive its this pointer explicitly, as * the first argument. */ void ProgramBase:: add_option(const string &option, const string &parm_name, int index_group, const string &description, OptionDispatchMethod option_method, bool *bool_var, void *option_data) { Option opt; opt._option = option; opt._parm_name = parm_name; opt._index_group = index_group; opt._sequence = ++_next_sequence; opt._description = description; opt._option_function = (OptionDispatchFunction)nullptr; opt._option_method = option_method; opt._bool_var = bool_var; opt._option_data = option_data; _options_by_name[option] = opt; _sorted_options = false; if (bool_var != nullptr) { (*bool_var) = false; } } /** * Changes the description associated with a previously-defined option. * Returns true if the option was changed, false if it hadn't been defined. */ bool ProgramBase:: redescribe_option(const string &option, const string &description) { OptionsByName::iterator oi = _options_by_name.find(option); if (oi == _options_by_name.end()) { return false; } (*oi).second._description = description; return true; } /** * Removes a previously-defined option. Returns true if the option was * removed, false if it hadn't existed. */ bool ProgramBase:: remove_option(const string &option) { OptionsByName::iterator oi = _options_by_name.find(option); if (oi == _options_by_name.end()) { return false; } _options_by_name.erase(oi); _sorted_options = false; return true; } /** * Adds -pr etc. as valid options for this program. These are appropriate * for a model converter or model reader type program, and specify how to * locate possibly-invalid pathnames in the source model file. */ void ProgramBase:: add_path_replace_options() { add_option ("pr", "path_replace", 40, "Sometimes references to other files (textures, external references) " "are stored with a full path that is appropriate for some other system, " "but does not exist here. This option may be used to specify how " "those invalid paths map to correct paths. Generally, this is of " "the form 'orig_prefix=replacement_prefix', which indicates a " "particular initial sequence of characters that should be replaced " "with a new sequence; e.g. '/c/home/models=/beta/fish'. " "If the replacement prefix does not begin with a slash, the file " "will then be searched for along the search path specified by -pp. " "You may use standard filename matching characters ('*', '?', etc.) in " "the original prefix, and '**' as a component by itself stands for " "any number of components.\n\n" "This option may be repeated as necessary; each file will be tried " "against each specified method, in the order in which they appear in " "the command line, until the file is found. If the file is not found, " "the last matching prefix is used anyway.", &ProgramBase::dispatch_path_replace, nullptr, _path_replace.p()); add_option ("pp", "dirname", 40, "Adds the indicated directory name to the list of directories to " "search for filenames referenced by the source file. This is used " "only for relative paths, or for paths that are made relative by a " "-pr replacement string that doesn't begin with a leading slash. " "The model-path is always implicitly searched anyway.", &ProgramBase::dispatch_search_path, nullptr, &(_path_replace->_path)); } /** * Adds -ps etc. as valid options for this program. These are appropriate * for a model converter type program, and specify how to represent filenames * in the output file. */ void ProgramBase:: add_path_store_options() { // If a program has path store options at all, the default path store is // relative. _path_replace->_path_store = PS_relative; add_option ("ps", "path_store", 40, "Specifies the way an externally referenced file is to be " "represented in the resulting output file. This " "assumes the named filename actually exists; " "see -pr to indicate how to deal with external " "references that have bad pathnames. " "This option will not help you to find a missing file, but simply " "controls how filenames are represented in the output.\n\n" "The option may be one of: rel, abs, rel_abs, strip, or keep. If " "either rel or rel_abs is specified, the files are made relative to " "the directory specified by -pd. The default is rel.", &ProgramBase::dispatch_path_store, &_got_path_store, &(_path_replace->_path_store)); add_option ("pd", "path_directory", 40, "Specifies the name of a directory to make paths relative to, if " "'-ps rel' or '-ps rel_abs' is specified. If this is omitted, the " "directory name is taken from the name of the output file.", &ProgramBase::dispatch_filename, &_got_path_directory, &(_path_replace->_path_directory)); add_option ("pc", "target_directory", 40, "Copies textures and other dependent files into the indicated " "directory. If a relative pathname is specified, it is relative " "to the directory specified with -pd, above.", &ProgramBase::dispatch_filename, &(_path_replace->_copy_files), &(_path_replace->_copy_into_directory)); } /** * Standard dispatch function for an option that takes no parameters, and does * nothing special. Typically this would be used for a boolean flag, whose * presence means something and whose absence means something else. Use the * bool_var parameter to add_option() to determine whether the option appears * on the command line or not. */ bool ProgramBase:: dispatch_none(const string &, const string &, void *) { return true; } /** * Standard dispatch function for an option that takes no parameters, and when * it is present sets a bool variable to the 'true' value. This is another * way to handle a boolean flag. See also dispatch_none() and * dispatch_false(). * * The data pointer is to a bool variable. */ bool ProgramBase:: dispatch_true(const string &, const string &, void *var) { bool *bp = (bool *)var; (*bp) = true; return true; } /** * Standard dispatch function for an option that takes no parameters, and when * it is present sets a bool variable to the 'false' value. This is another * way to handle a boolean flag. See also dispatch_none() and * dispatch_true(). * * The data pointer is to a bool variable. */ bool ProgramBase:: dispatch_false(const string &, const string &, void *var) { bool *bp = (bool *)var; (*bp) = false; return true; } /** * Standard dispatch function for an option that takes no parameters, but * whose presence on the command line increments an integer counter for each * time it appears. -v is often an option that works this way. The data * pointer is to an int counter variable. */ bool ProgramBase:: dispatch_count(const string &, const string &, void *var) { int *ip = (int *)var; (*ip)++; return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as an integer. The data pointer is to an int variable. */ bool ProgramBase:: dispatch_int(const string &opt, const string &arg, void *var) { int *ip = (int *)var; if (!string_to_int(arg, *ip)) { nout << "Invalid integer parameter for -" << opt << ": " << arg << "\n"; return false; } return true; } /** * Standard dispatch function for an option that takes a pair of integer * parameters. The data pointer is to an array of two integers. */ bool ProgramBase:: dispatch_int_pair(const string &opt, const string &arg, void *var) { int *ip = (int *)var; vector_string words; tokenize(arg, words, ","); bool okflag = false; if (words.size() == 2) { okflag = string_to_int(words[0], ip[0]) && string_to_int(words[1], ip[1]); } if (!okflag) { nout << "-" << opt << " requires a pair of integers separated by a comma.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes a quad of integer * parameters. The data pointer is to an array of four integers. */ bool ProgramBase:: dispatch_int_quad(const string &opt, const string &arg, void *var) { int *ip = (int *)var; vector_string words; tokenize(arg, words, ","); bool okflag = false; if (words.size() == 4) { okflag = string_to_int(words[0], ip[0]) && string_to_int(words[1], ip[1]) && string_to_int(words[1], ip[2]) && string_to_int(words[1], ip[3]); } if (!okflag) { nout << "-" << opt << " requires a quad of integers separated by a comma.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a double. The data pointer is to an double variable. */ bool ProgramBase:: dispatch_double(const string &opt, const string &arg, void *var) { double *ip = (double *)var; if (!string_to_double(arg, *ip)) { nout << "Invalid numeric parameter for -" << opt << ": " << arg << "\n"; return false; } return true; } /** * Standard dispatch function for an option that takes a pair of double * parameters. The data pointer is to an array of two doubles. */ bool ProgramBase:: dispatch_double_pair(const string &opt, const string &arg, void *var) { double *ip = (double *)var; vector_string words; tokenize(arg, words, ","); bool okflag = false; if (words.size() == 2) { okflag = string_to_double(words[0], ip[0]) && string_to_double(words[1], ip[1]); } if (!okflag) { nout << "-" << opt << " requires a pair of numbers separated by a comma.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes a triple of double * parameters. The data pointer is to an array of three doubles. */ bool ProgramBase:: dispatch_double_triple(const string &opt, const string &arg, void *var) { double *ip = (double *)var; vector_string words; tokenize(arg, words, ","); bool okflag = false; if (words.size() == 3) { okflag = string_to_double(words[0], ip[0]) && string_to_double(words[1], ip[1]) && string_to_double(words[2], ip[2]); } if (!okflag) { nout << "-" << opt << " requires three numbers separated by commas.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes a quad of double * parameters. The data pointer is to an array of four doubles. */ bool ProgramBase:: dispatch_double_quad(const string &opt, const string &arg, void *var) { double *ip = (double *)var; vector_string words; tokenize(arg, words, ","); bool okflag = false; if (words.size() == 4) { okflag = string_to_double(words[0], ip[0]) && string_to_double(words[1], ip[1]) && string_to_double(words[2], ip[2]) && string_to_double(words[3], ip[3]); } if (!okflag) { nout << "-" << opt << " requires four numbers separated by commas.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes a color, as l or l,a or * r,g,b or r,g,b,a. The data pointer is to an array of four floats, e.g. a * LColor. */ bool ProgramBase:: dispatch_color(const string &opt, const string &arg, void *var) { PN_stdfloat *ip = (PN_stdfloat *)var; vector_string words; tokenize(arg, words, ","); bool okflag = false; switch (words.size()) { case 4: okflag = string_to_stdfloat(words[0], ip[0]) && string_to_stdfloat(words[1], ip[1]) && string_to_stdfloat(words[2], ip[2]) && string_to_stdfloat(words[3], ip[3]); break; case 3: okflag = string_to_stdfloat(words[0], ip[0]) && string_to_stdfloat(words[1], ip[1]) && string_to_stdfloat(words[2], ip[2]); ip[3] = 1.0; break; case 2: okflag = string_to_stdfloat(words[0], ip[0]) && string_to_stdfloat(words[1], ip[3]); ip[1] = ip[0]; ip[2] = ip[0]; break; case 1: okflag = string_to_stdfloat(words[0], ip[0]); ip[1] = ip[0]; ip[2] = ip[0]; ip[3] = 1.0; break; } if (!okflag) { nout << "-" << opt << " requires one through four numbers separated by commas.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a string. The data pointer is to a string variable. */ bool ProgramBase:: dispatch_string(const string &, const string &arg, void *var) { string *ip = (string *)var; (*ip) = arg; return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a string. This is different from dispatch_string in * that the parameter may be repeated multiple times, and each time the string * value is appended to a vector. * * The data pointer is to a vector_string variable. */ bool ProgramBase:: dispatch_vector_string(const string &, const string &arg, void *var) { vector_string *ip = (vector_string *)var; (*ip).push_back(arg); return true; } /** * Similar to dispatch_vector_string, but a comma is allowed to separate * multiple tokens in one argument, without having to repeat the argument for * each token. * * The data pointer is to a vector_string variable. */ bool ProgramBase:: dispatch_vector_string_comma(const string &, const string &arg, void *var) { vector_string *ip = (vector_string *)var; vector_string words; tokenize(arg, words, ","); vector_string::const_iterator wi; for (wi = words.begin(); wi != words.end(); ++wi) { (*ip).push_back(*wi); } return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a filename. The data pointer is to a Filename * variable. */ bool ProgramBase:: dispatch_filename(const string &opt, const string &arg, void *var) { if (arg.empty()) { nout << "-" << opt << " requires a filename parameter.\n"; return false; } Filename *ip = (Filename *)var; (*ip) = Filename::from_os_specific(arg); return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a single directory name to add to a search path. The * data pointer is to a DSearchPath variable. This kind of option may appear * multiple times on the command line; each time, the new directory is * appended. */ bool ProgramBase:: dispatch_search_path(const string &opt, const string &arg, void *var) { if (arg.empty()) { nout << "-" << opt << " requires a search path parameter.\n"; return false; } DSearchPath *ip = (DSearchPath *)var; ip->append_directory(Filename::from_os_specific(arg)); return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a coordinate system string. The data pointer is to a * CoordinateSystem variable. */ bool ProgramBase:: dispatch_coordinate_system(const string &opt, const string &arg, void *var) { CoordinateSystem *ip = (CoordinateSystem *)var; (*ip) = parse_coordinate_system_string(arg); if ((*ip) == CS_invalid) { nout << "Invalid coordinate system for -" << opt << ": " << arg << "\n" << "Valid coordinate system strings are any of 'y-up', 'z-up', " "'y-up-left', or 'z-up-left'.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a unit of distance measurement. The data pointer is * to a DistanceUnit variable. */ bool ProgramBase:: dispatch_units(const string &opt, const string &arg, void *var) { DistanceUnit *ip = (DistanceUnit *)var; (*ip) = string_distance_unit(arg); if ((*ip) == DU_invalid) { nout << "Invalid units for -" << opt << ": " << arg << "\n" << "Valid units are mm, cm, m, km, yd, ft, in, nmi, and mi.\n"; return false; } return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to indicate an image file type, like rgb, bmp, jpg, etc. The data pointer * is to a PNMFileType pointer. */ bool ProgramBase:: dispatch_image_type(const string &opt, const string &arg, void *var) { PNMFileType **ip = (PNMFileType **)var; PNMFileTypeRegistry *reg = PNMFileTypeRegistry::get_global_ptr(); (*ip) = reg->get_type_from_extension(arg); if ((*ip) == nullptr) { nout << "Invalid image type for -" << opt << ": " << arg << "\n" << "The following image types are known:\n"; reg->write(nout, 2); return false; } return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a single component of a path replace request. The * data pointer is to a PathReplace variable. */ bool ProgramBase:: dispatch_path_replace(const string &opt, const string &arg, void *var) { PathReplace *ip = (PathReplace *)var; size_t equals = arg.find('='); if (equals == string::npos) { nout << "Invalid path replacement string for -" << opt << ": " << arg << "\n" << "String should be of the form 'old-prefix=new-prefix'.\n"; return false; } ip->add_pattern(arg.substr(0, equals), arg.substr(equals + 1)); return true; } /** * Standard dispatch function for an option that takes one parameter, which is * to be interpreted as a path store string. The data pointer is to a * PathStore variable. */ bool ProgramBase:: dispatch_path_store(const string &opt, const string &arg, void *var) { PathStore *ip = (PathStore *)var; (*ip) = string_path_store(arg); if ((*ip) == PS_invalid) { nout << "Invalid path store for -" << opt << ": " << arg << "\n" << "Valid path store strings are any of 'rel', 'abs', " << "'rel_abs', 'strip', or 'keep'.\n"; return false; } return true; } /** * Called when the user enters '-h', this describes how to use the program and * then exits. */ bool ProgramBase:: handle_help_option(const string &, const string &, void *data) { ProgramBase *me = (ProgramBase *)data; me->show_description(); me->show_usage(); me->show_options(); exit(0); return false; } /** * Word-wraps the indicated text to the indicated output stream. The first * line is prefixed with the indicated prefix, then tabbed over to * indent_width where the text actually begins. A newline is inserted at or * before column line_width. Each subsequent line begins with indent_width * spaces. * * An embedded newline character ('\n') forces a line break, while an embedded * carriage-return character ('\r'), or two or more consecutive newlines, * marks a paragraph break, which is usually printed as a blank line. * Redundant newline and carriage-return characters are generally ignored. * * The flag last_newline should be initialized to false for the first call to * format_text, and then preserved for future calls; it tracks the state of * trailing newline characters between calls so we can correctly identify * doubled newlines. */ void ProgramBase:: format_text(std::ostream &out, bool &last_newline, const string &prefix, int indent_width, const string &text, int line_width) { indent_width = min(indent_width, line_width - 20); int indent_amount = indent_width; bool initial_break = false; if (!prefix.empty()) { out << prefix; indent_amount = indent_width - prefix.length(); if ((int)prefix.length() + 1 > indent_width) { out << "\n"; initial_break = true; indent_amount = indent_width; } } size_t p = 0; // Skip any initial whitespace and newlines. while (p < text.length() && isspace(text[p])) { if (text[p] == '\r' || (p > 0 && text[p] == '\n' && text[p - 1] == '\n') || (p == 0 && text[p] == '\n' && last_newline)) { if (!initial_break) { // Here's an initial paragraph break, however. out << "\n"; initial_break = true; } indent_amount = indent_width; } else if (text[p] == '\n') { // Largely ignore an initial newline. indent_amount = indent_width; } else if (text[p] == ' ') { // Do count up leading spaces. indent_amount++; } p++; } last_newline = (!text.empty() && text[text.length() - 1] == '\n'); while (p < text.length()) { // Look for the paragraph or line break--the next newline character, if // any. size_t par = text.find_first_of("\n\r", p); bool is_paragraph_break = false; if (par == string::npos) { par = text.length(); /* This shouldn't be necessary. } else { is_paragraph_break = (text[par] == '\r'); */ } indent(out, indent_amount); size_t eol = p + (line_width - indent_width); if (eol >= par) { // The rest of the paragraph fits completely on the line. eol = par; } else { // The paragraph doesn't fit completely on the line. Determine the best // place to break the line. Look for the last space before the ideal // eol. size_t min_eol = max((int)p, (int)eol - 25); size_t q = eol; while (q > min_eol && !isspace(text[q])) { q--; } // Now roll back to the last non-space before this one. while (q > min_eol && isspace(text[q])) { q--; } if (q != min_eol) { // Here's a good place to stop! eol = q + 1; } else { // The line cannot be broken cleanly. Just let it keep going; don't // try to wrap it. eol = par; } } out << text.substr(p, eol - p) << "\n"; p = eol; // Skip additional whitespace between the lines. while (p < text.length() && isspace(text[p])) { if (text[p] == '\r' || (p > 0 && text[p] == '\n' && text[p - 1] == '\n')) { is_paragraph_break = true; } p++; } if (eol == par && is_paragraph_break) { // Print the paragraph break as a blank line. out << "\n"; if (p >= text.length()) { // If we end on a paragraph break, don't try to insert a new one in // the next pass. last_newline = false; } } indent_amount = indent_width; } } /** * Puts all the options in order by index number (e.g. in the order they were * added, within index_groups), for output by show_options(). */ void ProgramBase:: sort_options() { if (!_sorted_options) { _options_by_index.clear(); OptionsByName::const_iterator oi; for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) { _options_by_index.push_back(&(*oi).second); } sort(_options_by_index.begin(), _options_by_index.end(), SortOptionsByIndex()); _sorted_options = true; } } /** * Attempts to determine the ideal terminal width for formatting output. */ void ProgramBase:: get_terminal_width() { if (!_got_terminal_width) { _got_terminal_width = true; _got_option_indent = false; #ifdef IOCTL_TERMINAL_WIDTH if (use_terminal_width) { struct winsize size; int result = ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&size); if (result < 0 || size.ws_col < 10) { // Couldn't determine the width for some reason. Instead of // complaining, just punt. _terminal_width = default_terminal_width; } else { // Subtract 10% for the comfort margin at the edge. _terminal_width = size.ws_col - min(8, (int)(size.ws_col * 0.1)); } return; } #endif // IOCTL_TERMINAL_WIDTH _terminal_width = default_terminal_width; } }