From b1c7495fec8d0ae6db3a411bbdd55d53c2a7e8cc Mon Sep 17 00:00:00 2001 From: James Turner Date: Sun, 16 Oct 2011 18:35:40 +0100 Subject: [PATCH] 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. --- src/Main/bootstrap.cxx | 32 +- src/Main/fg_init.cxx | 269 +--------- src/Main/fg_init.hxx | 5 - src/Main/fgviewer.cxx | 1 - src/Main/globals.cxx | 36 +- src/Main/globals.hxx | 2 +- src/Main/main.cxx | 28 +- src/Main/options.cxx | 1124 +++++++++++++++++++++++++--------------- src/Main/options.hxx | 85 ++- 9 files changed, 830 insertions(+), 752 deletions(-) diff --git a/src/Main/bootstrap.cxx b/src/Main/bootstrap.cxx index 2208ceb15..0b648fc59 100644 --- a/src/Main/bootstrap.cxx +++ b/src/Main/bootstrap.cxx @@ -38,6 +38,10 @@ # include #endif +#ifndef _WIN32 +# include // for gethostname() +#endif + #include #include #include @@ -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])) { diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 88ea231fa..e402167c1 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -121,225 +121,9 @@ #include "main.hxx" -#ifdef __APPLE__ -# include -#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 bool fgFindAircraftInDir(const SGPath& dirPath, T* obj, bool (T::*pred)(const SGPath& p)) { @@ -709,9 +454,8 @@ 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"); fgLoadProps("preferences.xml", globals->get_props()); @@ -761,8 +505,9 @@ bool fgInitConfig ( int argc, char **argv ) { } } - // Scan user config files and command line for a specified aircraft. - fgInitFGAircraft(argc, argv); + // Scan user config files and command line for a specified aircraft. + flightgear::Options::sharedInstance()->initAircraft(); + FindAndCacheAircraft f(&autosave); if (!f.loadAircraft()) { return false; @@ -772,8 +517,8 @@ 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; } diff --git a/src/Main/fg_init.hxx b/src/Main/fg_init.hxx index b23fead60..021feb879 100644 --- a/src/Main/fg_init.hxx +++ b/src/Main/fg_init.hxx @@ -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(); diff --git a/src/Main/fgviewer.cxx b/src/Main/fgviewer.cxx index 2f11aa66b..e7f46005b 100644 --- a/src/Main/fgviewer.cxx +++ b/src/Main/fgviewer.cxx @@ -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); diff --git a/src/Main/globals.cxx b/src/Main/globals.cxx index 40afef14c..4d6d7bf57 100644 --- a/src/Main/globals.cxx +++ b/src/Main/globals.cxx @@ -24,6 +24,9 @@ # include #endif +#include +#include + #include #include #include @@ -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 diff --git a/src/Main/globals.hxx b/src/Main/globals.hxx index d2027d333..54b777a71 100644 --- a/src/Main/globals.hxx +++ b/src/Main/globals.hxx @@ -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); diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 79fe251de..1db151ad9 100644 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -609,36 +609,12 @@ 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.) if ( !fgInitConfig(argc, argv) ) { - SG_LOG( SG_GENERAL, SG_ALERT, "Config option parsing failed ..." ); - exit(-1); + SG_LOG( SG_GENERAL, SG_ALERT, "Config option parsing failed ..." ); + exit(-1); } // Initialize the Window/Graphics environment. diff --git a/src/Main/options.cxx b/src/Main/options.cxx index 172d133ea..c65b355b1 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -31,6 +31,8 @@ #include #include +#include + #include // rint() #include #include // atof(), atoi() @@ -61,12 +63,6 @@ #include -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 @@ -74,8 +70,22 @@ using std::endl; # include #endif +#ifdef __APPLE__ +# include +#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,27 +743,13 @@ 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); + globals->append_fg_scenery(arg); return FG_OPTIONS_OK; } -static int -fgOptFgAircraft(const char* arg) -{ - // this option is dealt with by fgInitFGAircraft - return FG_OPTIONS_OK; -} - static int fgOptFov( const char *arg ) { @@ -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 fgOptionMap; +// Set a property for the --prop: option. Syntax: --prop:[:]= +// 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,413 +1507,665 @@ 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:[:]= -// 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(':'); +/** + * internal storage of a value->option binding + */ +class OptionValue +{ +public: + OptionValue(OptionDesc* d, const string& v) : + desc(d), value(v) + {;} + + OptionDesc* desc; + string value; +}; - if (pos != name.npos && pos != 0 && pos + 1 != name.size()) { - type = name.substr(0, pos); - name = name.substr(pos + 1); +typedef std::vector OptionValueVec; +typedef std::map 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 + } + + OptionDesc* findOption(const string& key) const + { + OptionDescDict::const_iterator it = options.find(key); + if (it == options.end()) { + return NULL; } - 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) -{ - if ( fgOptionMap.size() == 0 ) { - size_t i = 0; - OptionDesc *pt = &fgOptionArray[ 0 ]; - while ( pt->option != 0 ) { - fgOptionMap[ pt->option ] = i; - i += 1; - pt += 1; + + return it->second; + } + + int processOption(OptionDesc* desc, const string& arg_value) + { + switch ( desc->type & 0xffff ) { + case OPTION_BOOL: + fgSetBool( desc->property, desc->b_param ); + break; + case OPTION_STRING: + 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 '" << desc->option << "' does not have a parameter" ); + return FG_OPTIONS_ERROR; } - } - - // 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; + break; + case OPTION_DOUBLE: + if ( !arg_value.empty() ) { + fgSetDouble( desc->property, atof( arg_value ) ); + } else { + SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" ); + return FG_OPTIONS_ERROR; } - } 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 ); + break; + case OPTION_INT: + if ( !arg_value.empty() ) { + fgSetInt( desc->property, atoi( arg_value ) ); } else { - arg_name = arg.substr( 2, pos - 2 ); - arg_value = arg.substr( pos + 1); + SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" ); + return FG_OPTIONS_ERROR; } - map::iterator it = fgOptionMap.find( arg_name ); - if ( it != fgOptionMap.end() ) { - OptionDesc *pt = &fgOptionArray[ it->second ]; - switch ( pt->type ) { - case OPTION_BOOL: - fgSetBool( pt->property, pt->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" ); - return FG_OPTIONS_ERROR; - } else { - SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' does not have a parameter" ); - return FG_OPTIONS_ERROR; - } - break; - case OPTION_DOUBLE: - if ( !arg_value.empty() ) { - fgSetDouble( pt->property, atof( arg_value ) ); - } else { - SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' needs a parameter" ); - return FG_OPTIONS_ERROR; - } - break; - case OPTION_INT: - if ( !arg_value.empty() ) { - fgSetInt( pt->property, atoi( arg_value ) ); - } else { - SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' 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" ); - return FG_OPTIONS_ERROR; - } else { - SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' 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" ); - return FG_OPTIONS_ERROR; - } else { - SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << arg << "' does not have a parameter" ); - return FG_OPTIONS_ERROR; - } - break; - } + break; + case OPTION_CHANNEL: + // XXX return value of add_channel should be checked? + 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, "Unknown option '" << arg << "'" ); - return FG_OPTIONS_ERROR; + SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" ); + return FG_OPTIONS_ERROR; } - } else { - SG_LOG( SG_GENERAL, SG_ALERT, "Unknown option '" << arg << "'" ); - return FG_OPTIONS_ERROR; + break; + case OPTION_FUNC: + 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 '" << desc->option << "' does not have a parameter" ); + return FG_OPTIONS_ERROR; + } + break; + + case OPTION_IGNORE: + break; } - + return FG_OPTIONS_OK; + } + + bool showHelp, + verbose, + showAircraft; + OptionDescDict options; + OptionValueVec values; + simgear::PathList propertyFiles; +}; + +Options* Options::sharedInstance() +{ + if (shared_instance == NULL) { + shared_instance = new Options; + } + + return shared_instance; +} + +Options::Options() : + p(new OptionsPrivate()) +{ + p->showHelp = false; + p->verbose = false; + p->showAircraft = 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; ipropertyFiles.push_back(f); + } + } // of arguments iteration + +// 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); + } - -// Parse the command line options -void -fgParseArgs (int argc, char **argv) + config.set(globals->get_fg_root()); + config.append( "system.fgfsrc" ); + readConfig(config); +} + +void Options::initAircraft() { - bool in_options = true; - bool verbose = false; - bool help = false; - - SG_LOG(SG_GENERAL, SG_ALERT, "Processing command line arguments"); - - for (int i = 1; i < argc; i++) { - string arg = argv[i]; - - if (in_options && (arg.find('-') == 0)) { - if (arg == "--") { - in_options = false; - } else { - int result = parse_option(arg); - if ((result == FG_OPTIONS_HELP) || (result == FG_OPTIONS_ERROR)) - help = true; - - else if (result == FG_OPTIONS_VERBOSE_HELP) - verbose = true; - - else if (result == FG_OPTIONS_SHOW_AIRCRAFT) { - fgOptLogLevel( "alert" ); - SGPath path( globals->get_fg_root() ); - path.append("Aircraft"); - fgShowAircraft(path); - exit(0); - - } else if (result == FG_OPTIONS_SHOW_SOUND_DEVICES) { - SGSoundMgr smgr; - - smgr.init(); - string vendor = smgr.get_vendor(); - string renderer = smgr.get_renderer(); - cout << renderer << " provided by " << vendor << endl; - cout << endl << "No. Device" << endl; - - vector devices = smgr.get_available_devices(); - for (vector ::size_type i=0; iappend_aircraft_paths(paths); + } + + const char* envp = ::getenv("FG_AIRCRAFT"); + if (envp) { + globals->append_aircraft_paths(envp); + } - 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()); - } + 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; + + smgr.init(); + string vendor = smgr.get_vendor(); + string renderer = smgr.get_renderer(); + cout << renderer << " provided by " << vendor << endl; + cout << endl << "No. Device" << endl; + + vector devices = smgr.get_available_devices(); + for (vector ::size_type i=0; i> skipcomment; + while ( ! in.eof() ) { + string line; + getline( in, line, '\n' ); + + // catch extraneous (DOS) line ending character + int i; + for (i = line.length(); i > 0; i--) + if (line[i - 1] > 32) + break; + line = line.substr( 0, i ); + + if ( parseOption( line ) == FG_OPTIONS_ERROR ) { + cerr << endl << "Config file parse error: " << path.str() << " '" + << line << "'" << endl; + p->showHelp = true; } + in >> skipcomment; + } - SG_LOG(SG_GENERAL, SG_INFO, "Finished command line arguments"); } - - -// Parse config file options -void -fgParseOptions (const string& path) { - sg_gzifstream in( path ); - if ( !in.is_open() ) { - return; + +int Options::parseOption(const string& s) +{ + 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; } - - SG_LOG( SG_GENERAL, SG_INFO, "Processing config file: " << path ); - - in >> skipcomment; - while ( ! in.eof() ) { - string line; - getline( in, line, '\n' ); - - // catch extraneous (DOS) line ending character - int i; - for (i = line.length(); i > 0; i--) - if (line[i - 1] > 32) - break; - line = line.substr( 0, i ); - - if ( parse_option( line ) == FG_OPTIONS_ERROR ) { - cerr << endl << "Config file parse error: " << path << " '" - << line << "'" << endl; - fgUsage(); - exit(-1); - } - in >> skipcomment; + + 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; + } } - - -// Print usage message -void -fgUsage (bool verbose) + +int Options::addOption(const string &key, const string &value) { - SGPropertyNode *locale = globals->get_locale(); - - SGPropertyNode options_root; - - SG_LOG( SG_GENERAL, SG_ALERT, "" ); // To popup the console on Windows - cout << endl; - - try { - fgLoadProps("options.xml", &options_root); - } catch (const sg_exception &) { - cout << "Unable to read the help file." << endl; - cout << "Make sure the file options.xml is located in the FlightGear base directory," << endl; - cout << "and the location of the base directory is specified by setting $FG_ROOT or" << endl; - cout << "by adding --fg-root=path as a program argument." << endl; - - exit(-1); + 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; +} - SGPropertyNode *options = options_root.getNode("options"); - if (!options) { - SG_LOG( SG_GENERAL, SG_ALERT, - "Error reading options.xml: directive not found." ); - exit(-1); +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); } - - SGPropertyNode *usage = locale->getNode(options->getStringValue("usage")); - if (usage) { - cout << "Usage: " << usage->getStringValue() << endl; + } + + 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()); + } - vectorsection = options->getChildren("section"); - for (unsigned int j = 0; j < section.size(); j++) { - string msg = ""; - - vectoroption = section[j]->getChildren("option"); - for (unsigned int k = 0; k < option.size(); k++) { - - SGPropertyNode *name = option[k]->getNode("name"); - SGPropertyNode *short_name = option[k]->getNode("short"); - SGPropertyNode *key = option[k]->getNode("key"); - SGPropertyNode *arg = option[k]->getNode("arg"); - bool brief = option[k]->getNode("brief") != 0; - - if ((brief || verbose) && name) { - string tmp = name->getStringValue(); - - if (key){ - tmp.append(":"); - tmp.append(key->getStringValue()); - } - if (arg) { - tmp.append("="); - tmp.append(arg->getStringValue()); - } - if (short_name) { - tmp.append(", -"); - tmp.append(short_name->getStringValue()); - } - - if (tmp.size() <= 25) { - msg+= " --"; - msg += tmp; - msg.append( 27-tmp.size(), ' '); - } else { - msg += "\n --"; - msg += tmp + '\n'; - msg.append(32, ' '); - } - // There may be more than one tag assosiated - // with one option - - vector desc; - desc = option[k]->getChildren("description"); - if (desc.size() > 0) { - for ( unsigned int l = 0; l < desc.size(); l++) { - - // There may be more than one translation line. - - string t = desc[l]->getStringValue(); - SGPropertyNode *n = locale->getNode("strings"); - vectortrans_desc = - n->getChildren(t.substr(8).c_str()); - - for ( unsigned int m = 0; m < trans_desc.size(); m++ ) { - string t_str = trans_desc[m]->getStringValue(); - - if ((m > 0) || ((l > 0) && m == 0)) { - msg.append( 32, ' '); - } - - // If the string is too large to fit on the screen, - // then split it up in several pieces. - - while ( t_str.size() > 47 ) { - - string::size_type m = t_str.rfind(' ', 47); - msg += t_str.substr(0, m) + '\n'; - msg.append( 32, ' '); - - t_str.erase(t_str.begin(), t_str.begin() + m + 1); - } - msg += t_str + '\n'; - } - } - } - } +// 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 + cout << endl; + + try { + fgLoadProps("options.xml", &options_root); + } catch (const sg_exception &) { + cout << "Unable to read the help file." << endl; + cout << "Make sure the file options.xml is located in the FlightGear base directory," << endl; + cout << "and the location of the base directory is specified by setting $FG_ROOT or" << endl; + cout << "by adding --fg-root=path as a program argument." << endl; + + exit(-1); + } + + SGPropertyNode *options = options_root.getNode("options"); + if (!options) { + SG_LOG( SG_GENERAL, SG_ALERT, + "Error reading options.xml: directive not found." ); + exit(-1); + } + + SGPropertyNode *usage = locale->getNode(options->getStringValue("usage")); + if (usage) { + cout << "Usage: " << usage->getStringValue() << endl; + } + + vectorsection = options->getChildren("section"); + for (unsigned int j = 0; j < section.size(); j++) { + string msg = ""; + + vectoroption = section[j]->getChildren("option"); + for (unsigned int k = 0; k < option.size(); k++) { + + SGPropertyNode *name = option[k]->getNode("name"); + SGPropertyNode *short_name = option[k]->getNode("short"); + SGPropertyNode *key = option[k]->getNode("key"); + SGPropertyNode *arg = option[k]->getNode("arg"); + bool brief = option[k]->getNode("brief") != 0; + + if ((brief || p->verbose) && name) { + string tmp = name->getStringValue(); + + if (key){ + tmp.append(":"); + tmp.append(key->getStringValue()); } - - SGPropertyNode *name; - name = locale->getNode(section[j]->getStringValue("name")); - - if (!msg.empty() && name) { - cout << endl << name->getStringValue() << ":" << endl; - cout << msg; - msg.erase(); + if (arg) { + tmp.append("="); + tmp.append(arg->getStringValue()); + } + if (short_name) { + tmp.append(", -"); + tmp.append(short_name->getStringValue()); + } + + if (tmp.size() <= 25) { + msg+= " --"; + msg += tmp; + msg.append( 27-tmp.size(), ' '); + } else { + msg += "\n --"; + msg += tmp + '\n'; + msg.append(32, ' '); + } + // There may be more than one tag assosiated + // with one option + + vector desc; + desc = option[k]->getChildren("description"); + if (desc.size() > 0) { + for ( unsigned int l = 0; l < desc.size(); l++) { + + // There may be more than one translation line. + + string t = desc[l]->getStringValue(); + SGPropertyNode *n = locale->getNode("strings"); + vectortrans_desc = + n->getChildren(t.substr(8).c_str()); + + for ( unsigned int m = 0; m < trans_desc.size(); m++ ) { + string t_str = trans_desc[m]->getStringValue(); + + if ((m > 0) || ((l > 0) && m == 0)) { + msg.append( 32, ' '); + } + + // If the string is too large to fit on the screen, + // then split it up in several pieces. + + while ( t_str.size() > 47 ) { + + string::size_type m = t_str.rfind(' ', 47); + msg += t_str.substr(0, m) + '\n'; + msg.append( 32, ' '); + + t_str.erase(t_str.begin(), t_str.begin() + m + 1); + } + msg += t_str + '\n'; + } + } } + } } + + SGPropertyNode *name; + name = locale->getNode(section[j]->getStringValue("name")); + + if (!msg.empty() && name) { + cout << endl << name->getStringValue() << ":" << endl; + cout << msg; + msg.erase(); + } + } + + if ( !p->verbose ) { + cout << endl; + cout << "For a complete list of options use --help --verbose" << endl; + } +#ifdef _MSC_VER + cout << "Hit a key to continue..." << endl; + cin.get(); +#endif +} + +#if defined(__CYGWIN__) +string Options::platformDefaultRoot() const +{ + return "../data"; +} - if ( !verbose ) { - cout << endl; - cout << "For a complete list of options use --help --verbose" << endl; +#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 - cout << "Hit a key to continue..." << endl; + cerr << "Hit a key to continue..." << endl; cin.get(); #endif + exit(-1); + } } + +} // of namespace flightgear + diff --git a/src/Main/options.hxx b/src/Main/options.hxx index 4ad8b133d..bd7e36848 100644 --- a/src/Main/options.hxx +++ b/src/Main/options.hxx @@ -24,14 +24,85 @@ #ifndef _OPTIONS_HXX #define _OPTIONS_HXX +#include +#include -#ifndef __cplusplus -# error This library requires C++ -#endif +#include -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 p; +}; + +} // of namespace flightgear #endif /* _OPTIONS_HXX */ -- 2.39.5