]> git.mxchange.org Git - flightgear.git/commitdiff
Restructure option/config handling code, to avoid multiple scans & parses for special...
authorJames Turner <zakalawe@mac.com>
Sun, 16 Oct 2011 17:35:40 +0000 (18:35 +0100)
committerJames Turner <zakalawe@mac.com>
Sun, 16 Oct 2011 17:35:40 +0000 (18:35 +0100)
src/Main/bootstrap.cxx
src/Main/fg_init.cxx
src/Main/fg_init.hxx
src/Main/fgviewer.cxx
src/Main/globals.cxx
src/Main/globals.hxx
src/Main/main.cxx
src/Main/options.cxx
src/Main/options.hxx

index 2208ceb15502b16a735c8686cc6c2fc98db3c917..0b648fc590945c115873288a9d97c5a1cd3794af 100644 (file)
 #  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])) {
index 88ea231faa1069030ba46f12006346d8e12b7be2..e402167c139520fb993e6e52a2e90a30a363de35 100644 (file)
 #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,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;
 }
 
index b23fead60bd2c108d9379d18ae98dfaa16407750..021feb8797d90605b343c15194c0b0eed1434a7e 100644 (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();
 
index 2f11aa66bd496d17891e78f1ec12a0484e3eb54d..e7f46005b5e819bec44e63cae6db9fee4af18632 100644 (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);
index 40afef14c9e1c082c169411feabeafd8201897a4..4d6d7bf57ec776e3f7a02ef91582371ebf33a214 100644 (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
index d2027d333d8c91db4bfc7baa3c1d15a07d2a57da..54b777a71842c4769a18cbff448b15791d8814a4 100644 (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);
index 79fe251de240787e5136a17c3124fb240ec2069b..1db151ad9059b58027160e00517f9e1138c18d66 100644 (file)
@@ -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.
index 172d133ea7e329e83d5807a57af9040e41d45b9a..c65b355b1b1910bfc1073cb8a0117b22e7f22c0b 100644 (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()
 
 #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,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<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,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:[<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(':');
+/**
+ * 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<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
+  }
+  
+  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<string,size_t>::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; 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 {
+    // XML properties file
+      SGPath f(argv[i]);
+      if (!f.exists()) {
+        SG_LOG(SG_GENERAL, SG_ALERT, "config file not found:" << f.str());
+        return;
+      }
+      
+      p->propertyFiles.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 <const char*>devices = smgr.get_available_devices();
-              for (vector <const char*>::size_type i=0; i<devices.size(); i++) {
-                cout << i << ".  \"" << devices[i] << "\"" << endl;
-              }
-              devices.clear();
-              exit(0);
-            }
+  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);
+  }
 
-            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 <const char*>devices = smgr.get_available_devices();
+    for (vector <const char*>::size_type i=0; i<devices.size(); i++) {
+      cout << i << ".  \"" << devices[i] << "\"" << endl;
     }
-
-    if (help) {
-       fgOptLogLevel( "alert" );
-       fgUsage(verbose);
-       exit(0);
+    devices.clear();
+    exit(0);
+  } else if (result == FG_OPTIONS_EXIT) {
+    exit(0);
+  }
+}
+  
+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.str() );
+  
+  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 ( 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: <options> 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());
+  }
 
-    vector<SGPropertyNode_ptr>section = options->getChildren("section");
-    for (unsigned int j = 0; j < section.size(); j++) {
-        string msg = "";
-
-        vector<SGPropertyNode_ptr>option = 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 <description> tag assosiated
-                // with one option
-
-                vector<SGPropertyNode_ptr> 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");
-                      vector<SGPropertyNode_ptr>trans_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: <options> directive not found." );
+    exit(-1);
+  }
+  
+  SGPropertyNode *usage = locale->getNode(options->getStringValue("usage"));
+  if (usage) {
+    cout << "Usage: " << usage->getStringValue() << endl;
+  }
+  
+  vector<SGPropertyNode_ptr>section = options->getChildren("section");
+  for (unsigned int j = 0; j < section.size(); j++) {
+    string msg = "";
+    
+    vector<SGPropertyNode_ptr>option = 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 <description> tag assosiated
+        // with one option
+        
+        vector<SGPropertyNode_ptr> 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");
+            vector<SGPropertyNode_ptr>trans_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
+
index 4ad8b133d335e1f3292fc3c12122411171a724ce..bd7e368480fc108c13407df60a8f9ed2ff4e70c7 100644 (file)
 #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 */