Simple command-line option parser with automatic setting of variables and built-in error checking.
The story is I was working on another new project that had a lot of configurable options, as they usually do. I got tired of writing the same parser 50 different times, so I spent some time to write one that hopefully I could reuse.
It's nothing fancy, and I realized later of a more OO way to do this, but it worked for my purpose, and I'm looking forward to being done with the boring dry parsing bits forever. The end.
The syntax is simple: blah.exe [<option>=<value>, ...]
<option>
is the name and <value>
can be an integer, boolean, or string type.
As usual, any and all criticism welcome.
example usage
> program.exe bar=5 foo=2 bim=prog.conf baz=1
foo is 2
bar is 5
baz is 1
bim is "prog.conf"
example where bounds checking kicks in
> program.exe bar=5 foo=12 bim=prog.conf baz=1
error: value out of bounds, option #2 "foo=12"
exiting...
main.cpp
/*
Author: Mode77
Date: 7/2/2023
*/
#include <string>
#include <iostream>
#include "cmdopt.h"
int foo;
int bar;
bool baz;
std::string bim;
int main(int argc, char *argv[])
{
for(int i = 1; i < argc; i++) {
if(intOption(argv[i], i, "foo", 0, 9, foo, argv[0])) { }
else if(intOption(argv[i], i, "bar", 3, 5, bar, argv[0])) { }
else if(boolOption(argv[i], i, "baz", baz, argv[0])) { }
else if(stringOption(argv[i], i, "bim", bim, argv[0])) { }
else unknownOptionError(argv[i], i);
}
std::cout << "foo is " << foo << "\n";
std::cout << "bar is " << bar << "\n";
std::cout << "baz is " << baz << "\n";
std::cout << "bim is \"" << bim << "\"\n";
}
cmdopt.h
/*
Author: Mode77
Date: 7/2/2023
*/
#pragma once
#include <string>
// Processes an integer-typed command-line option in three stages.
// First, the syntax of the assignment string is checked. If the syntax check
// fails, an error message is printed, and the program is halted and exited
// with EXIT_FAILURE.
//
// Otherwise, the option name in the assignment is checked against
// the given option name. If the name check fails, the function returns false.
//
// Finally, the value is parsed as an integer. If there is a parsing error
// (value is not a number, under/overflow, value outside of given range),
// an error message is printed, and the program is halted and exited
// with EXIT_FAILURE.
//
// If all checks pass, the destination variable is written with the parsed
// value and the function returns true.
//
// assignment: Option assignment string
// optionNumber: Used for possible error messages
// optionName: The name which the option is expected to match
// minimum: The smallest value allowed for the option
// maximum: The largest value allowed for the option
// destination: The variable written to if the parse succeeds
// programName: Used for possible error messages
bool intOption(
std::string const & assignment,
unsigned optionNumber,
std::string const & optionName,
int minimum,
int maximum,
int & destination,
std::string const & programName);
// Processes a boolean-typed command-line option using the same three-stage
// process as intOption().
//
// The value is parsed as a boolean; 0 represents false and any other
// value is interpreted as true.
//
// The strings "true" and "false" are not allowed (Todo: YET!!!)
//
// assignment: Option assignment string
// optionNumber: Used for possible error messages
// optionName: The name which the option is expected to match
// destination: The variable written to if the parse succeeds
// programName: Used for possible error messages
bool boolOption(
std::string const & assignment,
unsigned optionNumber,
std::string const & optionName,
bool & destination,
std::string const & programName);
// Processes a string-typed command-line option using the same three-stage
// process as intOption() and boolOption().
//
// assignment: Option assignment string
// optionNumber: Used for possible error messages
// optionName: The name which the option is expected to match
// destination: The variable written to if the parse succeeds
// programName: Used for possible error messages
bool stringOption(
std::string const & assignment,
unsigned optionNumber,
std::string const & optionName,
std::string & destination,
std::string const & programName);
// Prints an unknown option error message and exits the program with the failure code
void unknownOptionError(std::string const &assignment, unsigned optionNumber);
cmdopt.cpp
/*
Author: Mode77
Date: 7/2/2023
*/
#include <string>
#include <iostream>
#include <cstdlib>
#include <climits>
#include "cmdopt.h"
static bool isValidOptionSyntax(std::string const &);
static bool optionMatches(std::string const &, std::string const &);
static int expectInt(std::string const &, unsigned, int, int);
static void invalidSyntaxError(std::string const &, unsigned, std::string const &);
static void invalidValueError(std::string const &, unsigned);
static void valueOutOfBoundsError(std::string const &, unsigned);
bool intOption(
std::string const & assignment,
unsigned optionNumber,
std::string const & optionName,
int minimum,
int maximum,
int & destination,
std::string const & programName)
{
if(!isValidOptionSyntax(assignment)) {
invalidSyntaxError(assignment, optionNumber, programName);
return false; // Unreachable since the above will exit
}
if(!optionMatches(assignment, optionName)) {
return false;
}
destination = expectInt(assignment, optionNumber, minimum, maximum);
return true;
}
bool boolOption(
std::string const & assignment,
unsigned optionNumber,
std::string const & optionName,
bool & destination,
std::string const & programName)
{
if(!isValidOptionSyntax(assignment)) {
invalidSyntaxError(assignment, optionNumber, programName);
return false; // Unreachable since the above will exit
}
if(!optionMatches(assignment, optionName)) {
return false;
}
destination = static_cast<bool>(expectInt(assignment, optionNumber, INT_MIN, INT_MAX));
return true;
}
bool stringOption(
std::string const & assignment,
unsigned optionNumber,
std::string const & optionName,
std::string & destination,
std::string const & programName)
{
if(!isValidOptionSyntax(assignment)) {
invalidSyntaxError(assignment, optionNumber, programName);
return false; // Unreachable since the above will exit
}
if(!optionMatches(assignment, optionName)) {
return false;
}
size_t const indexOfEquals = assignment.find('=');
destination = assignment.substr(indexOfEquals + 1, std::string::npos);
return true;
}
void unknownOptionError(std::string const &assignment, unsigned optionNumber)
{
size_t const indexOfEquals = assignment.find('=');
std::string const option(assignment.substr(0, indexOfEquals));
std::cout <<
"error: unknown option, "
"option #" << optionNumber << " \"" << option << "\"\n";
std::cout << "exiting...\n";
exit(EXIT_FAILURE);
}
// Returns true if the option assignment string has the syntax:
// <option>=<value>
// where option and value are at least one character each.
// Returns false otherwise.
static bool isValidOptionSyntax(std::string const &assignment)
{
size_t const indexOfEquals = assignment.find('=');
bool const containsEqualsSign = indexOfEquals != std::string::npos;
bool const hasAtLeastThreeCharacters = assignment.length() > 2;
bool const equalsIsNotFirstOrLast =
(indexOfEquals > 0) && (indexOfEquals < (assignment.length() - 1));
return
containsEqualsSign && hasAtLeastThreeCharacters && equalsIsNotFirstOrLast;
}
// Given a string in option assignment syntax, compares the assignment option
// with the given expected name.
//
// Returns true if they compare the same, false otherwise.
static bool optionMatches(std::string const &assignment, std::string const &expected)
{
size_t const indexOfEquals = assignment.find('=');
std::string const option(assignment.substr(0, indexOfEquals));
return option == expected;
}
// Given a string in option assignment syntax, returns the value as an integer.
//
// If there was a parsing error (value is not a number, under/overflow, value is
// outside of given range), an error message is printed, and the program is halted
// and exited with EXIT_FAILURE.
static int expectInt(std::string const &assignment, unsigned optionNumber, int mini, int maxi)
{
size_t const indexOfEquals = assignment.find('=');
std::string const value(assignment.substr(indexOfEquals + 1, std::string::npos));
int v;
try {
v = std::stoi(value);
}
catch(std::invalid_argument const &) {
invalidValueError(assignment, optionNumber);
}
catch(std::out_of_range const &) {
valueOutOfBoundsError(assignment, optionNumber);
}
if(v < mini || v > maxi) {
valueOutOfBoundsError(assignment, optionNumber);
}
return v;
}
// Prints an invalid syntax error message and exits the program with the failure code
static void invalidSyntaxError(
std::string const & assignment,
unsigned optionNumber,
std::string const & programName)
{
std::cout <<
"error: invalid syntax, "
"option #" << optionNumber << " \"" << assignment << "\"\n";
std::cout << "usage: " << programName << " [<option>=<value>...]\n";
std::cout << "exiting...\n";
exit(EXIT_FAILURE);
}
// Prints an invalid value error message and exits the program with the failure code
static void invalidValueError(std::string const &assignment, unsigned optionNumber)
{
std::cout <<
"error: invalid value, "
"option #" << optionNumber << " \"" << assignment << "\"\n";
std::cout << "exiting...\n";
exit(EXIT_FAILURE);
}
// Prints a value out of bounds error message and exits the program with the failure code
static void valueOutOfBoundsError(std::string const &assignment, unsigned optionNumber)
{
std::cout <<
"error: value out of bounds, "
"option #" << optionNumber << " \"" << assignment << "\"\n";
std::cout << "exiting...\n";
exit(EXIT_FAILURE);
}
std::size_t
,std::exit
and possible other identifiers. \$\endgroup\$