openMSX
CommandLineParser.cc
Go to the documentation of this file.
1 #include "CommandLineParser.hh"
2 #include "CLIOption.hh"
4 #include "Interpreter.hh"
5 #include "SettingsConfig.hh"
6 #include "File.hh"
7 #include "FileContext.hh"
8 #include "FileOperations.hh"
9 #include "GlobalCliComm.hh"
10 #include "StdioMessages.hh"
11 #include "Version.hh"
12 #include "MSXRomCLI.hh"
13 #include "CliExtension.hh"
14 #include "CliConnection.hh"
15 #include "ReplayCLI.hh"
16 #include "SaveStateCLI.hh"
17 #include "CassettePlayerCLI.hh"
18 #include "DiskImageCLI.hh"
19 #include "HDImageCLI.hh"
20 #include "CDImageCLI.hh"
21 #include "ConfigException.hh"
22 #include "FileException.hh"
23 #include "EnumSetting.hh"
24 #include "XMLLoader.hh"
25 #include "XMLElement.hh"
26 #include "XMLException.hh"
27 #include "HostCPU.hh"
28 #include "StringOp.hh"
29 #include "utf8_checked.hh"
30 #include "xrange.hh"
31 #include "GLUtil.hh"
32 #include "Reactor.hh"
33 #include "RomInfo.hh"
34 #include "StringMap.hh"
35 #include "memory.hh"
36 #include "build-info.hh"
37 #include "components.hh"
38 
39 #if COMPONENT_LASERDISC
40 #include "LaserdiscPlayerCLI.hh"
41 #endif
42 
43 #include <cassert>
44 #include <iostream>
45 #include <cstdio>
46 
47 using std::cout;
48 using std::endl;
49 using std::deque;
50 using std::map;
51 using std::set;
52 using std::string;
53 using std::vector;
54 
55 #ifdef _WIN32
56 using namespace utf8;
57 #endif
58 
59 namespace openmsx {
60 
61 class HelpOption : public CLIOption
62 {
63 public:
64  explicit HelpOption(CommandLineParser& parser);
65  virtual void parseOption(const string& option, deque<string>& cmdLine);
66  virtual string_ref optionHelp() const;
67 private:
68  CommandLineParser& parser;
69 };
70 
71 class VersionOption : public CLIOption
72 {
73 public:
74  explicit VersionOption(CommandLineParser& parser);
75  virtual void parseOption(const string& option, deque<string>& cmdLine);
76  virtual string_ref optionHelp() const;
77 private:
78  CommandLineParser& parser;
79 };
80 
81 class ControlOption : public CLIOption
82 {
83 public:
84  explicit ControlOption(CommandLineParser& parser);
85  virtual void parseOption(const string& option, deque<string>& cmdLine);
86  virtual string_ref optionHelp() const;
87 private:
88  CommandLineParser& parser;
89 };
90 
91 class ScriptOption : public CLIOption, public CLIFileType
92 {
93 public:
94  const CommandLineParser::Scripts& getScripts() const;
95  virtual void parseOption(const string& option, deque<string>& cmdLine);
96  virtual string_ref optionHelp() const;
97  virtual void parseFileType(const std::string& filename,
98  std::deque<std::string>& cmdLine);
99  virtual string_ref fileTypeHelp() const;
100 
101 private:
103 };
104 
105 class MachineOption : public CLIOption
106 {
107 public:
108  explicit MachineOption(CommandLineParser& parser);
109  virtual void parseOption(const string& option, deque<string>& cmdLine);
110  virtual string_ref optionHelp() const;
111 private:
112  CommandLineParser& parser;
113 };
114 
115 class SettingOption : public CLIOption
116 {
117 public:
118  explicit SettingOption(CommandLineParser& parser);
119  virtual void parseOption(const string& option, deque<string>& cmdLine);
120  virtual string_ref optionHelp() const;
121 private:
122  CommandLineParser& parser;
123 };
124 
125 class NoMMXOption : public CLIOption
126 {
127 public:
128  virtual void parseOption(const string& option, deque<string>& cmdLine);
129  virtual string_ref optionHelp() const;
130 };
131 
132 class NoSSEOption : public CLIOption {
133 public:
134  virtual void parseOption(const string& option, deque<string>& cmdLine);
135  virtual string_ref optionHelp() const;
136 };
137 
138 class NoSSE2Option : public CLIOption {
139 public:
140  virtual void parseOption(const string& option, deque<string>& cmdLine);
141  virtual string_ref optionHelp() const;
142 };
143 
144 class NoPBOOption : public CLIOption {
145 public:
146  virtual void parseOption(const string& option, deque<string>& cmdLine);
147  virtual string_ref optionHelp() const;
148 };
149 
151 {
152 public:
153  explicit TestConfigOption(CommandLineParser& parser);
154  virtual void parseOption(const string& option, deque<string>& cmdLine);
155  virtual string_ref optionHelp() const;
156 private:
157  CommandLineParser& parser;
158 };
159 
160 class BashOption : public CLIOption
161 {
162 public:
163  explicit BashOption(CommandLineParser& parser);
164  virtual void parseOption(const string& option, deque<string>& cmdLine);
165  virtual string_ref optionHelp() const;
166 private:
167  CommandLineParser& parser;
168 };
169 
170 
171 // class CommandLineParser
172 
173 CommandLineParser::CommandLineParser(Reactor& reactor_)
174  : reactor(reactor_)
175  , helpOption(make_unique<HelpOption>(*this))
176  , versionOption(make_unique<VersionOption>(*this))
177  , controlOption(make_unique<ControlOption>(*this))
178  , scriptOption(make_unique<ScriptOption>())
179  , machineOption(make_unique<MachineOption>(*this))
180  , settingOption(make_unique<SettingOption>(*this))
181  , noMMXOption(make_unique<NoMMXOption>())
182  , noSSEOption(make_unique<NoSSEOption>())
183  , noSSE2Option(make_unique<NoSSE2Option>())
184  , noPBOOption(make_unique<NoPBOOption>())
185  , testConfigOption(make_unique<TestConfigOption>(*this))
186  , bashOption(make_unique<BashOption>(*this))
187  , msxRomCLI(make_unique<MSXRomCLI>(*this))
188  , cliExtension(make_unique<CliExtension>(*this))
189  , replayCLI(make_unique<ReplayCLI>(*this))
190  , saveStateCLI(make_unique<SaveStateCLI>(*this))
191  , cassettePlayerCLI(make_unique<CassettePlayerCLI>(*this))
193  , laserdiscPlayerCLI(make_unique<LaserdiscPlayerCLI>(*this))
194 #endif
195  , diskImageCLI(make_unique<DiskImageCLI>(*this))
196  , hdImageCLI(make_unique<HDImageCLI>(*this))
197  , cdImageCLI(make_unique<CDImageCLI>(*this))
198  , parseStatus(UNPARSED)
199 {
200  haveConfig = false;
201  haveSettings = false;
202 
203  registerOption("-h", *helpOption, PHASE_BEFORE_INIT, 1);
204  registerOption("--help", *helpOption, PHASE_BEFORE_INIT, 1);
205  registerOption("-v", *versionOption, PHASE_BEFORE_INIT, 1);
206  registerOption("--version", *versionOption, PHASE_BEFORE_INIT, 1);
207  registerOption("-bash", *bashOption, PHASE_BEFORE_INIT, 1);
208 
209  registerOption("-setting", *settingOption, PHASE_BEFORE_SETTINGS);
210  registerOption("-control", *controlOption, PHASE_BEFORE_SETTINGS, 1);
211  registerOption("-script", *scriptOption, PHASE_BEFORE_SETTINGS, 1); // correct phase?
212  #if ASM_X86
213  registerOption("-nommx", *noMMXOption, PHASE_BEFORE_SETTINGS, 1);
214  registerOption("-nosse", *noSSEOption, PHASE_BEFORE_SETTINGS, 1);
215  registerOption("-nosse2", *noSSE2Option, PHASE_BEFORE_SETTINGS, 1);
216  #endif
217  #if COMPONENT_GL
218  registerOption("-nopbo", *noPBOOption, PHASE_BEFORE_SETTINGS, 1);
219  #endif
220  registerOption("-testconfig", *testConfigOption, PHASE_BEFORE_SETTINGS, 1);
221 
222  registerOption("-machine", *machineOption, PHASE_BEFORE_MACHINE);
223 
224  registerFileClass("Tcl script", *scriptOption);
225  registerFileTypes();
226 }
227 
229 {
230 }
231 
233  const char* str, CLIOption& cliOption, ParsePhase phase, unsigned length)
234 {
235  OptionData temp;
236  temp.option = &cliOption;
237  temp.phase = phase;
238  temp.length = length;
239  optionMap[str] = temp;
240 }
241 
243  const char* str, CLIFileType& cliFileType)
244 {
245  fileClassMap[str] = &cliFileType;
246 }
247 
248 void CommandLineParser::registerFileTypes()
249 {
250  map<string, string> fileExtMap;
251  fileExtMap["rom"] = "romimage";
252  fileExtMap["ri"] = "romimage";
253  fileExtMap["dsk"] = "diskimage";
254  fileExtMap["dmk"] = "diskimage";
255  fileExtMap["di1"] = "diskimage";
256  fileExtMap["di2"] = "diskimage";
257  fileExtMap["xsa"] = "diskimage";
258  fileExtMap["wav"] = "cassetteimage";
259  fileExtMap["cas"] = "cassetteimage";
260  fileExtMap["ogv"] = "laserdiscimage";
261  fileExtMap["omr"] = "openMSX replay";
262  fileExtMap["oms"] = "openMSX savestate";
263  fileExtMap["tcl"] = "Tcl script";
264  for (auto& p : fileExtMap) {
265  auto i = fileClassMap.find(p.second);
266  if (i != fileClassMap.end()) {
267  fileTypeMap[p.first] = i->second;
268  }
269  }
270 }
271 
272 bool CommandLineParser::parseOption(
273  const string& arg, deque<string>& cmdLine, ParsePhase phase)
274 {
275  auto it1 = optionMap.find(arg);
276  if (it1 != optionMap.end()) {
277  // parse option
278  if (it1->second.phase <= phase) {
279  try {
280  it1->second.option->parseOption(arg, cmdLine);
281  return true;
282  } catch (MSXException& e) {
283  throw FatalError(e.getMessage());
284  }
285  }
286  }
287  return false; // unknown
288 }
289 
290 bool CommandLineParser::parseFileName(const string& arg, deque<string>& cmdLine)
291 {
292  // First try the fileName as we get it from the commandline. This may
293  // be more interesting than the original fileName of a (g)zipped file:
294  // in case of an OMR file for instance, we want to select on the
295  // original extension, and not on the extension inside the gzipped
296  // file.
297  bool processed = parseFileNameInner(arg, arg, cmdLine);
298  if (!processed) {
299  try {
300  File file(UserFileContext().resolve(arg));
301  string originalName = file.getOriginalName();
302  processed = parseFileNameInner(originalName, arg, cmdLine);
303  } catch (FileException&) {
304  // ignore
305  }
306  }
307  return processed;
308 }
309 
310 bool CommandLineParser::parseFileNameInner(const string& name, const string& originalPath, deque<string>& cmdLine)
311 {
312  string_ref extension = FileOperations::getExtension(name);
313  if (!extension.empty()) {
314  // there is an extension
315  auto it = fileTypeMap.find(extension.str());
316  if (it != fileTypeMap.end()) {
317  try {
318  // parse filetype
319  it->second->parseFileType(originalPath, cmdLine);
320  return true; // file processed
321  } catch (MSXException& e) {
322  throw FatalError(e.getMessage());
323  }
324  }
325  }
326  return false; // unknown
327 }
328 
329 void CommandLineParser::parse(int argc, char** argv)
330 {
331  parseStatus = RUN;
332 
333  deque<string> cmdLine;
334  deque<string> backupCmdLine;
335  for (auto i : xrange(1, argc)) {
336  cmdLine.push_back(FileOperations::getConventionalPath(argv[i]));
337  }
338 
339  for (ParsePhase phase = PHASE_BEFORE_INIT;
340  (phase <= PHASE_LAST) && (parseStatus != EXIT);
341  phase = static_cast<ParsePhase>(phase + 1)) {
342  switch (phase) {
343  case PHASE_INIT:
344  reactor.init();
345  reactor.getGlobalCommandController().getInterpreter().init(argv[0]);
346  break;
347  case PHASE_LOAD_SETTINGS:
348  // after -control and -setting has been parsed
349  if (parseStatus != CONTROL) {
350  // if there already is a XML-StdioConnection, we
351  // can't also show plain messages on stdout
352  auto& cliComm = reactor.getGlobalCliComm();
353  cliComm.addListener(new StdioMessages());
354  }
355  if (!haveSettings) {
356  auto& settingsConfig =
358  // Load default settings file in case the user
359  // didn't specify one.
360  SystemFileContext context;
361  string filename = "settings.xml";
362  try {
363  settingsConfig.loadSetting(context, filename);
364  } catch (XMLException& e) {
365  reactor.getCliComm().printWarning(
366  "Loading of settings failed: " +
367  e.getMessage() + "\n"
368  "Reverting to default settings.");
369  } catch (FileException&) {
370  // settings.xml not found
371  } catch (ConfigException& e) {
372  throw FatalError("Error in default settings: "
373  + e.getMessage());
374  }
375  // Consider an attempt to load the settings good enough.
376  haveSettings = true;
377  // Even if parsing failed, use this file for saving,
378  // this forces overwriting a non-setting file.
379  settingsConfig.setSaveFilename(context, filename);
380  }
381  break;
382  case PHASE_LOAD_MACHINE: {
383  if (!haveConfig) {
384  // load default config file in case the user didn't specify one
385  const auto& machine =
386  reactor.getMachineSetting().getValueString();
387  try {
388  reactor.switchMachine(machine);
389  } catch (MSXException& e) {
390  reactor.getCliComm().printInfo(
391  "Failed to initialize default machine: " + e.getMessage());
392  // Default machine is broken; fall back to C-BIOS config.
393  const auto& fallbackMachine =
395  reactor.getCliComm().printInfo("Using fallback machine: " + fallbackMachine);
396  try {
397  reactor.switchMachine(fallbackMachine);
398  } catch (MSXException& e2) {
399  // Fallback machine failed as well; we're out of options.
400  throw FatalError(e2.getMessage());
401  }
402  }
403  haveConfig = true;
404  }
405  break;
406  }
407  default:
408  // iterate over all arguments
409  while (!cmdLine.empty()) {
410  string arg = std::move(cmdLine.front());
411  cmdLine.pop_front();
412  // first try options
413  if (!parseOption(arg, cmdLine, phase)) {
414  // next try the registered filetypes (xml)
415  if ((phase != PHASE_LAST) ||
416  !parseFileName(arg, cmdLine)) {
417  // no option or known file
418  backupCmdLine.push_back(arg);
419  auto it1 = optionMap.find(arg);
420  if (it1 != optionMap.end()) {
421  for (unsigned i = 0; i < it1->second.length - 1; ++i) {
422  if (!cmdLine.empty()) {
423  backupCmdLine.push_back(std::move(cmdLine.front()));
424  cmdLine.pop_front();
425  }
426  }
427  }
428  }
429  }
430  }
431  cmdLine = backupCmdLine;
432  backupCmdLine.clear();
433  break;
434  }
435  }
436  if (!cmdLine.empty() && (parseStatus != EXIT)) {
437  throw FatalError(
438  "Error parsing command line: " + cmdLine.front() + "\n" +
439  "Use \"openmsx -h\" to see a list of available options" );
440  }
441 
442  hiddenStartup = (parseStatus == CONTROL || parseStatus == TEST );
443 }
444 
446 {
447  return hiddenStartup;
448 }
449 
451 {
452  assert(parseStatus != UNPARSED);
453  return parseStatus;
454 }
455 
457 {
458  return scriptOption->getScripts();
459 }
460 
462 {
463  return reactor.getMotherBoard();
464 }
465 
467 {
468  return reactor.getGlobalCommandController();
469 }
470 
471 
472 // Control option
473 
475  : parser(parser_)
476 {
477 }
478 
479 void ControlOption::parseOption(const string& option, deque<string>& cmdLine)
480 {
481  const auto& fullType = getArgument(option, cmdLine);
482  string_ref type, arguments;
483  StringOp::splitOnFirst(fullType, ':', type, arguments);
484 
485  auto& controller = parser.getGlobalCommandController();
486  auto& distributor = parser.reactor.getEventDistributor();
487  auto& cliComm = parser.reactor.getGlobalCliComm();
488  std::unique_ptr<CliListener> connection;
489  if (type == "stdio") {
490  connection = make_unique<StdioConnection>(
491  controller, distributor);
492 #ifdef _WIN32
493  } else if (type == "pipe") {
494  OSVERSIONINFO info;
495  info.dwOSVersionInfoSize = sizeof(info);
496  GetVersionEx(&info);
497  if (info.dwPlatformId == VER_PLATFORM_WIN32_NT) {
498  connection = make_unique<PipeConnection>(
499  controller, distributor, arguments);
500  } else {
501  throw FatalError("Pipes are not supported on this "
502  "version of Windows");
503  }
504 #endif
505  } else {
506  throw FatalError("Unknown control type: '" + type + '\'');
507  }
508  cliComm.addListener(connection.release());
509 
510  parser.parseStatus = CommandLineParser::CONTROL;
511 }
512 
514 {
515  return "Enable external control of openMSX process";
516 }
517 
518 
519 // Script option
520 
522 {
523  return scripts;
524 }
525 
526 void ScriptOption::parseOption(const string& option, deque<string>& cmdLine)
527 {
528  parseFileType(getArgument(option, cmdLine), cmdLine);
529 }
530 
532 {
533  return "Run extra startup script";
534 }
535 
536 void ScriptOption::parseFileType(const string& filename,
537  deque<std::string>& /*cmdLine*/)
538 {
539  scripts.push_back(filename);
540 }
541 
543 {
544  return "Extra Tcl script to run at startup";
545 }
546 
547 // Help option
548 
549 static string formatSet(const set<string>& inputSet, string::size_type columns)
550 {
551  StringOp::Builder outString;
552  string::size_type totalLength = 0; // ignore the starting spaces for now
553  for (auto& temp : inputSet) {
554  if (totalLength == 0) {
555  // first element ?
556  outString << " " << temp;
557  totalLength = temp.size();
558  } else {
559  outString << ", ";
560  if ((totalLength + temp.size()) > columns) {
561  outString << "\n " << temp;
562  totalLength = temp.size();
563  } else {
564  outString << temp;
565  totalLength += 2 + temp.size();
566  }
567  }
568  }
569  if (totalLength < columns) {
570  outString << string(columns - totalLength, ' ');
571  }
572  return outString;
573 }
574 
575 static string formatHelptext(string_ref helpText,
576  unsigned maxLength, unsigned indent)
577 {
578  string outText;
579  string_ref::size_type index = 0;
580  while (helpText.substr(index).size() > maxLength) {
581  auto pos = helpText.substr(index, maxLength).rfind(' ');
582  if (pos == string_ref::npos) {
583  pos = helpText.substr(maxLength).find(' ');
584  if (pos == string_ref::npos) {
585  pos = helpText.substr(index).size();
586  }
587  }
588  outText += helpText.substr(index, index + pos) + '\n' +
589  string(indent, ' ');
590  index = pos + 1;
591  }
592  string_ref t = helpText.substr(index);
593  outText.append(t.data(), t.size());
594  return outText;
595 }
596 
597 static void printItemMap(const StringMap<set<string>>& itemMap)
598 {
599  set<string> printSet;
600  for (auto& p : itemMap) {
601  printSet.insert(formatSet(p.second, 15) + ' ' +
602  formatHelptext(p.first(), 50, 20));
603  }
604  for (auto& s : printSet) {
605  cout << s << endl;
606  }
607 }
608 
610  : parser(parser_)
611 {
612 }
613 
614 void HelpOption::parseOption(const string& /*option*/,
615  deque<string>& /*cmdLine*/)
616 {
617  const auto& fullVersion = Version::full();
618  cout << fullVersion << endl;
619  cout << string(fullVersion.size(), '=') << endl;
620  cout << endl;
621  cout << "usage: openmsx [arguments]" << endl;
622  cout << " an argument is either an option or a filename" << endl;
623  cout << endl;
624  cout << " this is the list of supported options:" << endl;
625 
626  StringMap<set<string>> optionMap;
627  for (auto& p : parser.optionMap) {
628  const auto& helpText = p.second.option->optionHelp();
629  if (!helpText.empty()) {
630  optionMap[helpText].insert(p.first);
631  }
632  }
633  printItemMap(optionMap);
634 
635  cout << endl;
636  cout << " this is the list of supported file types:" << endl;
637 
638  StringMap<set<string>> extMap;
639  for (auto& p : parser.fileTypeMap) {
640  extMap[p.second->fileTypeHelp()].insert(p.first);
641  }
642  printItemMap(extMap);
643 
644  parser.parseStatus = CommandLineParser::EXIT;
645 }
646 
648 {
649  return "Shows this text";
650 }
651 
652 
653 // class VersionOption
654 
656  : parser(parser_)
657 {
658 }
659 
660 void VersionOption::parseOption(const string& /*option*/,
661  deque<string>& /*cmdLine*/)
662 {
663  cout << Version::full() << endl;
664  cout << "flavour: " << BUILD_FLAVOUR << endl;
665  cout << "components: " << BUILD_COMPONENTS << endl;
666  parser.parseStatus = CommandLineParser::EXIT;
667 }
668 
670 {
671  return "Prints openMSX version and exits";
672 }
673 
674 
675 // Machine option
676 
678  : parser(parser_)
679 {
680 }
681 
682 void MachineOption::parseOption(const string& option, deque<string>& cmdLine)
683 {
684  if (parser.haveConfig) {
685  throw FatalError("Only one machine option allowed");
686  }
687  try {
688  parser.reactor.switchMachine(getArgument(option, cmdLine));
689  } catch (MSXException& e) {
690  throw FatalError(e.getMessage());
691  }
692  parser.haveConfig = true;
693 }
695 {
696  return "Use machine specified in argument";
697 }
698 
699 
700 // Setting Option
701 
703  : parser(parser_)
704 {
705 }
706 
707 void SettingOption::parseOption(const string& option, deque<string>& cmdLine)
708 {
709  if (parser.haveSettings) {
710  throw FatalError("Only one setting option allowed");
711  }
712  try {
713  auto& settingsConfig = parser.reactor.getGlobalCommandController().getSettingsConfig();
714  settingsConfig.loadSetting(
715  CurrentDirFileContext(), getArgument(option, cmdLine));
716  parser.haveSettings = true;
717  } catch (FileException& e) {
718  throw FatalError(e.getMessage());
719  } catch (ConfigException& e) {
720  throw FatalError(e.getMessage());
721  }
722 }
723 
725 {
726  return "Load an alternative settings file";
727 }
728 
729 
730 // class NoMMXOption
731 
732 void NoMMXOption::parseOption(const string& /*option*/,
733  deque<string>& /*cmdLine*/)
734 {
735  cout << "Disabling MMX" << endl;
737 }
738 
740 {
741  return "Disables usage of MMX, SSE and SSE2 (for debugging)";
742 }
743 
744 
745 // class NoSSEOption
746 
747 void NoSSEOption::parseOption(const string& /*option*/,
748  deque<string>& /*cmdLine*/)
749 {
750  cout << "Disabling SSE" << endl;
752 }
753 
755 {
756  return "Disables usage of SSE and SSE2 (for debugging)";
757 }
758 
759 
760 // class NoSSE2Option
761 
762 void NoSSE2Option::parseOption(const string& /*option*/,
763  deque<string>& /*cmdLine*/)
764 {
765  cout << "Disabling SSE2" << endl;
767 }
768 
770 {
771  return "Disables usage of SSE2 (for debugging)";
772 }
773 
774 
775 // class NoPBOOption
776 
777 void NoPBOOption::parseOption(const string& /*option*/,
778  deque<string>& /*cmdLine*/)
779 {
780  #if COMPONENT_GL
781  cout << "Disabling PBO" << endl;
782  PixelBuffers::enabled = false;
783  #endif
784 }
785 
787 {
788  return "Disables usage of openGL PBO (for debugging)";
789 }
790 
791 
792 // class TestConfigOption
793 
795  : parser(parser_)
796 {
797 }
798 
799 void TestConfigOption::parseOption(const string& /*option*/,
800  deque<string>& /*cmdLine*/)
801 {
802  parser.parseStatus = CommandLineParser::TEST;
803 }
804 
806 {
807  return "Test if the specified config works and exit";
808 }
809 
810 // class BashOption
811 
813  : parser(parser_)
814 {
815 }
816 
817 void BashOption::parseOption(const string& /*option*/,
818  deque<string>& cmdLine)
819 {
820  string last = cmdLine.empty() ? "" : cmdLine.front();
821  cmdLine.clear(); // eat all remaining parameters
822 
823  vector<string> items;
824  if (last == "-machine") {
825  items = Reactor::getHwConfigs("machines");
826  } else if (last == "-ext") {
827  items = Reactor::getHwConfigs("extensions");
828  } else if (last == "-romtype") {
829  items = RomInfo::getAllRomTypes();
830  } else {
831  for (auto& p : parser.optionMap) {
832  items.push_back(p.first);
833  }
834  }
835  for (auto& s : items) {
836  cout << s << '\n';
837  }
838  parser.parseStatus = CommandLineParser::EXIT;
839 }
840 
842 {
843  return ""; // don't include this option in --help
844 }
845 
846 } // namespace openmsx