options_heirarchy.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. // Copyright Thomas Kent 2016
  2. // Distributed under the Boost Software License, Version 1.0.
  3. // (See accompanying file LICENSE_1_0.txt
  4. // or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. //
  6. // This is an example of a program that uses multiple facets of the boost
  7. // program_options library. It will go through different types of config
  8. // options in a heirarchal manner:
  9. // 1. Default options are set.
  10. // 2. Command line options are set (they override defaults).
  11. // 3. Environment options are set (they override defaults but not command
  12. // line options).
  13. // 4. Config files specified on the command line are read, if present, in
  14. // the order specified. (these override defaults but not options from the
  15. // other steps).
  16. // 5. Default config file (default.cfg) is read, if present (it overrides
  17. // defaults but not options from the other steps).
  18. //
  19. // See the bottom of this file for full usage examples
  20. //
  21. #include <boost/program_options.hpp>
  22. namespace po = boost::program_options;
  23. #include <string>
  24. #include <iostream>
  25. #include <map>
  26. #include <stdexcept>
  27. #include <fstream>
  28. const std::string version("1.0");
  29. // Used to exit the program if the help/version option is set
  30. class OptionsExitsProgram : public std::exception
  31. {};
  32. struct GuiOpts
  33. {
  34. unsigned int width;
  35. unsigned int height;
  36. };
  37. struct NetworkOpts
  38. {
  39. std::string address;
  40. unsigned short port;
  41. };
  42. class OptionsHeirarchy
  43. {
  44. public:
  45. // The constructor sets up all the various options that will be parsed
  46. OptionsHeirarchy()
  47. {
  48. SetOptions();
  49. }
  50. // Parse options runs through the heirarchy doing all the parsing
  51. void ParseOptions(int argc, char* argv[])
  52. {
  53. ParseCommandLine(argc, argv);
  54. CheckForHelp();
  55. CheckForVersion();
  56. ParseEnvironment();
  57. ParseConfigFiles();
  58. ParseDefaultConfigFile();
  59. }
  60. // Below is the interface to access the data, once ParseOptions has been run
  61. std::string Path()
  62. {
  63. return results["path"].as<std::string>();
  64. }
  65. std::string Verbosity()
  66. {
  67. return results["verbosity"].as<std::string>();
  68. }
  69. std::vector<std::string> IncludePath()
  70. {
  71. if (results.count("include-path"))
  72. {
  73. return results["include-path"].as<std::vector<std::string>>();
  74. }
  75. return std::vector<std::string>();
  76. }
  77. std::string MasterFile()
  78. {
  79. if (results.count("master-file"))
  80. {
  81. return results["master-file"].as<std::string>();
  82. }
  83. return "";
  84. }
  85. std::vector<std::string> Files()
  86. {
  87. if (results.count("file"))
  88. {
  89. return results["file"].as<std::vector<std::string>>();
  90. }
  91. return std::vector<std::string>();
  92. }
  93. bool GUI()
  94. {
  95. if (results["run-gui"].as<bool>())
  96. {
  97. return true;
  98. }
  99. return false;
  100. }
  101. GuiOpts GuiValues()
  102. {
  103. GuiOpts opts;
  104. opts.width = results["gui.width"].as<unsigned int>();
  105. opts.height = results["gui.height"].as<unsigned int>();
  106. return opts;
  107. }
  108. NetworkOpts NetworkValues()
  109. {
  110. NetworkOpts opts;
  111. opts.address = results["network.ip"].as<std::string>();
  112. opts.port = results["network.port"].as<unsigned short>();
  113. return opts;
  114. }
  115. private:
  116. void SetOptions()
  117. {
  118. SetCommandLineOptions();
  119. SetCommonOptions();
  120. SetConfigOnlyOptions();
  121. SetEnvMapping();
  122. }
  123. void SetCommandLineOptions()
  124. {
  125. command_line_options.add_options()
  126. ("help,h", "display this help message")
  127. ("version,v", "show program version")
  128. ("config,c", po::value<std::vector<std::string>>(),
  129. "config files to parse (always parses default.cfg)")
  130. ;
  131. hidden_command_line_options.add_options()
  132. ("master-file", po::value<std::string>())
  133. ("file", po::value<std::vector<std::string>>())
  134. ;
  135. positional_options.add("master-file", 1);
  136. positional_options.add("file", -1);
  137. }
  138. void SetCommonOptions()
  139. {
  140. common_options.add_options()
  141. ("path", po::value<std::string>()->default_value(""),
  142. "the execution path to use (imports from environment if not specified)")
  143. ("verbosity", po::value<std::string>()->default_value("INFO"),
  144. "set verbosity: DEBUG, INFO, WARN, ERROR, FATAL")
  145. ("include-path,I", po::value<std::vector<std::string>>()->composing(),
  146. "paths to search for include files")
  147. ("run-gui", po::bool_switch(), "start the GUI")
  148. ;
  149. }
  150. void SetConfigOnlyOptions()
  151. {
  152. config_only_options.add_options()
  153. ("log-dir", po::value<std::string>()->default_value("log"))
  154. ("gui.height", po::value<unsigned int>()->default_value(100))
  155. ("gui.width", po::value<unsigned int>()->default_value(100))
  156. ("network.ip", po::value<std::string>()->default_value("127.0.0.1"))
  157. ("network.port", po::value<unsigned short>()->default_value(12345))
  158. ;
  159. // Run a parser here (with no command line options) to add these defaults into
  160. // results, this way they will be enabled even if no config files are parsed.
  161. store(po::command_line_parser(0, 0).options(config_only_options).run(), results);
  162. notify(results);
  163. }
  164. void SetEnvMapping()
  165. {
  166. env_to_option["PATH"] = "path";
  167. env_to_option["EXAMPLE_VERBOSE"] = "verbosity";
  168. }
  169. void ParseCommandLine(int argc, char* argv[])
  170. {
  171. po::options_description cmd_opts;
  172. cmd_opts.add(command_line_options).add(hidden_command_line_options).add(common_options);
  173. store(po::command_line_parser(argc, argv).
  174. options(cmd_opts).positional(positional_options).run(), results);
  175. notify(results);
  176. }
  177. void CheckForHelp()
  178. {
  179. if (results.count("help"))
  180. {
  181. PrintHelp();
  182. }
  183. }
  184. void PrintHelp()
  185. {
  186. std::cout << "Program Options Example" << std::endl;
  187. std::cout << "Usage: example [OPTION]... MASTER-FILE [FILE]...\n";
  188. std::cout << " or example [OPTION] --run-gui\n";
  189. po::options_description help_opts;
  190. help_opts.add(command_line_options).add(common_options);
  191. std::cout << help_opts << std::endl;
  192. throw OptionsExitsProgram();
  193. }
  194. void CheckForVersion()
  195. {
  196. if (results.count("version"))
  197. {
  198. PrintVersion();
  199. }
  200. }
  201. void PrintVersion()
  202. {
  203. std::cout << "Program Options Example " << version << std::endl;
  204. throw OptionsExitsProgram();
  205. }
  206. void ParseEnvironment()
  207. {
  208. store(po::parse_environment(common_options,
  209. // The next two lines are the crazy syntax to use EnvironmentMapper as
  210. // the lookup function for env->config name conversions
  211. boost::function1<std::string, std::string>(
  212. std::bind1st(std::mem_fun(&OptionsHeirarchy::EnvironmentMapper), this))),
  213. results);
  214. notify(results);
  215. }
  216. std::string EnvironmentMapper(std::string env_var)
  217. {
  218. // ensure the env_var is all caps
  219. std::transform(env_var.begin(), env_var.end(), env_var.begin(), ::toupper);
  220. auto entry = env_to_option.find(env_var);
  221. if (entry != env_to_option.end())
  222. {
  223. return entry->second;
  224. }
  225. return "";
  226. }
  227. void ParseConfigFiles()
  228. {
  229. if (results.count("config"))
  230. {
  231. auto files = results["config"].as<std::vector<std::string>>();
  232. for (auto file = files.begin(); file != files.end(); file++)
  233. {
  234. LoadAConfigFile(*file);
  235. }
  236. }
  237. }
  238. void LoadAConfigFile(std::string filename)
  239. {
  240. bool ALLOW_UNREGISTERED = true;
  241. po::options_description config_opts;
  242. config_opts.add(config_only_options).add(common_options);
  243. std::ifstream cfg_file(filename.c_str());
  244. if (cfg_file)
  245. {
  246. store(parse_config_file(cfg_file, config_opts, ALLOW_UNREGISTERED), results);
  247. notify(results);
  248. }
  249. }
  250. void ParseDefaultConfigFile()
  251. {
  252. LoadAConfigFile("default.cfg");
  253. }
  254. std::map<std::string, std::string> env_to_option;
  255. po::options_description config_only_options;
  256. po::options_description common_options;
  257. po::options_description command_line_options;
  258. po::options_description hidden_command_line_options;
  259. po::positional_options_description positional_options;
  260. po::variables_map results;
  261. };
  262. void get_env_options()
  263. {
  264. }
  265. void PrintOptions(OptionsHeirarchy options)
  266. {
  267. auto path = options.Path();
  268. if (path.length())
  269. {
  270. std::cout << "First 75 chars of the system path: \n";
  271. std::cout << options.Path().substr(0, 75) << std::endl;
  272. }
  273. std::cout << "Verbosity: " << options.Verbosity() << std::endl;
  274. std::cout << "Include Path:\n";
  275. auto includePaths = options.IncludePath();
  276. for (auto path = includePaths.begin(); path != includePaths.end(); path++)
  277. {
  278. std::cout << " " << *path << std::endl;
  279. }
  280. std::cout << "Master-File: " << options.MasterFile() << std::endl;
  281. std::cout << "Additional Files:\n";
  282. auto files = options.Files();
  283. for (auto file = files.begin(); file != files.end(); file++)
  284. {
  285. std::cout << " " << *file << std::endl;
  286. }
  287. std::cout << "GUI Enabled: " << std::boolalpha << options.GUI() << std::endl;
  288. if (options.GUI())
  289. {
  290. auto gui_values = options.GuiValues();
  291. std::cout << "GUI Height: " << gui_values.height << std::endl;
  292. std::cout << "GUI Width: " << gui_values.width << std::endl;
  293. }
  294. auto network_values = options.NetworkValues();
  295. std::cout << "Network Address: " << network_values.address << std::endl;
  296. std::cout << "Network Port: " << network_values.port << std::endl;
  297. }
  298. int main(int ac, char* av[])
  299. {
  300. OptionsHeirarchy options;
  301. try
  302. {
  303. options.ParseOptions(ac, av);
  304. PrintOptions(options);
  305. }
  306. catch (OptionsExitsProgram){}
  307. return 0;
  308. }
  309. /*
  310. Full Usage Examples
  311. ===================
  312. These were run on windows, so some results may show that environment, but
  313. results should be similar on POSIX platforms.
  314. Help
  315. ----
  316. To see the help screen, with the available options just pass the --help (or -h)
  317. parameter. The program will then exit.
  318. > example.exe --help
  319. Program Options Example
  320. Usage: example [OPTION]... MASTER-FILE [FILE]...
  321. or example [OPTION] --run-gui
  322. -h [ --help ] display this help message
  323. -v [ --version ] show program version
  324. -c [ --config ] arg config files to parse (always parses default.cfg)
  325. --path arg the execution path to use (imports from
  326. environment if not specified)
  327. --verbosity arg (=INFO) set verbosity: DEBUG, INFO, WARN, ERROR, FATAL
  328. -I [ --include-path ] arg paths to search for include files
  329. --run-gui start the GUI
  330. Version is similar to help (--version or -v).
  331. > example.exe -v
  332. Program Options Example 1.0
  333. Basics
  334. ------
  335. Running without any options will get the default values (path is set from the
  336. environment):
  337. > example.exe
  338. First 75 chars of the system path:
  339. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  340. Verbosity: INFO
  341. Include Path:
  342. Master-File:
  343. Additional Files:
  344. GUI Enabled: false
  345. Network Address: 127.0.0.1
  346. Network Port: 12345
  347. We can easily override that environment path with a simple option:
  348. > example.exe --path a/b/c;d/e/f
  349. First 75 chars of the system path:
  350. a/b/c;d/e/f
  351. Verbosity: INFO
  352. Include Path:
  353. Master-File:
  354. Additional Files:
  355. GUI Enabled: false
  356. Network Address: 127.0.0.1
  357. Network Port: 12345
  358. You can use a space or equals sign after long options, also backslashes are
  359. treated literally on windows, on POSIX they need to be escaped.
  360. > example.exe --path=a\b\c\;d\e\\f
  361. First 75 chars of the system path:
  362. a\b\c\;d\e\\f
  363. Verbosity: INFO
  364. Include Path:
  365. Master-File:
  366. Additional Files:
  367. GUI Enabled: false
  368. Network Address: 127.0.0.1
  369. Network Port: 12345
  370. For short options you can use a space:
  371. > example.exe -I path/to/includes
  372. First 75 chars of the system path:
  373. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  374. Verbosity: INFO
  375. Include Path:
  376. path\to\includes
  377. Master-File:
  378. Additional Files:
  379. GUI Enabled: false
  380. Network Address: 127.0.0.1
  381. Network Port: 12345
  382. Or you can put the option immediately after it:
  383. > example.exe -Ipath/to/includes
  384. First 75 chars of the system path:
  385. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  386. Verbosity: INFO
  387. Include Path:
  388. path\to\includes
  389. Master-File:
  390. Additional Files:
  391. GUI Enabled: false
  392. Network Address: 127.0.0.1
  393. Network Port: 12345
  394. The include path (--include-path or -I) option allows for multiple paths to be
  395. specified (both on the command line and in config files) and combined into a
  396. vector for use by the program.
  397. > example.exe --include-path=a/b/c --include-path d/e/f -I g/h/i -Ij/k/l
  398. First 75 chars of the system path:
  399. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  400. Verbosity: INFO
  401. Include Path:
  402. a/b/c
  403. d/e/f
  404. g/h/i
  405. j/k/l
  406. Master-File:
  407. Additional Files:
  408. GUI Enabled: false
  409. Network Address: 127.0.0.1
  410. Network Port: 12345
  411. There are also the option of flags that do not take parameters and just set a
  412. boolean value to true. In this case, running the gui also causes default values
  413. for the gui to be output to the screen.
  414. > example.exe --run-gui
  415. First 75 chars of the system path:
  416. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  417. Verbosity: INFO
  418. Include Path:
  419. Master-File:
  420. Additional Files:
  421. GUI Enabled: true
  422. GUI Height: 100
  423. GUI Width: 100
  424. Network Address: 127.0.0.1
  425. Network Port: 12345
  426. There are also "positional" options at the end of the command line. The first
  427. one specifies the "master" file the others are additional files.
  428. > example.exe --path=a-path -I an-include master.cpp additional1.cpp additional2.cpp
  429. First 75 chars of the system path:
  430. a-path
  431. Verbosity: INFO
  432. Include Path:
  433. an-include
  434. Master-File: master.cpp
  435. Additional Files:
  436. additional1.cpp
  437. additional2.cpp
  438. GUI Enabled: false
  439. Network Address: 127.0.0.1
  440. Network Port: 12345
  441. Environment Variables
  442. ---------------------
  443. In addition to the PATH environment variable, it also knows how to read the
  444. EXAMPLE_VERBOSE environmental variable and use that to set the verbosity
  445. option/
  446. > set EXAMPLE_VERBOSE=DEBUG
  447. > example.exe
  448. First 75 chars of the system path:
  449. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  450. Verbosity: DEBUG
  451. Include Path:
  452. Master-File:
  453. Additional Files:
  454. GUI Enabled: false
  455. Network Address: 127.0.0.1
  456. Network Port: 12345
  457. However, if the --verboseity flag is also set, it will override the env
  458. variable. This illustrates an important example, the way program_options works,
  459. is that a parser will not override a value that has previously been set by
  460. another parser. Thus the env parser doesn't override the command line parser.
  461. (We will see this again in config files.) Default values are seperate from this
  462. heirarcy, they only apply if no parser has set the value and it is being read.
  463. > set EXAMPLE_VERBOSE=DEBUG
  464. > example.exe --verbosity=WARN
  465. First 75 chars of the system path:
  466. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  467. Verbosity: WARN
  468. Include Path:
  469. Master-File:
  470. Additional Files:
  471. GUI Enabled: false
  472. Network Address: 127.0.0.1
  473. Network Port: 12345
  474. (You can unset an environmental variable with an empty set command)
  475. > set EXAMPLE_VERBOSE=
  476. > example.exe
  477. First 75 chars of the system path:
  478. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  479. Verbosity: INFO
  480. Include Path:
  481. Master-File:
  482. Additional Files:
  483. GUI Enabled: false
  484. Network Address: 127.0.0.1
  485. Network Port: 12345
  486. Config Files
  487. ------------
  488. Config files generally follow the [INI file format]
  489. (https://en.wikipedia.org/wiki/INI_file) with a few exceptions.
  490. Values can be simply added tp options with an equal sign. Here are two include
  491. paths added via the default config file (default.cfg), you can have optional
  492. spaces around the equal sign.
  493. # You can use comments in a config file
  494. include-path=first/default/path
  495. include-path = second/default/path
  496. Results in
  497. > example.exe
  498. First 75 chars of the system path:
  499. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  500. Verbosity: INFO
  501. Include Path:
  502. first/default/path
  503. second/default/path
  504. Master-File:
  505. Additional Files:
  506. GUI Enabled: false
  507. Network Address: 127.0.0.1
  508. Network Port: 12345
  509. Values can also be in sections of the config file. Again, editing default.cfg
  510. include-path=first/default/path
  511. include-path = second/default/path
  512. [network]
  513. ip=1.2.3.4
  514. port=3000
  515. Results in
  516. > example.exe
  517. First 75 chars of the system path:
  518. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  519. Verbosity: INFO
  520. Include Path:
  521. first/default/path
  522. second/default/path
  523. Master-File:
  524. Additional Files:
  525. GUI Enabled: false
  526. Network Address: 1.2.3.4
  527. Network Port: 3000
  528. This example is also setup to allow multiple config files to be specified on
  529. the command line, which are checked before the default.cfg file is read (but
  530. after the environment and command line parsing). Thus we can set the first.cfg
  531. file to contain the following:
  532. verbosity=ERROR
  533. [network]
  534. ip = 5.6.7.8
  535. Results in:
  536. > example.exe --config first.cfg
  537. First 75 chars of the system path:
  538. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  539. Verbosity: ERROR
  540. Include Path:
  541. first/default/path
  542. second/default/path
  543. Master-File:
  544. Additional Files:
  545. GUI Enabled: false
  546. Network Address: 5.6.7.8
  547. Network Port: 3000
  548. But since the config files are read after the command line, setting the
  549. verbosity there causes the value in the file to be ignored.
  550. > example.exe --config first.cfg --verbosity=WARN
  551. First 75 chars of the system path:
  552. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  553. Verbosity: WARN
  554. Include Path:
  555. first/default/path
  556. second/default/path
  557. Master-File:
  558. Additional Files:
  559. GUI Enabled: false
  560. Network Address: 5.6.7.8
  561. Network Port: 3000
  562. The config files are parsed in the order they are received on the command line.
  563. So adding the second.cfg file:
  564. verbosity=FATAL
  565. run-gui=true
  566. [gui]
  567. height=720
  568. width=1280
  569. Results in a combination of all three config files:
  570. > example.exe --config first.cfg --config second.cfg
  571. First 75 chars of the system path:
  572. C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
  573. Verbosity: ERROR
  574. Include Path:
  575. first/default/path
  576. second/default/path
  577. Master-File:
  578. Additional Files:
  579. GUI Enabled: true
  580. GUI Height: 720
  581. GUI Width: 1280
  582. Network Address: 5.6.7.8
  583. Network Port: 3000
  584. Incidently the boolean run-gui option could have been set a number of ways
  585. that all result in the C++ boolean value of true:
  586. run-gui=true
  587. run-gui=on
  588. run-gui=1
  589. run-gui=yes
  590. run-gui=
  591. Since run-gui is an option that was set with the bool_switch type, which
  592. forces its use on the command line without a parameter (i.e. --run-gui instead
  593. of --run-gui=true) it can't be given a "false" option, bool_switch values can
  594. only be turned true. If instead we had a value ("my-switch", po::value<bool>())
  595. that could be set at the command line --my-switch=true or --my-switch=false, or
  596. any of the other types of boolean keywords true: true, on, 1, yes;
  597. false: false, off, 0, no. In a config file this could look like:
  598. my-switch=true
  599. my-switch=on
  600. my-switch=1
  601. my-switch=yes
  602. my-switch=
  603. my-switch=false
  604. my-switch=off
  605. my-switch=0
  606. my-switch=no
  607. */