// $Id: CommandLineParser.cc 5993 2007-01-12 22:12:12Z m9710797 $ #include "CommandLineParser.hh" #include "CLIOption.hh" #include "CommandController.hh" #include "SettingsConfig.hh" #include "File.hh" #include "FileContext.hh" #include "FileOperations.hh" #include "GlobalCliComm.hh" #include "Version.hh" #include "MSXRomCLI.hh" #include "CliExtension.hh" #include "CassettePlayerCLI.hh" #include "DiskImageCLI.hh" #include "HDImageCLI.hh" #include "CDImageCLI.hh" #include "MSXEventRecorderReplayerCLI.hh" #include "ConfigException.hh" #include "FileException.hh" #include "EnumSetting.hh" #include "HostCPU.hh" #include "Reactor.hh" #include #include #include using std::cout; using std::endl; using std::list; using std::map; using std::set; using std::string; using std::vector; namespace openmsx { class HelpOption : public CLIOption { public: explicit HelpOption(CommandLineParser& parser); virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; private: CommandLineParser& parser; }; class VersionOption : public CLIOption { public: explicit VersionOption(CommandLineParser& parser); virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; private: CommandLineParser& parser; }; class ControlOption : public CLIOption { public: explicit ControlOption(CommandLineParser& parser); virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; private: CommandLineParser& parser; }; class ScriptOption : public CLIOption { public: const CommandLineParser::Scripts& getScripts() const; virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; private: CommandLineParser::Scripts scripts; }; class MachineOption : public CLIOption { public: explicit MachineOption(CommandLineParser& parser); virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; private: CommandLineParser& parser; }; class SettingOption : public CLIOption { public: explicit SettingOption(CommandLineParser& parser); virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; private: CommandLineParser& parser; }; class NoMMXOption : public CLIOption { public: virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; }; class NoMMXEXTOption : public CLIOption { public: virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; }; class TestConfigOption : public CLIOption { public: explicit TestConfigOption(CommandLineParser& parser); virtual bool parseOption(const std::string& option, std::list& cmdLine); virtual const std::string& optionHelp() const; private: CommandLineParser& parser; }; // class CommandLineParser CommandLineParser::CommandLineParser(Reactor& reactor_) : parseStatus(UNPARSED) , reactor(reactor_) , settingsConfig(reactor.getCommandController().getSettingsConfig()) , output(reactor.getGlobalCliComm()) , helpOption(new HelpOption(*this)) , versionOption(new VersionOption(*this)) , controlOption(new ControlOption(*this)) , scriptOption(new ScriptOption()) , machineOption(new MachineOption(*this)) , settingOption(new SettingOption(*this)) , noMMXOption(new NoMMXOption()) , noMMXEXTOption(new NoMMXEXTOption()) , testConfigOption(new TestConfigOption(*this)) , msxRomCLI(new MSXRomCLI(*this)) , cliExtension(new CliExtension(*this)) , cassettePlayerCLI(new CassettePlayerCLI(*this)) , diskImageCLI(new DiskImageCLI(*this)) , hdImageCLI(new HDImageCLI(*this)) , cdImageCLI(new CDImageCLI(*this)) , eventRecorderReplayerCLI(new MSXEventRecorderReplayerCLI(*this)) { haveConfig = false; haveSettings = false; registerOption("-machine", *machineOption, 4); registerOption("-setting", *settingOption, 2); registerOption("-h", *helpOption, 1, 1); registerOption("--help", *helpOption, 1, 1); registerOption("-v", *versionOption, 1, 1); registerOption("--version", *versionOption, 1, 1); registerOption("-control", *controlOption, 1, 1); registerOption("-script", *scriptOption, 1, 1); #ifdef ASM_X86 registerOption("-nommx", *noMMXOption, 1, 1); registerOption("-nommxext", *noMMXEXTOption, 1, 1); #endif registerOption("-testconfig", *testConfigOption, 1, 1); registerFileTypes(); } CommandLineParser::~CommandLineParser() { } void CommandLineParser::registerOption( const string& str, CLIOption& cliOption, unsigned prio, unsigned length) { OptionData temp; temp.option = &cliOption; temp.prio = prio; temp.length = length; optionMap[str] = temp; } void CommandLineParser::registerFileClass( const string& str, CLIFileType& cliFileType) { fileClassMap[str] = &cliFileType; } void CommandLineParser::registerFileTypes() { const XMLElement* config = settingsConfig.findChild("FileTypes"); if (config) { for (FileClassMap::const_iterator i = fileClassMap.begin(); i != fileClassMap.end(); ++i) { XMLElement::Children extensions; config->getChildren(i->first, extensions); for (XMLElement::Children::const_iterator j = extensions.begin(); j != extensions.end(); ++j) { fileTypeMap[(*j)->getData()] = i->second; } } } else { map fileExtMap; fileExtMap["rom"] = "romimage"; fileExtMap["dsk"] = "diskimage"; fileExtMap["di1"] = "diskimage"; fileExtMap["di2"] = "diskimage"; fileExtMap["xsa"] = "diskimage"; fileExtMap["wav"] = "cassetteimage"; fileExtMap["cas"] = "cassetteimage"; for (map::const_iterator j = fileExtMap.begin(); j != fileExtMap.end(); ++j) { FileClassMap::const_iterator i = fileClassMap.find(j->second); if (i != fileClassMap.end()) { fileTypeMap[j->first] = i->second; } } } } bool CommandLineParser::parseOption( const string& arg, list& cmdLine, unsigned priority) { map::const_iterator it1 = optionMap.find(arg); if (it1 != optionMap.end()) { // parse option if (it1->second.prio <= priority) { try { return it1->second.option->parseOption( arg, cmdLine); } catch (MSXException& e) { throw FatalError(e.getMessage()); } } } return false; // unknown } bool CommandLineParser::parseFileName(const string& arg, list& cmdLine) { string originalName(arg); try { UserFileContext context(getReactor().getCommandController()); File file(context.resolve(arg)); originalName = file.getOriginalName(); } catch (FileException& e) { // ignore } string::size_type begin = originalName.find_last_of('.'); if (begin != string::npos) { // there is an extension string extension = originalName.substr(begin + 1); FileTypeMap::const_iterator it2 = fileTypeMap.find(extension); if (it2 != fileTypeMap.end()) { try { // parse filetype it2->second->parseFileType(arg, cmdLine); return true; // file processed } catch (MSXException& e) { throw FatalError(e.getMessage()); } } } return false; // unknown } void CommandLineParser::parse(int argc, char** argv) { parseStatus = RUN; list cmdLine; list backupCmdLine; for (int i = 1; i < argc; i++) { cmdLine.push_back(FileOperations::getConventionalPath(argv[i])); } for (int priority = 1; (priority <= 8) && (parseStatus != EXIT); ++priority) { switch (priority) { case 3: if (!haveSettings) { // Load default settings file in case the user didn't specify // one. try { SystemFileContext context; settingsConfig.loadSetting(context, "settings.xml"); } catch (FileException& e) { // settings.xml not found } catch (ConfigException& e) { throw FatalError("Error in default settings: " + e.getMessage()); } // Consider an attempt to load the settings good enough. haveSettings = true; } break; case 5: { if (!haveConfig) { // load default config file in case the user didn't specify one const string& machine = reactor.getMachineSetting().getValueString(); output.printInfo("Using default machine: " + machine); try { reactor.createMotherBoard(machine); } catch (MSXException& e) { output.printInfo( "Failed to initialize default machine: " + e.getMessage() ); // Default machine is broken; fall back to C-BIOS config. const string& fallbackMachine = reactor.getMachineSetting().getRestoreValueString(); output.printInfo("Using fallback machine: " + fallbackMachine); try { reactor.createMotherBoard(fallbackMachine); } catch (MSXException& e2) { // Fallback machine failed as well; we're out of options. throw FatalError(e2.getMessage()); } } haveConfig = true; } break; } default: // iterate over all arguments while (!cmdLine.empty()) { string arg = cmdLine.front(); cmdLine.pop_front(); // first try options if (!parseOption(arg, cmdLine, priority)) { // next try the registered filetypes (xml) if ((priority < 7) || !parseFileName(arg, cmdLine)) { // no option or known file backupCmdLine.push_back(arg); map::const_iterator it1 = optionMap.find(arg); if (it1 != optionMap.end()) { for (unsigned i = 0; i < it1->second.length - 1; ++i) { if (!cmdLine.empty()) { backupCmdLine.push_back(cmdLine.front()); cmdLine.pop_front(); } } } } } } cmdLine = backupCmdLine; backupCmdLine.clear(); break; } } if (!cmdLine.empty() && (parseStatus != EXIT)) { throw FatalError( "Error parsing command line: " + cmdLine.front() + "\n" + "Use \"openmsx -h\" to see a list of available options" ); } hiddenStartup = (parseStatus == CONTROL || parseStatus == TEST ); } bool CommandLineParser::isHiddenStartup() const { return hiddenStartup; } CommandLineParser::ParseStatus CommandLineParser::getParseStatus() const { assert(parseStatus != UNPARSED); return parseStatus; } const CommandLineParser::Scripts& CommandLineParser::getStartupScripts() const { return scriptOption->getScripts(); } Reactor& CommandLineParser::getReactor() const { return reactor; } MSXMotherBoard* CommandLineParser::getMotherBoard() const { return getReactor().getMotherBoard(); } // Control option ControlOption::ControlOption(CommandLineParser& parser_) : parser(parser_) { } bool ControlOption::parseOption(const string& option, list& cmdLine) { parser.getReactor().getGlobalCliComm().startInput( getArgument(option, cmdLine)); parser.parseStatus = CommandLineParser::CONTROL; return true; } const string& ControlOption::optionHelp() const { static const string text("Enable external control of openMSX process"); return text; } // Script option const CommandLineParser::Scripts& ScriptOption::getScripts() const { return scripts; } bool ScriptOption::parseOption(const string& option, list& cmdLine) { scripts.push_back(getArgument(option, cmdLine)); return true; } const string& ScriptOption::optionHelp() const { static const string text("Run extra startup script"); return text; } // Help option static string formatSet(const set& inputSet, string::size_type columns) { string outString; string::size_type totalLength = 0; // ignore the starting spaces for now for (set::const_iterator it = inputSet.begin(); it != inputSet.end(); ++it) { string temp = *it; if (totalLength == 0) { // first element ? outString += " " + temp; totalLength = temp.length(); } else { outString += ", "; if ((totalLength + temp.length()) > columns) { outString += "\n " + temp; totalLength = temp.length(); } else { outString += temp; totalLength += 2 + temp.length(); } } } if (totalLength < columns) { outString += string(columns - totalLength, ' '); } return outString; } static string formatHelptext(const string& helpText, string::size_type maxlength, string::size_type indent) { string outText; string::size_type index = 0; while (helpText.substr(index).length() > maxlength) { string::size_type pos = helpText.substr(index).rfind(' ', maxlength); if (pos == string::npos) { pos = helpText.substr(maxlength).find(' '); if (pos == string::npos) { pos = helpText.substr(index).length(); } } outText += helpText.substr(index, index + pos) + '\n' + string(indent, ' '); index = pos + 1; } outText += helpText.substr(index); return outText; } static void printItemMap(const map >& itemMap) { set printSet; for (map >::const_iterator it = itemMap.begin(); it != itemMap.end(); ++it) { printSet.insert(formatSet(it->second, 15) + ' ' + formatHelptext(it->first, 50, 20)); } for (set::const_iterator it = printSet.begin(); it != printSet.end(); ++it) { cout << *it << endl; } } HelpOption::HelpOption(CommandLineParser& parser_) : parser(parser_) { } bool HelpOption::parseOption(const string& /*option*/, list& /*cmdLine*/) { if (!parser.haveSettings) { return false; // not parsed yet, load settings first } cout << Version::FULL_VERSION << endl; cout << string(Version::FULL_VERSION.length(), '=') << endl; cout << endl; cout << "usage: openmsx [arguments]" << endl; cout << " an argument is either an option or a filename" << endl; cout << endl; cout << " this is the list of supported options:" << endl; map > optionMap; for (map::const_iterator it = parser.optionMap.begin(); it != parser.optionMap.end(); ++it) { optionMap[it->second.option->optionHelp()].insert(it->first); } printItemMap(optionMap); cout << endl; cout << " this is the list of supported file types:" << endl; map > extMap; for (CommandLineParser::FileTypeMap::const_iterator it = parser.fileTypeMap.begin(); it != parser.fileTypeMap.end(); ++it) { extMap[it->second->fileTypeHelp()].insert(it->first); } printItemMap(extMap); parser.parseStatus = CommandLineParser::EXIT; return true; } const string& HelpOption::optionHelp() const { static const string text("Shows this text"); return text; } // class VersionOption VersionOption::VersionOption(CommandLineParser& parser_) : parser(parser_) { } bool VersionOption::parseOption(const string& /*option*/, list& /*cmdLine*/) { cout << Version::FULL_VERSION << endl; parser.parseStatus = CommandLineParser::EXIT; return true; } const string& VersionOption::optionHelp() const { static const string text("Prints openMSX version and exits"); return text; } // Machine option MachineOption::MachineOption(CommandLineParser& parser_) : parser(parser_) { } bool MachineOption::parseOption(const string& option, list& cmdLine) { if (parser.haveConfig) { throw FatalError("Only one machine option allowed"); } string machine(getArgument(option, cmdLine)); parser.output.printInfo("Using specified machine: " + machine); try { parser.getReactor().createMotherBoard(machine); } catch (MSXException& e) { throw FatalError(e.getMessage()); } parser.haveConfig = true; return true; } const string& MachineOption::optionHelp() const { static const string text("Use machine specified in argument"); return text; } // Setting Option SettingOption::SettingOption(CommandLineParser& parser_) : parser(parser_) { } bool SettingOption::parseOption(const string &option, list &cmdLine) { if (parser.haveSettings) { throw FatalError("Only one setting option allowed"); } try { UserFileContext context( parser.getReactor().getCommandController(), "", true); // skip user directories parser.settingsConfig.loadSetting( context, getArgument(option, cmdLine)); parser.haveSettings = true; } catch (FileException& e) { throw FatalError(e.getMessage()); } catch (ConfigException& e) { throw FatalError(e.getMessage()); } return true; } const string& SettingOption::optionHelp() const { static const string text("Load an alternative settings file"); return text; } // class NoMMXOption bool NoMMXOption::parseOption(const string& /*option*/, list& /*cmdLine*/) { cout << "Disabling MMX " << endl; HostCPU::getInstance().forceDisableMMX(); return true; } const string& NoMMXOption::optionHelp() const { static const string text( "Disables usage of MMX, including extensions (for debugging)"); return text; } // class NoMMXEXTOption bool NoMMXEXTOption::parseOption(const string& /*option*/, list& /*cmdLine*/) { cout << "Disabling MMX extensions" << endl; HostCPU::getInstance().forceDisableMMXEXT(); return true; } const string& NoMMXEXTOption::optionHelp() const { static const string text( "Disables usage of MMX extensions (for debugging)"); return text; } // class TestConfigOption TestConfigOption::TestConfigOption(CommandLineParser& parser_) : parser(parser_) { } bool TestConfigOption::parseOption(const string& /*option*/, list& /*cmdLine*/) { parser.parseStatus = CommandLineParser::TEST; return true; } const string& TestConfigOption::optionHelp() const { static const string text("Test if the specified config works and exit"); return text; } } // namespace openmsx