mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-05 11:28:17 -04:00
1467 lines
50 KiB
C++
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;
|
|
}
|
|
}
|