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;
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;
try {
result = opt_parser.parse(argc, argv);
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
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;
} else {
if (settings.verbose)
std::cout << "Writing output to standard out" << std::endl;
}
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.
#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 {
PatternType type { PatternType::basic_regex };
vector<string> files;
vector<string> patterns;
string pattern_file;
bool ignore_case{};
bool suppress_errors{};
bool invert_match{};
bool show_version{};
bool show_help{};
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;
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;
}
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 {
if (first_arg) {
first_arg = false;
opts.patterns.push_back(entry.original_text);
} else {
opts.files.push_back(entry.original_text);
}
}
}
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;
}
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;
}
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;
}
if (opts.count_only && !opts.quiet) {
if (opts.files.size() > 1)
cout << filename << ":";
cout << count << "\n";
}
return count > 0;
}
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.
#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",
"/",
"\0",
"\0",
":");
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";
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::string
s to the parser.
#include <iostream>
#include <string>
int main() {
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: