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