1
0
Fork 0

Restructure option/config handling code, to avoid multiple scans & parses for special options (fg-root/fg-aircraft/aircraft). Push most of the code into a new Options class, inside options.cxx, and clean up various call-sites as a result.

This commit is contained in:
James Turner 2011-10-16 18:35:40 +01:00
parent 3d544fbc1f
commit b1c7495fec
9 changed files with 845 additions and 767 deletions

View file

@ -38,6 +38,10 @@
# include <fpu_control.h>
#endif
#ifndef _WIN32
# include <unistd.h> // for gethostname()
#endif
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
@ -61,14 +65,8 @@ using std::endl;
#include "fg_os.hxx"
#ifdef _MSC_VER
char homepath[256] = "";
char * homedir = homepath;
char *hostname = ::getenv( "COMPUTERNAME" );
#else
char *homedir = ::getenv( "HOME" );
char *hostname = ::getenv( "HOSTNAME" );
#endif
char *homedir = NULL;
char *hostname = NULL;
bool free_hostname = false;
// foreward declaration.
@ -179,9 +177,23 @@ int main ( int argc, char **argv ) {
// Windows has no $HOME aka %HOME%, so we have to construct the full path.
// make sure it fits into the buffer. Max. path length is 255, but who knows
// what's in these environment variables?
char homepath[256] = "";
homepath[sizeof(homepath)-1] = 0;
strncpy( homepath, ::getenv("APPDATA"), sizeof(homepath)-1 );
strncat( homepath, "\\flightgear.org", sizeof(homepath)-strlen(homepath)-1 );
homedir = strdup(homepath);
hostname = ::getenv( "COMPUTERNAME" );
#else
// Unix(alike) systems
char _hostname[256];
gethostname(_hostname, 256);
hostname = strdup(_hostname);
free_hostname = true;
homedir = ::getenv( "HOME" );
signal(SIGPIPE, SIG_IGN);
#endif
#ifdef PTW32_STATIC_LIB
@ -203,9 +215,6 @@ int main ( int argc, char **argv ) {
}
initFPE();
#endif
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
#if defined(sgi)
flush_fpe();
@ -230,6 +239,7 @@ int main ( int argc, char **argv ) {
#if defined( HAVE_BC5PLUS )
_control87(MCW_EM, MCW_EM); /* defined in float.h */
#endif
bool fgviewer = false;
for (int i = 0; i < argc; ++i) {
if (!strcmp("--fgviewer", argv[i])) {

View file

@ -121,225 +121,9 @@
#include "main.hxx"
#ifdef __APPLE__
# include <CoreFoundation/CoreFoundation.h>
#endif
using std::string;
using namespace boost::algorithm;
extern const char *default_root;
// Scan the command line options for the specified option and return
// the value.
static string fgScanForOption( const string& option, int argc, char **argv ) {
int i = 1;
if (hostname == NULL)
{
char _hostname[256];
if( gethostname(_hostname, 256) >= 0 ) {
hostname = strdup(_hostname);
free_hostname = true;
}
}
SG_LOG(SG_GENERAL, SG_INFO, "Scanning command line for: " << option );
int len = option.length();
while ( i < argc ) {
SG_LOG( SG_GENERAL, SG_DEBUG, "argv[" << i << "] = " << argv[i] );
string arg = argv[i];
if ( arg.find( option ) == 0 ) {
return arg.substr( len );
}
i++;
}
return "";
}
// Scan the user config files for the specified option and return
// the value.
static string fgScanForOption( const string& option, const string& path ) {
sg_gzifstream in( path );
if ( !in.is_open() ) {
return "";
}
SG_LOG( SG_GENERAL, SG_INFO, "Scanning " << path << " for: " << option );
int len = option.length();
in >> skipcomment;
while ( ! in.eof() ) {
string line;
getline( in, line, '\n' );
// catch extraneous (DOS) line ending character
if ( line[line.length() - 1] < 32 ) {
line = line.substr( 0, line.length()-1 );
}
if ( line.find( option ) == 0 ) {
return line.substr( len );
}
in >> skipcomment;
}
return "";
}
// Scan the user config files for the specified option and return
// the value.
static string fgScanForOption( const string& option ) {
string arg("");
#if defined( unix ) || defined( __CYGWIN__ ) || defined(_MSC_VER)
// Next check home directory for .fgfsrc.hostname file
if ( arg.empty() ) {
if ( homedir != NULL && hostname != NULL && strlen(hostname) > 0) {
SGPath config( homedir );
config.append( ".fgfsrc" );
config.concat( "." );
config.concat( hostname );
arg = fgScanForOption( option, config.str() );
}
}
#endif
// Next check home directory for .fgfsrc file
if ( arg.empty() ) {
if ( homedir != NULL ) {
SGPath config( homedir );
config.append( ".fgfsrc" );
arg = fgScanForOption( option, config.str() );
}
}
if ( arg.empty() ) {
// Check for $fg_root/system.fgfsrc
SGPath config( globals->get_fg_root() );
config.append( "system.fgfsrc" );
arg = fgScanForOption( option, config.str() );
}
return arg;
}
// Read in configuration (files and command line options) but only set
// fg_root and aircraft_paths, which are needed *before* do_options() is called
// in fgInitConfig
bool fgInitFGRoot ( int argc, char **argv ) {
string root;
// First parse command line options looking for --fg-root=, this
// will override anything specified in a config file
root = fgScanForOption( "--fg-root=", argc, argv);
// Check in one of the user configuration files.
if (root.empty() )
root = fgScanForOption( "--fg-root=" );
// Next check if fg-root is set as an env variable
if ( root.empty() ) {
char *envp = ::getenv( "FG_ROOT" );
if ( envp != NULL ) {
root = envp;
}
}
// Otherwise, default to a random compiled-in location if we can't
// find fg-root any other way.
if ( root.empty() ) {
#if defined( __CYGWIN__ )
root = "../data";
#elif defined( _WIN32 )
root = "..\\data";
#elif defined(__APPLE__)
/*
The following code looks for the base package inside the application
bundle, in the standard Contents/Resources location.
*/
CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
// look for a 'data' subdir
CFURLRef dataDir = CFURLCreateCopyAppendingPathComponent(NULL, resourcesUrl, CFSTR("data"), true);
// now convert down to a path, and the a c-string
CFStringRef path = CFURLCopyFileSystemPath(dataDir, kCFURLPOSIXPathStyle);
root = CFStringGetCStringPtr(path, CFStringGetSystemEncoding());
CFRelease(resourcesUrl);
CFRelease(dataDir);
CFRelease(path);
#else
root = PKGLIBDIR;
#endif
}
SG_LOG(SG_INPUT, SG_INFO, "fg_root = " << root );
globals->set_fg_root(root);
return true;
}
// Read in configuration (files and command line options) but only set
// aircraft
bool fgInitFGAircraft ( int argc, char **argv ) {
string aircraftDir = fgScanForOption("--fg-aircraft=", argc, argv);
if (aircraftDir.empty()) {
aircraftDir = fgScanForOption("--fg-aircraft=");
}
const char* envp = ::getenv("FG_AIRCRAFT");
if (aircraftDir.empty() && envp) {
globals->append_aircraft_paths(envp);
}
if (!aircraftDir.empty()) {
globals->append_aircraft_paths(aircraftDir);
}
string aircraft;
// First parse command line options looking for --aircraft=, this
// will override anything specified in a config file
aircraft = fgScanForOption( "--aircraft=", argc, argv );
if ( aircraft.empty() ) {
// check synonym option
aircraft = fgScanForOption( "--vehicle=", argc, argv );
}
// Check in one of the user configuration files.
if ( aircraft.empty() ) {
aircraft = fgScanForOption( "--aircraft=" );
}
if ( aircraft.empty() ) {
aircraft = fgScanForOption( "--vehicle=" );
}
// if an aircraft was specified, set the property name
if ( !aircraft.empty() ) {
SG_LOG(SG_INPUT, SG_INFO, "aircraft = " << aircraft );
fgSetString("/sim/aircraft", aircraft.c_str() );
} else {
SG_LOG(SG_INPUT, SG_INFO, "No user specified aircraft, using default" );
}
return true;
}
// Return the current base package version
string fgBasePackageVersion() {
@ -477,45 +261,6 @@ bool fgDetectLanguage() {
return true;
}
// Attempt to locate and parse the various non-XML config files in order
// from least precidence to greatest precidence
static void
do_options (int argc, char ** argv)
{
// Check for $fg_root/system.fgfsrc
SGPath config( globals->get_fg_root() );
config.append( "system.fgfsrc" );
fgParseOptions(config.str());
#if defined( unix ) || defined( __CYGWIN__ ) || defined(_MSC_VER)
if( hostname != NULL && strlen(hostname) > 0 ) {
config.concat( "." );
config.concat( hostname );
fgParseOptions(config.str());
}
#endif
// Check for ~/.fgfsrc
if ( homedir != NULL ) {
config.set( homedir );
config.append( ".fgfsrc" );
fgParseOptions(config.str());
}
#if defined( unix ) || defined( __CYGWIN__ ) || defined(_MSC_VER)
if( hostname != NULL && strlen(hostname) > 0 ) {
// Check for ~/.fgfsrc.hostname
config.concat( "." );
config.concat( hostname );
fgParseOptions(config.str());
}
#endif
// Parse remaining command line options
// These will override anything specified in a config file
fgParseArgs(argc, argv);
}
template <class T>
bool fgFindAircraftInDir(const SGPath& dirPath, T* obj, bool (T::*pred)(const SGPath& p))
{
@ -709,8 +454,7 @@ private:
// Read in configuration (file and command line)
bool fgInitConfig ( int argc, char **argv ) {
// First, set some sane default values
fgSetDefaults();
flightgear::Options::sharedInstance()->init(argc, argv);
// Read global preferences from $FG_ROOT/preferences.xml
SG_LOG(SG_INPUT, SG_INFO, "Reading global preferences");
@ -762,7 +506,8 @@ bool fgInitConfig ( int argc, char **argv ) {
}
// Scan user config files and command line for a specified aircraft.
fgInitFGAircraft(argc, argv);
flightgear::Options::sharedInstance()->initAircraft();
FindAndCacheAircraft f(&autosave);
if (!f.loadAircraft()) {
return false;
@ -772,7 +517,7 @@ bool fgInitConfig ( int argc, char **argv ) {
// parse options after loading aircraft to ensure any user
// overrides of defaults are honored.
do_options(argc, argv);
flightgear::Options::sharedInstance()->processOptions();
return true;
}

View file

@ -32,11 +32,6 @@ class SGPropertyNode;
class SGTime;
class SGPath;
// Read in configuration (files and command line optoins) but only set
// fg_root
bool fgInitFGRoot ( int argc, char **argv );
// Return the current base package version
std::string fgBasePackageVersion();

View file

@ -188,7 +188,6 @@ fgviewerMain(int argc, char** argv)
globals = new FGGlobals;
fgInitFGRoot(arguments.argc(), arguments.argv());
if ( !fgInitConfig(arguments.argc(), arguments.argv()) ) {
SG_LOG( SG_GENERAL, SG_ALERT, "Config option parsing failed ..." );
exit(-1);

View file

@ -24,6 +24,9 @@
# include <config.h>
#endif
#include <boost/foreach.hpp>
#include <algorithm>
#include <simgear/structure/commands.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sg_dir.hxx>
@ -236,26 +239,30 @@ void FGGlobals::set_fg_root (const string &root) {
simgear::ResourceManager::PRIORITY_DEFAULT);
}
void FGGlobals::set_fg_scenery (const string &scenery)
void FGGlobals::append_fg_scenery (const string &paths)
{
SGPath s;
if (scenery.empty()) {
s.set( fg_root );
s.append( "Scenery" );
} else
s.set( scenery );
string_list path_list = sgPathSplit( s.str() );
fg_scenery.clear();
// fg_scenery.clear();
SGPropertyNode* sim = fgGetNode("/sim", true);
for (unsigned i = 0; i < path_list.size(); i++) {
SGPath path(path_list[i]);
// find first unused fg-scenery property in /sim
int propIndex = 0;
while (sim->getChild("fg-scenery", propIndex) != NULL) {
++propIndex;
}
BOOST_FOREACH(const SGPath& path, sgPathSplit( paths )) {
if (!path.exists()) {
SG_LOG(SG_GENERAL, SG_WARN, "scenery path not found:" << path.str());
continue;
}
// check for duplicates
string_list::const_iterator ex = std::find(fg_scenery.begin(), fg_scenery.end(), path.str());
if (ex != fg_scenery.end()) {
SG_LOG(SG_GENERAL, SG_INFO, "skipping duplicate add of scenery path:" << path.str());
continue;
}
simgear::Dir dir(path);
SGPath terrainDir(dir.file("Terrain"));
SGPath objectsDir(dir.file("Objects"));
@ -280,8 +287,7 @@ void FGGlobals::set_fg_scenery (const string &scenery)
fg_scenery.push_back("");
// make scenery dirs available to Nasal
sim->removeChild("fg-scenery", i, false);
SGPropertyNode* n = sim->getChild("fg-scenery", i, true);
SGPropertyNode* n = sim->getChild("fg-scenery", propIndex++, true);
n->setStringValue(path.str());
n->setAttribute(SGPropertyNode::WRITE, false);
} // of path list iteration

View file

@ -194,7 +194,7 @@ public:
void set_fg_root (const std::string &root);
inline const string_list &get_fg_scenery () const { return fg_scenery; }
void set_fg_scenery (const std::string &scenery);
void append_fg_scenery (const std::string &scenery);
const string_list& get_aircraft_paths() const { return fg_aircraft_dirs; }
void append_aircraft_path(const std::string& path);

View file

@ -609,30 +609,6 @@ int fgMainInit( int argc, char **argv ) {
upper_case_property("/sim/tower/airport-id");
upper_case_property("/autopilot/route-manager/input");
// Scan the config file(s) and command line options to see if
// fg_root was specified (ignore all other options for now)
fgInitFGRoot(argc, argv);
// Check for the correct base package version
static char required_version[] = "2.5.0";
string base_version = fgBasePackageVersion();
if ( !(base_version == required_version) ) {
// tell the operator how to use this application
SG_LOG( SG_GENERAL, SG_ALERT, "" ); // To popup the console on windows
cerr << endl << "Base package check failed:" << endl \
<< " Version " << base_version << " found at: " \
<< globals->get_fg_root() << endl \
<< " Version " << required_version << " is required." << endl \
<< "Please upgrade/downgrade base package and set the path to your fgdata" << endl \
<< "with --fg-root=path_to_your_fgdata" << endl;
#ifdef _MSC_VER
cerr << "Hit a key to continue..." << endl;
cin.get();
#endif
exit(-1);
}
// Load the configuration parameters. (Command line options
// override config file options. Config file options override
// defaults.)

View file

@ -31,6 +31,8 @@
#include <simgear/timing/sg_time.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <boost/foreach.hpp>
#include <math.h> // rint()
#include <stdio.h>
#include <stdlib.h> // atof(), atoi()
@ -61,12 +63,6 @@
#include <osg/Version>
using std::string;
using std::sort;
using std::cout;
using std::cerr;
using std::endl;
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
# include <Include/version.h>
# include <simgear/version.h>
@ -74,8 +70,22 @@ using std::endl;
# include <Include/no_version.h>
#endif
#ifdef __APPLE__
# include <CoreFoundation/CoreFoundation.h>
#endif
using std::string;
using std::sort;
using std::cout;
using std::cerr;
using std::endl;
#define NEW_DEFAULT_MODEL_HZ 120
// defined in bootstrap.cxx
extern char *homedir;
extern char *hostname;
enum
{
FG_OPTIONS_OK = 0,
@ -87,6 +97,8 @@ enum
FG_OPTIONS_SHOW_SOUND_DEVICES = 6
};
static flightgear::Options* shared_instance = NULL;
static double
atof( const string& str )
{
@ -108,20 +120,9 @@ static int fgSetupProxy( const char *arg );
* in case, we provide some initial sane values here. This method
* should be invoked *before* reading any init files.
*/
void
static void
fgSetDefaults ()
{
// set a possibly independent location for scenery data
const char *envp = ::getenv( "FG_SCENERY" );
if ( envp != NULL ) {
// fg_root could be anywhere, so default to environmental
// variable $FG_ROOT if it is set.
globals->set_fg_scenery(envp);
} else {
// Otherwise, default to Scenery being in $FG_ROOT/Scenery
globals->set_fg_scenery("");
}
// Position (deliberately out of range)
fgSetDouble("/position/longitude-deg", 9999.0);
@ -249,7 +250,9 @@ fgSetDefaults ()
fgSetString("/sim/version/revision", REVISION);
fgSetInt("/sim/version/build-number", HUDSON_BUILD_NUMBER);
fgSetString("/sim/version/build-id", HUDSON_BUILD_ID);
if( (envp = ::getenv( "http_proxy" )) != NULL )
char* envp = ::getenv( "http_proxy" );
if( envp != NULL )
fgSetupProxy( envp );
}
@ -740,24 +743,10 @@ fgOptRoc( const char *arg )
return FG_OPTIONS_OK;
}
static int
fgOptFgRoot( const char *arg )
{
// this option is dealt with by fgInitFGRoot
return FG_OPTIONS_OK;
}
static int
fgOptFgScenery( const char *arg )
{
globals->set_fg_scenery(arg);
return FG_OPTIONS_OK;
}
static int
fgOptFgAircraft(const char* arg)
{
// this option is dealt with by fgInitFGAircraft
globals->append_fg_scenery(arg);
return FG_OPTIONS_OK;
}
@ -1224,20 +1213,6 @@ fgOptVersion( const char *arg )
return FG_OPTIONS_EXIT;
}
static int
fgOptFpe(const char* arg)
{
// Actually handled in bootstrap.cxx
return FG_OPTIONS_OK;
}
static int
fgOptFgviewer(const char* arg)
{
// Actually handled in bootstrap.cxx
return FG_OPTIONS_OK;
}
static int
fgOptCallSign(const char * arg)
{
@ -1260,7 +1235,54 @@ fgOptCallSign(const char * arg)
}
static map<string,size_t> fgOptionMap;
// Set a property for the --prop: option. Syntax: --prop:[<type>:]<name>=<value>
// <type> can be "double" etc. but also only the first letter "d".
// Examples: --prop:alpha=1 --prop:bool:beta=true --prop:d:gamma=0.123
static int
fgOptSetProperty(const char* raw)
{
string arg(raw);
string::size_type pos = arg.find('=');
if (pos == arg.npos || pos == 0 || pos + 1 == arg.size())
return FG_OPTIONS_ERROR;
string name = arg.substr(0, pos);
string value = arg.substr(pos + 1);
string type;
pos = name.find(':');
if (pos != name.npos && pos != 0 && pos + 1 != name.size()) {
type = name.substr(0, pos);
name = name.substr(pos + 1);
}
SGPropertyNode *n = fgGetNode(name.c_str(), true);
bool writable = n->getAttribute(SGPropertyNode::WRITE);
if (!writable)
n->setAttribute(SGPropertyNode::WRITE, true);
bool ret = false;
if (type.empty())
ret = n->setUnspecifiedValue(value.c_str());
else if (type == "s" || type == "string")
ret = n->setStringValue(value.c_str());
else if (type == "d" || type == "double")
ret = n->setDoubleValue(strtod(value.c_str(), 0));
else if (type == "f" || type == "float")
ret = n->setFloatValue(atof(value.c_str()));
else if (type == "l" || type == "long")
ret = n->setLongValue(strtol(value.c_str(), 0, 0));
else if (type == "i" || type == "int")
ret = n->setIntValue(atoi(value.c_str()));
else if (type == "b" || type == "bool")
ret = n->setBoolValue(value == "true" || atoi(value.c_str()) != 0);
if (!writable)
n->setAttribute(SGPropertyNode::WRITE, false);
return ret ? FG_OPTIONS_OK : FG_OPTIONS_ERROR;
}
/*
option has_param type property b_param s_param func
@ -1289,11 +1311,13 @@ where:
argument.
*/
enum OptionType { OPTION_BOOL, OPTION_STRING, OPTION_DOUBLE, OPTION_INT, OPTION_CHANNEL, OPTION_FUNC };
enum OptionType { OPTION_BOOL = 0, OPTION_STRING, OPTION_DOUBLE, OPTION_INT, OPTION_CHANNEL, OPTION_FUNC, OPTION_IGNORE };
const int OPTION_MULTI = 1 << 17;
struct OptionDesc {
const char *option;
bool has_param;
enum OptionType type;
int type;
const char *property;
bool b_param;
const char *s_param;
@ -1366,9 +1390,9 @@ struct OptionDesc {
{"pitch", true, OPTION_DOUBLE, "/sim/presets/pitch-deg", false, "", 0 },
{"glideslope", true, OPTION_DOUBLE, "/sim/presets/glideslope-deg", false, "", 0 },
{"roc", true, OPTION_FUNC, "", false, "", fgOptRoc },
{"fg-root", true, OPTION_FUNC, "", false, "", fgOptFgRoot },
{"fg-scenery", true, OPTION_FUNC, "", false, "", fgOptFgScenery },
{"fg-aircraft", true, OPTION_FUNC, "", false, "", fgOptFgAircraft },
{"fg-root", true, OPTION_IGNORE, "", false, "", 0 },
{"fg-scenery", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptFgScenery },
{"fg-aircraft", true, OPTION_IGNORE | OPTION_MULTI, "", false, "", 0 },
{"fdm", true, OPTION_STRING, "/sim/flight-model", false, "", 0 },
{"aero", true, OPTION_STRING, "/sim/aero", false, "", 0 },
{"aircraft-dir", true, OPTION_STRING, "/sim/aircraft-dir", false, "", 0 },
@ -1460,19 +1484,19 @@ struct OptionDesc {
{"trace-read", true, OPTION_FUNC, "", false, "", fgOptTraceRead },
{"trace-write", true, OPTION_FUNC, "", false, "", fgOptTraceWrite },
{"log-level", true, OPTION_FUNC, "", false, "", fgOptLogLevel },
{"view-offset", true, OPTION_FUNC, "", false, "", fgOptViewOffset },
{"view-offset", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptViewOffset },
{"visibility", true, OPTION_FUNC, "", false, "", fgOptVisibilityMeters },
{"visibility-miles", true, OPTION_FUNC, "", false, "", fgOptVisibilityMiles },
{"random-wind", false, OPTION_FUNC, "", false, "", fgOptRandomWind },
{"wind", true, OPTION_FUNC, "", false, "", fgOptWind },
{"wind", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptWind },
{"turbulence", true, OPTION_FUNC, "", false, "", fgOptTurbulence },
{"ceiling", true, OPTION_FUNC, "", false, "", fgOptCeiling },
{"wp", true, OPTION_FUNC, "", false, "", fgOptWp },
{"wp", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptWp },
{"flight-plan", true, OPTION_STRING, "/autopilot/route-manager/file-path", false, "", NULL },
{"config", true, OPTION_FUNC, "", false, "", fgOptConfig },
{"config", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptConfig },
{"aircraft", true, OPTION_STRING, "/sim/aircraft", false, "", 0 },
{"vehicle", true, OPTION_STRING, "/sim/aircraft", false, "", 0 },
{"failure", true, OPTION_FUNC, "", false, "", fgOptFailure },
{"failure", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptFailure },
{"com1", true, OPTION_DOUBLE, "/instrumentation/comm[0]/frequencies/selected-mhz", false, "", 0 },
{"com2", true, OPTION_DOUBLE, "/instrumentation/comm[1]/frequencies/selected-mhz", false, "", 0 },
{"nav1", true, OPTION_FUNC, "", false, "", fgOptNAV1 },
@ -1483,214 +1507,273 @@ struct OptionDesc {
{"dme", true, OPTION_FUNC, "", false, "", fgOptDME },
{"min-status", true, OPTION_STRING, "/sim/aircraft-min-status", false, "all", 0 },
{"livery", true, OPTION_FUNC, "", false, "", fgOptLivery },
{"ai-scenario", true, OPTION_FUNC, "", false, "", fgOptScenario },
{"ai-scenario", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptScenario },
{"disable-ai-scenarios", false, OPTION_FUNC, "", false, "", fgOptNoScenarios},
{"parking-id", true, OPTION_FUNC, "", false, "", fgOptParking },
{"version", false, OPTION_FUNC, "", false, "", fgOptVersion },
{"enable-fpe", false, OPTION_FUNC, "", false, "", fgOptFpe},
{"fgviewer", false, OPTION_FUNC, "", false, "", fgOptFgviewer},
{"enable-fpe", false, OPTION_IGNORE, "", false, "", 0},
{"fgviewer", false, OPTION_IGNORE, "", false, "", 0},
{"prop", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptSetProperty},
{0}
};
// Set a property for the --prop: option. Syntax: --prop:[<type>:]<name>=<value>
// <type> can be "double" etc. but also only the first letter "d".
// Examples: --prop:alpha=1 --prop:bool:beta=true --prop:d:gamma=0.123
static bool
set_property(const string& arg)
namespace flightgear
{
string::size_type pos = arg.find('=');
if (pos == arg.npos || pos == 0 || pos + 1 == arg.size())
return false;
string name = arg.substr(0, pos);
string value = arg.substr(pos + 1);
string type;
pos = name.find(':');
if (pos != name.npos && pos != 0 && pos + 1 != name.size()) {
type = name.substr(0, pos);
name = name.substr(pos + 1);
}
SGPropertyNode *n = fgGetNode(name.c_str(), true);
bool writable = n->getAttribute(SGPropertyNode::WRITE);
if (!writable)
n->setAttribute(SGPropertyNode::WRITE, true);
bool ret = false;
if (type.empty())
ret = n->setUnspecifiedValue(value.c_str());
else if (type == "s" || type == "string")
ret = n->setStringValue(value.c_str());
else if (type == "d" || type == "double")
ret = n->setDoubleValue(strtod(value.c_str(), 0));
else if (type == "f" || type == "float")
ret = n->setFloatValue(atof(value.c_str()));
else if (type == "l" || type == "long")
ret = n->setLongValue(strtol(value.c_str(), 0, 0));
else if (type == "i" || type == "int")
ret = n->setIntValue(atoi(value.c_str()));
else if (type == "b" || type == "bool")
ret = n->setBoolValue(value == "true" || atoi(value.c_str()) != 0);
if (!writable)
n->setAttribute(SGPropertyNode::WRITE, false);
return ret;
}
// Parse a single option
static int
parse_option (const string& arg)
/**
* internal storage of a value->option binding
*/
class OptionValue
{
if ( fgOptionMap.size() == 0 ) {
size_t i = 0;
OptionDesc *pt = &fgOptionArray[ 0 ];
while ( pt->option != 0 ) {
fgOptionMap[ pt->option ] = i;
i += 1;
pt += 1;
public:
OptionValue(OptionDesc* d, const string& v) :
desc(d), value(v)
{;}
OptionDesc* desc;
string value;
};
typedef std::vector<OptionValue> OptionValueVec;
typedef std::map<string, OptionDesc*> OptionDescDict;
class Options::OptionsPrivate
{
public:
OptionValueVec::const_iterator findValue(const string& key) const
{
OptionValueVec::const_iterator it = values.begin();
for (; it != values.end(); ++it) {
if (it->desc->option == key) {
return it;
}
} // of set values iteration
return it; // not found
}
// General Options
if ( (arg == "--help") || (arg == "-h") ) {
// help/usage request
return(FG_OPTIONS_HELP);
} else if ( (arg == "--verbose") || (arg == "-v") ) {
// verbose help/usage request
return(FG_OPTIONS_VERBOSE_HELP);
} else if ( arg.find( "--show-aircraft") == 0) {
return(FG_OPTIONS_SHOW_AIRCRAFT);
} else if ( arg.find( "--show-sound-devices") == 0) {
return(FG_OPTIONS_SHOW_SOUND_DEVICES);
} else if ( arg.find( "--prop:" ) == 0 ) {
if (!set_property(arg.substr(7))) {
SG_LOG( SG_GENERAL, SG_ALERT, "Bad property assignment: " << arg );
return FG_OPTIONS_ERROR;
OptionDesc* findOption(const string& key) const
{
OptionDescDict::const_iterator it = options.find(key);
if (it == options.end()) {
return NULL;
}
} else if ( arg.find("-psn_") == 0) {
// on Mac, when launched from the GUI, we are passed the ProcessSerialNumber
// as an argument (and no others). Silently ignore the argument here.
return FG_OPTIONS_OK;
} else if ( arg.find( "--" ) == 0 ) {
size_t pos = arg.find( '=' );
string arg_name, arg_value;
if ( pos == string::npos ) {
arg_name = arg.substr( 2 );
} else {
arg_name = arg.substr( 2, pos - 2 );
arg_value = arg.substr( pos + 1);
return it->second;
}
map<string,size_t>::iterator it = fgOptionMap.find( arg_name );
if ( it != fgOptionMap.end() ) {
OptionDesc *pt = &fgOptionArray[ it->second ];
switch ( pt->type ) {
int processOption(OptionDesc* desc, const string& arg_value)
{
switch ( desc->type & 0xffff ) {
case OPTION_BOOL:
fgSetBool( pt->property, pt->b_param );
fgSetBool( desc->property, desc->b_param );
break;
case OPTION_STRING:
if ( pt->has_param && !arg_value.empty() ) {
fgSetString( pt->property, arg_value.c_str() );
} else if ( !pt->has_param && arg_value.empty() ) {
fgSetString( pt->property, pt->s_param );
} else if ( pt->has_param ) {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
if ( desc->has_param && !arg_value.empty() ) {
fgSetString( desc->property, arg_value.c_str() );
} else if ( !desc->has_param && arg_value.empty() ) {
fgSetString( desc->property, desc->s_param );
} else if ( desc->has_param ) {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
return FG_OPTIONS_ERROR;
} else {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' does not have a parameter" );
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
return FG_OPTIONS_ERROR;
}
break;
case OPTION_DOUBLE:
if ( !arg_value.empty() ) {
fgSetDouble( pt->property, atof( arg_value ) );
fgSetDouble( desc->property, atof( arg_value ) );
} else {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
return FG_OPTIONS_ERROR;
}
break;
case OPTION_INT:
if ( !arg_value.empty() ) {
fgSetInt( pt->property, atoi( arg_value ) );
fgSetInt( desc->property, atoi( arg_value ) );
} else {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
return FG_OPTIONS_ERROR;
}
break;
case OPTION_CHANNEL:
// XXX return value of add_channel should be checked?
if ( pt->has_param && !arg_value.empty() ) {
add_channel( pt->option, arg_value );
} else if ( !pt->has_param && arg_value.empty() ) {
add_channel( pt->option, pt->s_param );
} else if ( pt->has_param ) {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
if ( desc->has_param && !arg_value.empty() ) {
add_channel( desc->option, arg_value );
} else if ( !desc->has_param && arg_value.empty() ) {
add_channel( desc->option, desc->s_param );
} else if ( desc->has_param ) {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
return FG_OPTIONS_ERROR;
} else {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' does not have a parameter" );
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
return FG_OPTIONS_ERROR;
}
break;
case OPTION_FUNC:
if ( pt->has_param && !arg_value.empty() ) {
return pt->func( arg_value.c_str() );
} else if ( !pt->has_param && arg_value.empty() ) {
return pt->func( pt->s_param );
} else if ( pt->has_param ) {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" );
if ( desc->has_param && !arg_value.empty() ) {
return desc->func( arg_value.c_str() );
} else if ( !desc->has_param && arg_value.empty() ) {
return desc->func( desc->s_param );
} else if ( desc->has_param ) {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
return FG_OPTIONS_ERROR;
} else {
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' does not have a parameter" );
SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
return FG_OPTIONS_ERROR;
}
break;
}
} else {
SG_LOG( SG_GENERAL, SG_ALERT, "Unknown option '" << arg << "'" );
return FG_OPTIONS_ERROR;
}
} else {
SG_LOG( SG_GENERAL, SG_ALERT, "Unknown option '" << arg << "'" );
return FG_OPTIONS_ERROR;
case OPTION_IGNORE:
break;
}
return FG_OPTIONS_OK;
}
bool showHelp,
verbose,
showAircraft;
OptionDescDict options;
OptionValueVec values;
simgear::PathList propertyFiles;
};
// Parse the command line options
void
fgParseArgs (int argc, char **argv)
Options* Options::sharedInstance()
{
bool in_options = true;
bool verbose = false;
bool help = false;
if (shared_instance == NULL) {
shared_instance = new Options;
}
SG_LOG(SG_GENERAL, SG_ALERT, "Processing command line arguments");
return shared_instance;
}
for (int i = 1; i < argc; i++) {
string arg = argv[i];
Options::Options() :
p(new OptionsPrivate())
{
p->showHelp = false;
p->verbose = false;
p->showAircraft = false;
if (in_options && (arg.find('-') == 0)) {
if (arg == "--") {
in_options = false;
// build option map
OptionDesc *desc = &fgOptionArray[ 0 ];
while ( desc->option != 0 ) {
p->options[ desc->option ] = desc++;
}
}
Options::~Options()
{
}
void Options::init(int argc, char **argv)
{
fgSetDefaults();
// first, process the command line
bool inOptions = true;
for (int i=1; i<argc; ++i) {
if (inOptions && (argv[i][0] == '-')) {
if (strcmp(argv[i], "--") == 0) { // end of options delimiter
inOptions = true;
continue;
}
int result = parseOption(argv[i]);
processArgResult(result);
} else {
int result = parse_option(arg);
if ((result == FG_OPTIONS_HELP) || (result == FG_OPTIONS_ERROR))
help = true;
// XML properties file
SGPath f(argv[i]);
if (!f.exists()) {
SG_LOG(SG_GENERAL, SG_ALERT, "config file not found:" << f.str());
return;
}
else if (result == FG_OPTIONS_VERBOSE_HELP)
verbose = true;
p->propertyFiles.push_back(f);
}
} // of arguments iteration
else if (result == FG_OPTIONS_SHOW_AIRCRAFT) {
// then config files
SGPath config;
if( homedir && hostname && strlen(hostname) > 0 ) {
// Check for ~/.fgfsrc.hostname
config.set(homedir);
config.append(".fgfsrc");
config.concat( "." );
config.concat( hostname );
readConfig(config);
}
// Check for ~/.fgfsrc
if( homedir ) {
config.set(homedir);
config.append(".fgfsrc");
readConfig(config);
}
// setup FG_ROOT
setupRoot();
// system.fgfsrc handling
if( hostname && strlen(hostname) > 0 ) {
config.set(globals->get_fg_root());
config.append( "system.fgfsrc" );
config.concat( "." );
config.concat( hostname );
readConfig(config);
}
config.set(globals->get_fg_root());
config.append( "system.fgfsrc" );
readConfig(config);
}
void Options::initAircraft()
{
BOOST_FOREACH(const string& paths, valuesForOption("fg-aircraft")) {
globals->append_aircraft_paths(paths);
}
const char* envp = ::getenv("FG_AIRCRAFT");
if (envp) {
globals->append_aircraft_paths(envp);
}
string aircraft;
if (isOptionSet("aircraft")) {
aircraft = valueForOption("aircraft");
} else if (isOptionSet("vehicle")) {
aircraft = valueForOption("vehicle");
}
if (!aircraft.empty()) {
SG_LOG(SG_INPUT, SG_INFO, "aircraft = " << aircraft );
fgSetString("/sim/aircraft", aircraft.c_str() );
} else {
SG_LOG(SG_INPUT, SG_INFO, "No user specified aircraft, using default" );
}
if (p->showAircraft) {
fgOptLogLevel( "alert" );
SGPath path( globals->get_fg_root() );
path.append("Aircraft");
fgShowAircraft(path);
exit(0);
}
}
void Options::processArgResult(int result)
{
if ((result == FG_OPTIONS_HELP) || (result == FG_OPTIONS_ERROR))
p->showHelp = true;
else if (result == FG_OPTIONS_VERBOSE_HELP)
p->verbose = true;
else if (result == FG_OPTIONS_SHOW_AIRCRAFT) {
p->showAircraft = true;
} else if (result == FG_OPTIONS_SHOW_SOUND_DEVICES) {
SGSoundMgr smgr;
@ -1706,38 +1789,19 @@ fgParseArgs (int argc, char **argv)
}
devices.clear();
exit(0);
}
else if (result == FG_OPTIONS_EXIT)
} else if (result == FG_OPTIONS_EXIT) {
exit(0);
}
} else {
in_options = false;
SG_LOG(SG_GENERAL, SG_INFO,
"Reading command-line property file " << arg);
readProperties(arg, globals->get_props());
}
}
if (help) {
fgOptLogLevel( "alert" );
fgUsage(verbose);
exit(0);
}
SG_LOG(SG_GENERAL, SG_INFO, "Finished command line arguments");
}
// Parse config file options
void
fgParseOptions (const string& path) {
sg_gzifstream in( path );
void Options::readConfig(const SGPath& path)
{
sg_gzifstream in( path.str() );
if ( !in.is_open() ) {
return;
}
SG_LOG( SG_GENERAL, SG_INFO, "Processing config file: " << path );
SG_LOG( SG_GENERAL, SG_INFO, "Processing config file: " << path.str() );
in >> skipcomment;
while ( ! in.eof() ) {
@ -1751,23 +1815,153 @@ fgParseOptions (const string& path) {
break;
line = line.substr( 0, i );
if ( parse_option( line ) == FG_OPTIONS_ERROR ) {
cerr << endl << "Config file parse error: " << path << " '"
if ( parseOption( line ) == FG_OPTIONS_ERROR ) {
cerr << endl << "Config file parse error: " << path.str() << " '"
<< line << "'" << endl;
fgUsage();
exit(-1);
p->showHelp = true;
}
in >> skipcomment;
}
}
// Print usage message
void
fgUsage (bool verbose)
int Options::parseOption(const string& s)
{
SGPropertyNode *locale = globals->get_locale();
if ((s == "--help") || (s=="-h")) {
return FG_OPTIONS_HELP;
} else if ( (s == "--verbose") || (s == "-v") ) {
// verbose help/usage request
return FG_OPTIONS_VERBOSE_HELP;
} else if (s.find("-psn") == 0) {
// on Mac, when launched from the GUI, we are passed the ProcessSerialNumber
// as an argument (and no others). Silently ignore the argument here.
return FG_OPTIONS_OK;
} else if ( s.find( "--show-aircraft") == 0) {
return(FG_OPTIONS_SHOW_AIRCRAFT);
} else if ( s.find( "--show-sound-devices") == 0) {
return(FG_OPTIONS_SHOW_SOUND_DEVICES);
} else if ( s.find( "--prop:") == 0) {
// property setting has a slightly different syntax, so fudge things
OptionDesc* desc = p->findOption("prop");
if (s.find("=", 7) == string::npos) { // no equals token
SG_LOG(SG_GENERAL, SG_ALERT, "malformed property option:" << s);
return FG_OPTIONS_ERROR;
}
p->values.push_back(OptionValue(desc, s.substr(7)));
return FG_OPTIONS_OK;
} else if ( s.find( "--" ) == 0 ) {
size_t eqPos = s.find( '=' );
string key, value;
if (eqPos == string::npos) {
key = s.substr(2);
} else {
key = s.substr( 2, eqPos - 2 );
value = s.substr( eqPos + 1);
}
return addOption(key, value);
} else {
SG_LOG(SG_GENERAL, SG_ALERT, "unknown option:" << s);
return FG_OPTIONS_ERROR;
}
}
int Options::addOption(const string &key, const string &value)
{
OptionDesc* desc = p->findOption(key);
if (!desc) {
return FG_OPTIONS_ERROR;
}
if (!(desc->type & OPTION_MULTI)) {
OptionValueVec::const_iterator it = p->findValue(key);
if (it != p->values.end()) {
SG_LOG(SG_GENERAL, SG_INFO, "multiple values forbidden for option:" << key << ", ignoring:" << value);
return FG_OPTIONS_OK;
}
}
p->values.push_back(OptionValue(desc, value));
return FG_OPTIONS_OK;
}
bool Options::isOptionSet(const string &key) const
{
OptionValueVec::const_iterator it = p->findValue(key);
return (it != p->values.end());
}
string Options::valueForOption(const string& key, const string& defValue) const
{
OptionValueVec::const_iterator it = p->findValue(key);
if (it == p->values.end()) {
return defValue;
}
return it->value;
}
string_list Options::valuesForOption(const std::string& key) const
{
string_list result;
OptionValueVec::const_iterator it = p->values.begin();
for (; it != p->values.end(); ++it) {
if (it->desc->option == key) {
result.push_back(it->value);
}
}
return result;
}
void Options::processOptions()
{
// now FG_ROOT is setup, process various command line options that bail us
// out quickly, but rely on aircraft / root settings
if (p->showHelp) {
showUsage();
exit(0);
}
BOOST_FOREACH(const OptionValue& v, p->values) {
int result = p->processOption(v.desc, v.value);
if (result == FG_OPTIONS_ERROR) {
showUsage();
exit(-1);
}
}
BOOST_FOREACH(const SGPath& file, p->propertyFiles) {
if (!file.exists()) {
SG_LOG(SG_GENERAL, SG_ALERT, "config file not found:" << file.str());
continue;
}
SG_LOG(SG_GENERAL, SG_INFO,
"Reading command-line property file " << file.str());
readProperties(file.str(), globals->get_props());
}
// now options are process, do supplemental fixup
const char *envp = ::getenv( "FG_SCENERY" );
if (envp) {
globals->append_fg_scenery(envp);
}
if (globals->get_fg_scenery().empty()) {
// no scenery paths set *at all*, use the data in FG_ROOT
SGPath root(globals->get_fg_root());
root.append("Scenery");
globals->append_fg_scenery(root.str());
}
}
void Options::showUsage() const
{
fgOptLogLevel( "alert" );
SGPropertyNode *locale = globals->get_locale();
SGPropertyNode options_root;
SG_LOG( SG_GENERAL, SG_ALERT, "" ); // To popup the console on Windows
@ -1809,7 +2003,7 @@ fgUsage (bool verbose)
SGPropertyNode *arg = option[k]->getNode("arg");
bool brief = option[k]->getNode("brief") != 0;
if ((brief || verbose) && name) {
if ((brief || p->verbose) && name) {
string tmp = name->getStringValue();
if (key){
@ -1884,7 +2078,7 @@ fgUsage (bool verbose)
}
}
if ( !verbose ) {
if ( !p->verbose ) {
cout << endl;
cout << "For a complete list of options use --help --verbose" << endl;
}
@ -1893,3 +2087,85 @@ fgUsage (bool verbose)
cin.get();
#endif
}
#if defined(__CYGWIN__)
string Options::platformDefaultRoot() const
{
return "../data";
}
#elif defined(_WIN32)
string Options::platformDefaultRoot() const
{
return "..\\data";
}
#elif defined(__APPLE__)
string Options::platformDefaultRoot() const
{
/*
The following code looks for the base package inside the application
bundle, in the standard Contents/Resources location.
*/
CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
// look for a 'data' subdir
CFURLRef dataDir = CFURLCreateCopyAppendingPathComponent(NULL, resourcesUrl, CFSTR("data"), true);
// now convert down to a path, and the a c-string
CFStringRef path = CFURLCopyFileSystemPath(dataDir, kCFURLPOSIXPathStyle);
string root = CFStringGetCStringPtr(path, CFStringGetSystemEncoding());
CFRelease(resourcesUrl);
CFRelease(dataDir);
CFRelease(path);
return root;
}
#else
string Options::platformDefaultRoot() const
{
return PKGLIBDIR;
}
#endif
void Options::setupRoot()
{
string root;
if (isOptionSet("fg-root")) {
root = valueForOption("fg-root"); // easy!
} else {
// Next check if fg-root is set as an env variable
char *envp = ::getenv( "FG_ROOT" );
if ( envp != NULL ) {
root = envp;
} else {
root = platformDefaultRoot();
}
}
SG_LOG(SG_INPUT, SG_INFO, "fg_root = " << root );
globals->set_fg_root(root);
// validate it
static char required_version[] = "2.5.0";
string base_version = fgBasePackageVersion();
if ( !(base_version == required_version) ) {
// tell the operator how to use this application
SG_LOG( SG_GENERAL, SG_ALERT, "" ); // To popup the console on windows
cerr << endl << "Base package check failed:" << endl \
<< " Version " << base_version << " found at: " \
<< globals->get_fg_root() << endl \
<< " Version " << required_version << " is required." << endl \
<< "Please upgrade/downgrade base package and set the path to your fgdata" << endl \
<< "with --fg-root=path_to_your_fgdata" << endl;
#ifdef _MSC_VER
cerr << "Hit a key to continue..." << endl;
cin.get();
#endif
exit(-1);
}
}
} // of namespace flightgear

View file

@ -24,14 +24,85 @@
#ifndef _OPTIONS_HXX
#define _OPTIONS_HXX
#include <memory>
#include <string>
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/misc/strutils.hxx>
extern void fgSetDefaults ();
extern void fgParseArgs (int argc, char ** argv);
extern void fgParseOptions (const string &file_path);
extern void fgUsage (bool verbose = false);
// forward decls
class SGPath;
namespace flightgear
{
class Options
{
private:
Options();
public:
static Options* sharedInstance();
~Options();
/**
* pass command line arguments, read default config files
*/
void init(int argc, char* argv[]);
/**
* parse a config file (eg, .fgfsrc)
*/
void readConfig(const SGPath& path);
/**
* read the value for an option, if it has been set
*/
std::string valueForOption(const std::string& key, const std::string& defValue = std::string()) const;
/**
* return all values for a multi-valued option
*/
string_list valuesForOption(const std::string& key) const;
/**
* check if a particular option has been set (so far)
*/
bool isOptionSet(const std::string& key) const;
/**
* set an option value, assuming it is not already set (or multiple values
* are permitted)
* This can be used to inject option values, eg based upon environment variables
*/
int addOption(const std::string& key, const std::string& value);
/**
* apply option values to the simulation state
* (set properties, etc)
*/
void processOptions();
/**
* init the aircraft options
*/
void initAircraft();
private:
void showUsage() const;
int parseOption(const std::string& s);
void processArgResult(int result);
void setupRoot();
std::string platformDefaultRoot() const;
class OptionsPrivate;
std::auto_ptr<OptionsPrivate> p;
};
} // of namespace flightgear
#endif /* _OPTIONS_HXX */