123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690 |
- // Copyright Thomas Kent 2016
- // Distributed under the Boost Software License, Version 1.0.
- // (See accompanying file LICENSE_1_0.txt
- // or copy at http://www.boost.org/LICENSE_1_0.txt)
- //
- // This is an example of a program that uses multiple facets of the boost
- // program_options library. It will go through different types of config
- // options in a heirarchal manner:
- // 1. Default options are set.
- // 2. Command line options are set (they override defaults).
- // 3. Environment options are set (they override defaults but not command
- // line options).
- // 4. Config files specified on the command line are read, if present, in
- // the order specified. (these override defaults but not options from the
- // other steps).
- // 5. Default config file (default.cfg) is read, if present (it overrides
- // defaults but not options from the other steps).
- //
- // See the bottom of this file for full usage examples
- //
- #include <boost/program_options.hpp>
- namespace po = boost::program_options;
- #include <string>
- #include <iostream>
- #include <map>
- #include <stdexcept>
- #include <fstream>
- const std::string version("1.0");
- // Used to exit the program if the help/version option is set
- class OptionsExitsProgram : public std::exception
- {};
- struct GuiOpts
- {
- unsigned int width;
- unsigned int height;
- };
- struct NetworkOpts
- {
- std::string address;
- unsigned short port;
- };
- class OptionsHeirarchy
- {
- public:
- // The constructor sets up all the various options that will be parsed
- OptionsHeirarchy()
- {
- SetOptions();
- }
- // Parse options runs through the heirarchy doing all the parsing
- void ParseOptions(int argc, char* argv[])
- {
- ParseCommandLine(argc, argv);
- CheckForHelp();
- CheckForVersion();
- ParseEnvironment();
- ParseConfigFiles();
- ParseDefaultConfigFile();
- }
- // Below is the interface to access the data, once ParseOptions has been run
- std::string Path()
- {
- return results["path"].as<std::string>();
- }
- std::string Verbosity()
- {
- return results["verbosity"].as<std::string>();
- }
- std::vector<std::string> IncludePath()
- {
- if (results.count("include-path"))
- {
- return results["include-path"].as<std::vector<std::string>>();
- }
- return std::vector<std::string>();
- }
- std::string MasterFile()
- {
- if (results.count("master-file"))
- {
- return results["master-file"].as<std::string>();
- }
- return "";
- }
- std::vector<std::string> Files()
- {
- if (results.count("file"))
- {
- return results["file"].as<std::vector<std::string>>();
- }
- return std::vector<std::string>();
- }
- bool GUI()
- {
- if (results["run-gui"].as<bool>())
- {
- return true;
- }
- return false;
- }
- GuiOpts GuiValues()
- {
- GuiOpts opts;
- opts.width = results["gui.width"].as<unsigned int>();
- opts.height = results["gui.height"].as<unsigned int>();
- return opts;
- }
- NetworkOpts NetworkValues()
- {
- NetworkOpts opts;
- opts.address = results["network.ip"].as<std::string>();
- opts.port = results["network.port"].as<unsigned short>();
- return opts;
- }
- private:
- void SetOptions()
- {
- SetCommandLineOptions();
- SetCommonOptions();
- SetConfigOnlyOptions();
- SetEnvMapping();
- }
- void SetCommandLineOptions()
- {
- command_line_options.add_options()
- ("help,h", "display this help message")
- ("version,v", "show program version")
- ("config,c", po::value<std::vector<std::string>>(),
- "config files to parse (always parses default.cfg)")
- ;
- hidden_command_line_options.add_options()
- ("master-file", po::value<std::string>())
- ("file", po::value<std::vector<std::string>>())
- ;
- positional_options.add("master-file", 1);
- positional_options.add("file", -1);
- }
- void SetCommonOptions()
- {
- common_options.add_options()
- ("path", po::value<std::string>()->default_value(""),
- "the execution path to use (imports from environment if not specified)")
- ("verbosity", po::value<std::string>()->default_value("INFO"),
- "set verbosity: DEBUG, INFO, WARN, ERROR, FATAL")
- ("include-path,I", po::value<std::vector<std::string>>()->composing(),
- "paths to search for include files")
- ("run-gui", po::bool_switch(), "start the GUI")
- ;
- }
- void SetConfigOnlyOptions()
- {
- config_only_options.add_options()
- ("log-dir", po::value<std::string>()->default_value("log"))
- ("gui.height", po::value<unsigned int>()->default_value(100))
- ("gui.width", po::value<unsigned int>()->default_value(100))
- ("network.ip", po::value<std::string>()->default_value("127.0.0.1"))
- ("network.port", po::value<unsigned short>()->default_value(12345))
- ;
- // Run a parser here (with no command line options) to add these defaults into
- // results, this way they will be enabled even if no config files are parsed.
- store(po::command_line_parser(0, 0).options(config_only_options).run(), results);
- notify(results);
- }
- void SetEnvMapping()
- {
- env_to_option["PATH"] = "path";
- env_to_option["EXAMPLE_VERBOSE"] = "verbosity";
- }
- void ParseCommandLine(int argc, char* argv[])
- {
- po::options_description cmd_opts;
- cmd_opts.add(command_line_options).add(hidden_command_line_options).add(common_options);
- store(po::command_line_parser(argc, argv).
- options(cmd_opts).positional(positional_options).run(), results);
- notify(results);
- }
- void CheckForHelp()
- {
- if (results.count("help"))
- {
- PrintHelp();
- }
- }
- void PrintHelp()
- {
- std::cout << "Program Options Example" << std::endl;
- std::cout << "Usage: example [OPTION]... MASTER-FILE [FILE]...\n";
- std::cout << " or example [OPTION] --run-gui\n";
- po::options_description help_opts;
- help_opts.add(command_line_options).add(common_options);
- std::cout << help_opts << std::endl;
- throw OptionsExitsProgram();
- }
- void CheckForVersion()
- {
- if (results.count("version"))
- {
- PrintVersion();
- }
- }
- void PrintVersion()
- {
- std::cout << "Program Options Example " << version << std::endl;
- throw OptionsExitsProgram();
- }
- void ParseEnvironment()
- {
- store(po::parse_environment(common_options,
- // The next two lines are the crazy syntax to use EnvironmentMapper as
- // the lookup function for env->config name conversions
- boost::function1<std::string, std::string>(
- std::bind1st(std::mem_fun(&OptionsHeirarchy::EnvironmentMapper), this))),
- results);
- notify(results);
- }
- std::string EnvironmentMapper(std::string env_var)
- {
- // ensure the env_var is all caps
- std::transform(env_var.begin(), env_var.end(), env_var.begin(), ::toupper);
- auto entry = env_to_option.find(env_var);
- if (entry != env_to_option.end())
- {
- return entry->second;
- }
- return "";
- }
- void ParseConfigFiles()
- {
- if (results.count("config"))
- {
- auto files = results["config"].as<std::vector<std::string>>();
- for (auto file = files.begin(); file != files.end(); file++)
- {
- LoadAConfigFile(*file);
- }
- }
- }
- void LoadAConfigFile(std::string filename)
- {
- bool ALLOW_UNREGISTERED = true;
- po::options_description config_opts;
- config_opts.add(config_only_options).add(common_options);
- std::ifstream cfg_file(filename.c_str());
- if (cfg_file)
- {
- store(parse_config_file(cfg_file, config_opts, ALLOW_UNREGISTERED), results);
- notify(results);
- }
- }
- void ParseDefaultConfigFile()
- {
- LoadAConfigFile("default.cfg");
- }
- std::map<std::string, std::string> env_to_option;
- po::options_description config_only_options;
- po::options_description common_options;
- po::options_description command_line_options;
- po::options_description hidden_command_line_options;
- po::positional_options_description positional_options;
- po::variables_map results;
- };
- void get_env_options()
- {
- }
- void PrintOptions(OptionsHeirarchy options)
- {
- auto path = options.Path();
- if (path.length())
- {
- std::cout << "First 75 chars of the system path: \n";
- std::cout << options.Path().substr(0, 75) << std::endl;
- }
- std::cout << "Verbosity: " << options.Verbosity() << std::endl;
- std::cout << "Include Path:\n";
- auto includePaths = options.IncludePath();
- for (auto path = includePaths.begin(); path != includePaths.end(); path++)
- {
- std::cout << " " << *path << std::endl;
- }
- std::cout << "Master-File: " << options.MasterFile() << std::endl;
- std::cout << "Additional Files:\n";
- auto files = options.Files();
- for (auto file = files.begin(); file != files.end(); file++)
- {
- std::cout << " " << *file << std::endl;
- }
- std::cout << "GUI Enabled: " << std::boolalpha << options.GUI() << std::endl;
- if (options.GUI())
- {
- auto gui_values = options.GuiValues();
- std::cout << "GUI Height: " << gui_values.height << std::endl;
- std::cout << "GUI Width: " << gui_values.width << std::endl;
- }
- auto network_values = options.NetworkValues();
- std::cout << "Network Address: " << network_values.address << std::endl;
- std::cout << "Network Port: " << network_values.port << std::endl;
- }
- int main(int ac, char* av[])
- {
- OptionsHeirarchy options;
- try
- {
- options.ParseOptions(ac, av);
- PrintOptions(options);
- }
- catch (OptionsExitsProgram){}
- return 0;
- }
- /*
- Full Usage Examples
- ===================
- These were run on windows, so some results may show that environment, but
- results should be similar on POSIX platforms.
- Help
- ----
- To see the help screen, with the available options just pass the --help (or -h)
- parameter. The program will then exit.
- > example.exe --help
- Program Options Example
- Usage: example [OPTION]... MASTER-FILE [FILE]...
- or example [OPTION] --run-gui
- -h [ --help ] display this help message
- -v [ --version ] show program version
- -c [ --config ] arg config files to parse (always parses default.cfg)
- --path arg the execution path to use (imports from
- environment if not specified)
- --verbosity arg (=INFO) set verbosity: DEBUG, INFO, WARN, ERROR, FATAL
- -I [ --include-path ] arg paths to search for include files
- --run-gui start the GUI
- Version is similar to help (--version or -v).
- > example.exe -v
- Program Options Example 1.0
- Basics
- ------
- Running without any options will get the default values (path is set from the
- environment):
- > example.exe
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- We can easily override that environment path with a simple option:
- > example.exe --path a/b/c;d/e/f
- First 75 chars of the system path:
- a/b/c;d/e/f
- Verbosity: INFO
- Include Path:
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- You can use a space or equals sign after long options, also backslashes are
- treated literally on windows, on POSIX they need to be escaped.
- > example.exe --path=a\b\c\;d\e\\f
- First 75 chars of the system path:
- a\b\c\;d\e\\f
- Verbosity: INFO
- Include Path:
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- For short options you can use a space:
- > example.exe -I path/to/includes
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- path\to\includes
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- Or you can put the option immediately after it:
- > example.exe -Ipath/to/includes
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- path\to\includes
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- The include path (--include-path or -I) option allows for multiple paths to be
- specified (both on the command line and in config files) and combined into a
- vector for use by the program.
- > example.exe --include-path=a/b/c --include-path d/e/f -I g/h/i -Ij/k/l
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- a/b/c
- d/e/f
- g/h/i
- j/k/l
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- There are also the option of flags that do not take parameters and just set a
- boolean value to true. In this case, running the gui also causes default values
- for the gui to be output to the screen.
- > example.exe --run-gui
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- Master-File:
- Additional Files:
- GUI Enabled: true
- GUI Height: 100
- GUI Width: 100
- Network Address: 127.0.0.1
- Network Port: 12345
- There are also "positional" options at the end of the command line. The first
- one specifies the "master" file the others are additional files.
- > example.exe --path=a-path -I an-include master.cpp additional1.cpp additional2.cpp
- First 75 chars of the system path:
- a-path
- Verbosity: INFO
- Include Path:
- an-include
- Master-File: master.cpp
- Additional Files:
- additional1.cpp
- additional2.cpp
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- Environment Variables
- ---------------------
- In addition to the PATH environment variable, it also knows how to read the
- EXAMPLE_VERBOSE environmental variable and use that to set the verbosity
- option/
- > set EXAMPLE_VERBOSE=DEBUG
- > example.exe
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: DEBUG
- Include Path:
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- However, if the --verboseity flag is also set, it will override the env
- variable. This illustrates an important example, the way program_options works,
- is that a parser will not override a value that has previously been set by
- another parser. Thus the env parser doesn't override the command line parser.
- (We will see this again in config files.) Default values are seperate from this
- heirarcy, they only apply if no parser has set the value and it is being read.
- > set EXAMPLE_VERBOSE=DEBUG
- > example.exe --verbosity=WARN
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: WARN
- Include Path:
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- (You can unset an environmental variable with an empty set command)
-
- > set EXAMPLE_VERBOSE=
- > example.exe
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- Config Files
- ------------
- Config files generally follow the [INI file format]
- (https://en.wikipedia.org/wiki/INI_file) with a few exceptions.
- Values can be simply added tp options with an equal sign. Here are two include
- paths added via the default config file (default.cfg), you can have optional
- spaces around the equal sign.
- # You can use comments in a config file
- include-path=first/default/path
- include-path = second/default/path
- Results in
- > example.exe
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- first/default/path
- second/default/path
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 127.0.0.1
- Network Port: 12345
- Values can also be in sections of the config file. Again, editing default.cfg
- include-path=first/default/path
- include-path = second/default/path
- [network]
- ip=1.2.3.4
- port=3000
- Results in
- > example.exe
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: INFO
- Include Path:
- first/default/path
- second/default/path
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 1.2.3.4
- Network Port: 3000
- This example is also setup to allow multiple config files to be specified on
- the command line, which are checked before the default.cfg file is read (but
- after the environment and command line parsing). Thus we can set the first.cfg
- file to contain the following:
- verbosity=ERROR
- [network]
- ip = 5.6.7.8
- Results in:
- > example.exe --config first.cfg
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: ERROR
- Include Path:
- first/default/path
- second/default/path
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 5.6.7.8
- Network Port: 3000
- But since the config files are read after the command line, setting the
- verbosity there causes the value in the file to be ignored.
- > example.exe --config first.cfg --verbosity=WARN
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: WARN
- Include Path:
- first/default/path
- second/default/path
- Master-File:
- Additional Files:
- GUI Enabled: false
- Network Address: 5.6.7.8
- Network Port: 3000
- The config files are parsed in the order they are received on the command line.
- So adding the second.cfg file:
- verbosity=FATAL
- run-gui=true
- [gui]
- height=720
- width=1280
- Results in a combination of all three config files:
- > example.exe --config first.cfg --config second.cfg
- First 75 chars of the system path:
- C:\Program Files (x86)\MSBuild\14.0\bin;C:\Perl\site\bin;C:\Perl\bin;C:\Pro
- Verbosity: ERROR
- Include Path:
- first/default/path
- second/default/path
- Master-File:
- Additional Files:
- GUI Enabled: true
- GUI Height: 720
- GUI Width: 1280
- Network Address: 5.6.7.8
- Network Port: 3000
- Incidently the boolean run-gui option could have been set a number of ways
- that all result in the C++ boolean value of true:
- run-gui=true
- run-gui=on
- run-gui=1
- run-gui=yes
- run-gui=
- Since run-gui is an option that was set with the bool_switch type, which
- forces its use on the command line without a parameter (i.e. --run-gui instead
- of --run-gui=true) it can't be given a "false" option, bool_switch values can
- only be turned true. If instead we had a value ("my-switch", po::value<bool>())
- that could be set at the command line --my-switch=true or --my-switch=false, or
- any of the other types of boolean keywords true: true, on, 1, yes;
- false: false, off, 0, no. In a config file this could look like:
- my-switch=true
- my-switch=on
- my-switch=1
- my-switch=yes
- my-switch=
- my-switch=false
- my-switch=off
- my-switch=0
- my-switch=no
- */
|