// Filename: programBase.cxx // Created by: drose (13Feb00) // //////////////////////////////////////////////////////////////////// // // 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." // //////////////////////////////////////////////////////////////////// #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 #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 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 << 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.")); //////////////////////////////////////////////////////////////////// // Function: ProgramBase::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// ProgramBase:: ProgramBase() { // 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, NULL, (void *)this); // It's nice to start with a blank line. nout << "\r"; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::Destructor // Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// ProgramBase:: ~ProgramBase() { // Reset Notify in case any messages get sent after our // destruction--our stream is no longer valid. Notify::ptr()->set_ostream_ptr(NULL, false); } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::show_description // Access: Public // Description: Writes the program description to stderr. //////////////////////////////////////////////////////////////////// void ProgramBase:: show_description() { nout << _description << "\n"; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::show_usage // Access: Public // Description: 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"; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::show_options // Access: Public // Description: 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"); } } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::show_text // Access: Public // Description: 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); } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::parse_command_line // Access: Public, Virtual // Description: 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[]) { // 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]); } // 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 = (int *)NULL; // 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, NULL); while (flag != EOF) { string arg; if (optarg != NULL) { 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)NULL) { okflag = (*opt._option_function)(opt._option, arg, opt._option_data); } if (opt._option_method != (OptionDispatchMethod)NULL) { okflag = (*opt._option_method)(this, opt._option, arg, opt._option_data); } if (opt._bool_var != (bool *)NULL) { (*opt._bool_var) = true; } if (!okflag) { show_usage(); exit(1); } } } flag = getopt_long_only(argc, argv, short_options.c_str(), long_opts, NULL); } if (!handle_args(remaining_args)) { show_usage(); exit(1); } if (!post_command_line()) { show_usage(); exit(1); } } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::get_exec_command // Access: Public // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::handle_args // Access: Protected, Virtual // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::post_command_line // Access: Protected, Virtual // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::set_program_description // Access: Protected // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::clear_runlines // Access: Protected // Description: Removes all of the runlines that were previously // added, presumably before adding some new ones. //////////////////////////////////////////////////////////////////// void ProgramBase:: clear_runlines() { _runlines.clear(); } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::add_runline // Access: Protected // Description: 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); } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::clear_options // Access: Protected // Description: 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(); } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::add_option // Access: Protected // Description: 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)NULL; opt._bool_var = bool_var; opt._option_data = option_data; _options_by_name[option] = opt; _sorted_options = false; if (bool_var != (bool *)NULL) { (*bool_var) = false; } } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::add_option // Access: Protected // Description: 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)NULL; 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 != (bool *)NULL) { (*bool_var) = false; } } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::redescribe_option // Access: Protected // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::remove_option // Access: Protected // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::add_path_replace_options // Access: Public // Description: 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, NULL, _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, NULL, &(_path_replace->_path)); } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::add_path_store_options // Access: Public // Description: 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)); } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_none // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_true // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_false // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_count // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_int // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_int_pair // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_double // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_double_pair // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_double_triple // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_double_quad // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_color // Access: Protected, Static // Description: 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 Colorf. //////////////////////////////////////////////////////////////////// bool ProgramBase:: dispatch_color(const string &opt, const string &arg, void *var) { float *ip = (float *)var; vector_string words; tokenize(arg, words, ","); bool okflag = false; switch (words.size()) { case 4: okflag = string_to_float(words[0], ip[0]) && string_to_float(words[1], ip[1]) && string_to_float(words[2], ip[2]) && string_to_float(words[3], ip[3]); break; case 3: okflag = string_to_float(words[0], ip[0]) && string_to_float(words[1], ip[1]) && string_to_float(words[2], ip[2]); ip[3] = 1.0; break; case 2: okflag = string_to_float(words[0], ip[0]) && string_to_float(words[1], ip[3]); ip[1] = ip[0]; ip[2] = ip[0]; break; case 1: okflag = string_to_float(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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_string // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_vector_string // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_vector_string_comma // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_filename // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_search_path // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_coordinate_system // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_units // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_image_type // Access: Protected, Static // Description: 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) == (PNMFileType *)NULL) { nout << "Invalid image type for -" << opt << ": " << arg << "\n" << "The following image types are known:\n"; reg->write(nout, 2); return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_path_replace // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::dispatch_path_store // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::handle_help_option // Access: Protected, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::format_text // Access: Protected, Static // Description: 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(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; } } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::sort_options // Access: Private // Description: 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; } } //////////////////////////////////////////////////////////////////// // Function: ProgramBase::get_terminal_width // Access: Private // Description: 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; } }