// 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 namespace po = boost::program_options; #include #include #include #include #include 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 Verbosity() { return results["verbosity"].as(); } std::vector IncludePath() { if (results.count("include-path")) { return results["include-path"].as>(); } return std::vector(); } std::string MasterFile() { if (results.count("master-file")) { return results["master-file"].as(); } return ""; } std::vector Files() { if (results.count("file")) { return results["file"].as>(); } return std::vector(); } bool GUI() { if (results["run-gui"].as()) { return true; } return false; } GuiOpts GuiValues() { GuiOpts opts; opts.width = results["gui.width"].as(); opts.height = results["gui.height"].as(); return opts; } NetworkOpts NetworkValues() { NetworkOpts opts; opts.address = results["network.ip"].as(); opts.port = results["network.port"].as(); 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>(), "config files to parse (always parses default.cfg)") ; hidden_command_line_options.add_options() ("master-file", po::value()) ("file", po::value>()) ; positional_options.add("master-file", 1); positional_options.add("file", -1); } void SetCommonOptions() { common_options.add_options() ("path", po::value()->default_value(""), "the execution path to use (imports from environment if not specified)") ("verbosity", po::value()->default_value("INFO"), "set verbosity: DEBUG, INFO, WARN, ERROR, FATAL") ("include-path,I", po::value>()->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()->default_value("log")) ("gui.height", po::value()->default_value(100)) ("gui.width", po::value()->default_value(100)) ("network.ip", po::value()->default_value("127.0.0.1")) ("network.port", po::value()->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::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>(); 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 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()) 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 */