panda3d/pandatool/src/progbase/programBase.cxx
2013-01-05 16:06:22 +00:00

1467 lines
50 KiB
C++

// 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 "preprocess_argv.h"
#include <stdlib.h>
#include <algorithm>
#include <ctype.h>
// 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 <termios.h>
#ifndef TIOCGWINSZ
#include <sys/ioctl.h>
#elif __APPLE__
#include <sys/ioctl.h>
#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) {
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]);
}
// Build up the long options list and the short options string for
// getopt_long_only().
pvector<struct option> 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<int, const Option *> 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));
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));
}
////////////////////////////////////////////////////////////////////
// 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_int_quad
// Access: Protected, Static
// Description: 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;
}
////////////////////////////////////////////////////////////////////
// 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 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;
}
////////////////////////////////////////////////////////////////////
// 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;
}
}