Option++  2.0
C++ library for reading command-line options
Example Programs

Various example programs, each consisting of a single file.

The examples can also be found in docs/examples under the root project directory.

Introductory Example

A basic program that shows off some of Option++'s features.

#include <iostream>
#include <string>
struct Settings {
bool show_help {false};
bool show_version {false};
bool verbose {false};
std::string output_file;
};
void print_version() {
std::cout << "My Program 1.0" << std::endl;
}
int main(int argc, char* argv[]) {
Settings settings;
parser opt_parser;
// Set up options
opt_parser["help"].short_name('?')
.description("Show help information")
.bind_bool(&settings.show_help);
opt_parser["version"]
.description("Show program version information")
.bind_bool(&settings.show_version);
opt_parser["verbose"].short_name('v')
.description("Display additional explanations")
.bind_bool(&settings.verbose);
opt_parser["output"].short_name('o')
.description("File to write output")
.bind_string(&settings.output_file);
parser_result result;
// Parse options
try {
result = opt_parser.parse(argc, argv);
} catch(const optionpp::error& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
// Show help/version info
if (settings.show_help) {
print_version();
std::cout << "This program does important stuff.\n\n"
<< "My Program accepts the following options:\n"
<< opt_parser << std::endl;
return 0;
} else if (settings.show_version) {
print_version();
return 0;
}
if (!settings.output_file.empty()) {
if (settings.verbose)
std::cout << "Writing output to '"
<< settings.output_file
<< "'" << std::endl;
// Write output...
} else {
if (settings.verbose)
std::cout << "Writing output to standard out" << std::endl;
// Write output...
}
// Iterate over non-option arguments
for (const auto& entry : non_option_const_iterator(result)) {
if (!entry.is_option)
std::cout << "Received argument '"
<< entry.original_text
<< "'" << std::endl;
}
return 0;
}

Sample input/output:

prompt$ example_basic hello world
Received argument 'hello'
Received argument 'world'
prompt$ example_basic hello -v --output="My File.txt"
Writing output to 'My File.txt'
Received argument 'hello'

A grep Clone - Full Working Example

An unoptimized and highly-simplified version of the Unix grep utility for regular expression pattern matching.

/*
* example_mygrep -- Simple grep clone to demonstrate Option++
*
* Run example_mygrep --help for usage information.
*/
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
using std::cout;
using std::cerr;
using std::endl;
using std::ifstream;
using std::regex;
using std::string;
using std::vector;
enum class PatternType { basic_regex, extended_regex };
struct Options {
// Pattern options
PatternType type { PatternType::basic_regex };
vector<string> files;
vector<string> patterns;
string pattern_file;
bool ignore_case{};
// Miscellaneous options
bool suppress_errors{};
bool invert_match{};
bool show_version{};
bool show_help{};
// Output options
unsigned max_lines{};
bool limit_lines{};
bool quiet{};
bool count_only{};
};
template <typename InputIt>
void read_patterns_from_file(const string& filename,
InputIt dest);
bool match_file(const string& filename, const Options& opts);
bool does_line_match(const string& line, const Options& opts);
int main(int argc, char* argv[]) {
const string usage{"mygrep [OPTION]... PATTERNS [FILE]..."};
Options opts;
parser opt_parser;
// Set up the options
auto& pattern_group = opt_parser.group("Pattern selection and interpretation:");
pattern_group["extended-regexp"].short_name('E')
.description("PATTERNS are extended regular expressions");
pattern_group["basic-regexp"].short_name('G')
.description("PATTERNS are basic regular expressions (default)");
pattern_group["regexp"].short_name('e').argument("PATTERNS", true)
.description("Use PATTERNS for matching");
pattern_group["file"].short_name('f').argument("FILE", true)
.description("take PATTERNS from FILE").bind_string(&opts.pattern_file);
pattern_group["ignore-case"].short_name('i')
.description("ignore case distinctions in patterns and data");
pattern_group["no-ignore-case"]
.description("do not ignore case distinctions (default)");
auto& misc_group = opt_parser.group("Miscellaneous:");
misc_group["no-message"].short_name('s').bind_bool(&opts.suppress_errors)
.description("suppress error messages");
misc_group["invert-match"].short_name('v').bind_bool(&opts.invert_match)
.description("select non-matching lines");
misc_group["version"].short_name('V').bind_bool(&opts.show_version)
.description("display version information and exit");
misc_group["help"].bind_bool(&opts.show_help)
.description("display this help text and exit");
auto& output_group = opt_parser.group("Output control:");
output_group["max-count"].short_name('m').argument("NUM", true)
.bind_uint(&opts.max_lines).bind_bool(&opts.limit_lines)
.description("stop after NUM selected lines");
output_group["quiet"].short_name('q').bind_bool(&opts.quiet)
.description("suppress all normal output");
output_group["count"].short_name('c').bind_bool(&opts.count_only)
.description("print only a count of selected lines per FILE");
try {
result = opt_parser.parse(argc, argv);
} catch(const std::exception& e) {
if (!opts.suppress_errors)
cerr << "Error: " << e.what() << std::endl;
return 2;
}
if (opts.show_help) {
cout << "Usage: " << usage << "\n"
<< "Search for PATTERNS in each FILE.\n"
<< "Example: grep -i 'hello world' menu.h main.c\n\n";
opt_parser.print_help(std::cout);
cout << "\n\nExit status is 0 if any line is selected, "
<< "1 otherwise;\nif any error occurs and -q is not given, "
<< "the exit status is 2." << std::endl;
return 0;
}
if (opts.show_version) {
cout << "mygrep 1.0\n"
<< "An example program using the Option++ library.\n\n"
<< "Copyright (C) 2020 Greg Kikola\n"
<< "License: BSL-1.0: Boost Software License version 1.0"
<< endl;
return 0;
}
// Iterate over options and collect filenames
bool first_arg = true;
for (const auto& entry : result) {
if (entry.is_option) {
if (entry.short_name == 'E')
opts.type = PatternType::extended_regex;
else if (entry.short_name == 'G')
opts.type = PatternType::basic_regex;
else if (entry.short_name == 'i')
opts.ignore_case = true;
else if (entry.long_name == "ignore-case")
opts.ignore_case = false;
else if (entry.short_name == 'e')
opts.patterns.push_back(entry.argument);
} else { // Non-option
// First argument is the pattern
if (first_arg) {
first_arg = false;
opts.patterns.push_back(entry.original_text);
} else {
opts.files.push_back(entry.original_text);
}
}
} // End for
// Read and match in each file
bool match_found = false;
try {
if (!opts.pattern_file.empty())
read_patterns_from_file(opts.pattern_file,
std::back_inserter(opts.patterns));
for (const auto& filename : opts.files) {
if (match_file(filename, opts))
match_found = true;
}
} catch(const std::exception& e) {
if (!opts.suppress_errors)
cerr << "Error: " << e.what() << endl;
return 2;
}
return match_found ? 0 : 1;
}
// Read patterns into dest
template <typename InputIt>
void read_patterns_from_file(const string& filename,
InputIt dest) {
ifstream file(filename);
if (!file.is_open()) {
throw std::invalid_argument("Could not open file '" + filename + "'");
}
string line;
while (std::getline(file, line))
*dest++ = line;
}
// Print matches and return true if match found
bool match_file(const string& filename, const Options& opts) {
ifstream file(filename);
if (!file.is_open())
throw std::invalid_argument("Could not open file '" + filename + "'");
string cur_line;
int count = 0;
while (std::getline(file, cur_line)) {
bool match = does_line_match(cur_line, opts);
if (opts.invert_match)
match = !match;
if (match)
++count;
if (!opts.count_only) {
if (match && !opts.quiet) {
if (opts.files.size() > 1)
cout << filename << ":";
cout << cur_line << "\n";
}
}
if (opts.limit_lines && count >= opts.max_lines)
break;
} // End while
if (opts.count_only && !opts.quiet) {
if (opts.files.size() > 1)
cout << filename << ":";
cout << count << "\n";
}
return count > 0;
}
// Return true if line matches any of the patterns in opts.patterns
bool does_line_match(const string& line, const Options& opts) {
auto flags = regex::basic;
if (opts.type == PatternType::extended_regex)
flags = regex::extended;
if (opts.ignore_case)
flags |= regex::icase;
for (const auto& p : opts.patterns) {
regex pattern{p, flags};
std::smatch m;
if (std::regex_search(line, m, pattern))
return true;
}
return false;
}

Sample input/output:

prompt$ echo 'And you may find yourself' >> input.txt
prompt$ echo 'Living in a shotgun shack' >> input.txt
prompt$ echo 'And you may find yourself' >> input.txt
prompt$ echo 'In another part of the world' >> input.txt
prompt$ echo 'And you may find yourself' >> input.txt
prompt$ echo 'Behind the wheel of a large automobile' >> input.txt
prompt$ example_mygrep -E 'shack|auto' input.txt
Living in a shotgun shack
Behind the wheel of a large automobile
prompt$ example_mygrep -E 'shack|auto' --count input.txt
2
prompt$ example_mygrep 'WORLD' input.txt
prompt$ example_mygrep 'WORLD' --ignore-case input.txt
In another part of the world
prompt$ example_mygrep -E '^a' -i --invert-match input.txt
Living in a shotgun shack
In another part of the world
Behind the wheel of a large automobile

DOS-Style Options

This shows how to customize the strings that the parser interprets as option indicators.

/*
* Accepting DOS-style arguments: /A /B /F:file ...
*
* Not perfect: unlike the real DOS, you can't do /A/B without space
* between the options.
*/
#include <iostream>
struct Disk {
int size{160};
int sectors{15};
int tracks{80};
bool system_reserve{};
bool copy_os{};
bool quick_fmt{};
};
int main(int argc, char* argv[]) {
parser opt_parser;
Disk disk;
opt_parser.set_custom_strings(" \t\r\n", // Whitespace delimiters
"/", // Short prefix
"\0", // Long prefix, not used here
"\0", // End-of-options, not used here
":"); // Argument assignment
opt_parser['F'].argument("(size)", true).bind_int(&disk.size)
.description("Format disk to specific size");
opt_parser['N'].argument("(sectors)", true).bind_int(&disk.sectors)
.description("Specify number of sectors per track on the disk");
opt_parser['T'].argument("(tracks)", true).bind_int(&disk.tracks)
.description("Specify number of tracks on the disk");
opt_parser['B'].bind_bool(&disk.system_reserve)
.description("Reserve space on disk to later copy system files");
opt_parser['S'].bind_bool(&disk.copy_os)
.description("Copy operating system files to the disk after formatting");
opt_parser['Q'].bind_bool(&disk.quick_fmt)
.description("Quick format - erases file allocation table but does not "
"identify bad sectors");
parser_result result;
try {
result = opt_parser.parse(argc, argv);
} catch(const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
if (result.empty()) {
std::cout << "Acceptable options:\n";
// Line length: 78, group-indent: 0, option-indent: 2
// First line indent: 16, multiline indent: 18
opt_parser.print_help(std::cout, 78, 0, 2, 16, 18) << std::endl;
return 0;
}
if (disk.quick_fmt)
std::cout << "Performing quick format...\n";
std::cout << "Formatting disk of size "
<< disk.size << " Kb with "
<< disk.sectors << " sectors per track and "
<< disk.tracks << " tracks\n";
if (disk.system_reserve)
std::cout << "Reserving space for system files...\n";
if (disk.copy_os)
std::cout << "Copying operating system files...\n";
std::cout << "Done" << std::endl;
return 0;
}

Sample input/output:

prompt$ example_dos
Acceptable options:
/F:(size) Format disk to specific size
/N:(sectors) Specify number of sectors per track on the disk
/T:(tracks) Specify number of tracks on the disk
/B Reserve space on disk to later copy system files
/S Copy operating system files to the disk after formatting
/Q Quick format - erases file allocation table but does not
identify bad sectors
prompt$ example_dos /F:720 /N:9 /T:80 /S
Formatting disk of size 720 Kb with 9 sectors per track and 80 tracks
Copying operating system files...
Done

Parsing Arguments in Strings

Instead of passing argc and argv from main, you can also pass std::strings to the parser.

/*
* Parsing options from a string
*/
#include <iostream>
#include <string>
int main() {
optionpp::parser opt_parser;
opt_parser['a'];
opt_parser['b'];
opt_parser['c'];
std::string line;
while (true) {
std::cout << "Enter some words, or leave blank to exit:\n";
if (!std::getline(std::cin, line))
break;
if (line.empty())
break;
try {
result = opt_parser.parse(line);
} catch(const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
continue;
}
for (auto& entry : result) {
if (entry.is_option) {
switch (entry.short_name) {
case 'a':
std::cout << "Found option a\n";
break;
case 'b':
std::cout << "Found option b\n";
break;
case 'c':
std::cout << "Found option c\n";
break;
}
} else {
std::cout << "Found word '"
<< entry.original_text << "'\n";
}
}
}
}

Sample input/output:

prompt$ example_from_string
Enter some words, or leave blank to exit:
Hello world
Found word 'Hello'
Found word 'world'
Enter some words, or leave blank to exit:
Hello -ab
Found word 'Hello'
Found option a
Found option b
Enter some words, or leave blank to exit:
-c "quotes allow spaces in an argument"
Found option c
Found word 'quotes allow spaces in an argument'
Enter some words, or leave blank to exit:
optionpp.hpp
Main include file for library users.
optionpp::parser::parse
parser_result parse(InputIt first, InputIt last, bool ignore_first=true) const
Parse command-line arguments from a sequence of strings.
optionpp::error
Base class for library exceptions.
Definition: error.hpp:36
optionpp::parser_result
Holds data that was parsed from the program command line.
Definition: parser_result.hpp:152
optionpp::parser
Parses program options.
Definition: parser.hpp:87
optionpp::non_option_const_iterator
result_iterator< const parser_result, const parsed_entry *, const parsed_entry &, false > non_option_const_iterator
const_iterator over non-option entries in a parser_result.
Definition: result_iterator.hpp:184