Lector is a C++ library for parsing command line arguments. Lector is supported on Linux, macOS, and Windows, and can be built with Bazel, CMake, or Meson. The Lector library’s repository is hosted at https://github.com/acodcha/lector and its documentation is hosted at https://acodcha.github.io/lector.
Contents:
The following example illustrates the use of the Lector library:
#include <cstdint>
#include <iostream>
#include <filesystem>
#include <lector/arguments.hpp>
enum class Label : std::int8_t {OutputDirectory, Iterations, Help};
int main(int argc, char* argv[]) {
lector::Arguments arguments{
lector::Argument<Label::OutputDirectory, std::filesystem::path>{
{"-o", "--output_directory"}, "Output directory. Required."
},
lector::Argument<Label::Iterations, std::int32_t>{
{"-i", "--iterations"}, "Number of iterations. Optional. Default: 100.", 100
},
lector::Argument<Label::Help, bool>{
{"-h", "--help"}, "Display usage information and exit. Optional."
}
};
arguments.parse(argc, argv);
if (arguments.get<Label::Help>().parsed_or_default_value()) {
std::cout << "Usage: " << arguments.usage_command() << std::endl;
std::cout << "Options: " << std::endl << arguments.usage_options() << std::endl;
return EXIT_SUCCESS;
}
std::cout << "Execution: " << arguments.execution() << std::endl;
const std::filesystem::path& output_directory{
arguments.get<Label::OutputDirectory>().parsed_value().value()};
std::cout << "The output directory is: " << output_directory << std::endl;
const std::int32_t iterations{
arguments.get<Label::Iterations>().parsed_or_default_value()};
std::cout << "The number of iterations is: " << iterations << std::endl;
return EXIT_SUCCESS;
}
The above example imports the Lector library, defines an enumeration of argument labels, creates a collection of arguments, parses the arguments from the command line argc and argv variables, checks whether usage information should be printed, prints the execution information, and obtains and prints the parsed arguments.
In the Lector library, command line arguments are strongly-typed, support arbitrary types, can be declared as required or optional, and feature strict error checking.
The Lector library can parse command line arguments as whitespace-separated key-value pairs of the form key value or as inline key-value pairs of the form key=value. It can also handle keys that contain arbitrary characters.
This section describes how to use the Lector library in one of your C++ projects.
First, ensure that the Git source control system is installed on your system.
sudo apt install git.brew install git.choco install git.Second, ensure that a C++ compiler with support for the C++17 standard or any more recent standard is installed on your system.
sudo apt install g++ or the LLVM library with sudo apt install llvm.brew install llvm.Third, ensure that your C++ project uses a supported build system; the Lector library currently supports the Bazel, CMake, and Meson build systems. Refer to the section for your preferred build system:
Once your build system has been configured, simply include the Lector library’s headers in your C++ source files with:
#include <lector/arguments.hpp>
#include <lector/parse.hpp>
#include <lector/print.hpp>
The Lector library is modular:
<lector/arguments.hpp> defines the lector::Argument and lector::Arguments classes, as demonstrated in the Introduction section.<lector/parse.hpp> defines the lector::Spellings and lector::parse() utilities. See the User Guide: Enumerations section for usage.<lector/print.hpp> defines the lector::Names and lector::print() utilities. See the User Guide: Enumerations section for usage.All of the Lector library’s contents are cleanly encapsulated within the lector:: namespace.
To use the Lector library in one of your Bazel C++ projects, first ensure that the Bazel build system is installed on your system. Follow the instructions at https://bazel.build/install to install Bazel on your system.
Then, add the following code to your project’s MODULE.bazel file:
bazel_dep(name = "lector", version = "1.0.0")
git_override(
module_name = "lector",
remote = "https://github.com/acodcha/lector.git",
branch = "main",
)
Note: You can specify a release tag such as tag = "v1.0.0" instead of branch = "main".
Alternatively, if your project uses the legacy WORKSPACE.bazel system instead of the newer MODULE.bazel system, add the following code to your project’s WORKSPACE.bazel file:
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "lector",
remote = "https://github.com/acodcha/lector.git",
branch = "main",
)
Note: You can specify a release tag such as tag = "v1.0.0" instead of branch = "main".
The above code automatically downloads the Lector library from its GitHub repository and makes it available to your Bazel project.
Next, link the Lector library to the C++ targets in your project’s BUILD.bazel files. Because the Lector library is modular, you only need to depend on the specific parts of the library you actually use:
cc_library(
name = "my_library_name",
hdrs = ["my_library_file.hpp"],
srcs = ["my_library_file.cpp"],
deps = [
"@lector//lector:arguments",
"@lector//lector:parse",
"@lector//lector:print",
":my_other_dependency",
],
)
The above Bazel code defines your C++ Bazel library to depend on the Lector library modules.
To use the Lector library in one of your CMake C++ projects, first ensure that the CMake build system is installed on your system.
sudo apt install cmake.brew install cmake.choco install cmake.Then, add the following code to your project’s CMakeLists.txt file:
find_package(lector CONFIG QUIET)
if(lector_FOUND)
message(STATUS "The Lector library was found at: ${lector_CONFIG}")
else()
include(FetchContent)
FetchContent_Declare(
lector
GIT_REPOSITORY https://github.com/acodcha/lector.git
GIT_TAG main
)
FetchContent_MakeAvailable(lector)
add_library(lector::lector ALIAS lector)
message(STATUS "The Lector library was fetched from: https://github.com/acodcha/lector")
endif()
# [...]
target_link_libraries(my_target_name PRIVATE lector::lector my_other_dependency)
Note: You can specify a release tag such as GIT_TAG v1.0.0 instead of GIT_TAG main.
The above CMake code checks whether the Lector library is already installed on your system; if not, it automatically downloads it from its GitHub repository and makes it available to your CMake project.
To use the Lector library in one of your Meson C++ projects, first ensure that the Meson build system is installed on your system.
sudo apt install meson ninja-build.brew install meson ninja.choco install meson ninja.Then, create a subprojects/lector.wrap file in your project and give it the following contents:
[wrap-git]
url = https://github.com/acodcha/lector.git
revision = main
Note: You can specify a release tag such as revision = v1.0.0 instead of revision = main.
The above code automatically downloads the Lector library from its GitHub repository and makes it available to your Meson project.
Next, add the following code to your project’s meson.build files:
lector = dependency(
'lector',
fallback : ['lector', 'lector'],
required : true
)
my_library_name = library(
'my_library_name',
'src/my_library_file.cpp',
include_directories : include_directories('src'),
dependencies : [lector, my_other_dependency],
install : true
)
The above Meson code imports the Lector library in your project, defines your library, and adds the Lector library as a dependency to your library.
This section contains a comprehensive guide for using the Lector library in your C++ projects.
The Lector library uses enumeration values to label arguments. For example, the code from the Introduction section defines the following enumeration:
enum class Label : std::int8_t {OutputDirectory, Iterations, Help};
Defining an argument requires an enumeration value used as a label, a type, one or more keys, a description, and an optional default value. For example, the code from the Introduction section defines the following arguments:
lector::Arguments arguments{
lector::Argument<Label::OutputDirectory, std::filesystem::path>{
{"-o", "--output_directory"}, "Output directory. Required."
},
lector::Argument<Label::Iterations, std::int32_t>{
{"-i", "--iterations"}, "Number of iterations. Optional. Default: 100.", 100
},
lector::Argument<Label::Help, bool>{
{"-h", "--help"}, "Display usage information and exit. Optional."
}
};
Supported argument types include:
bool.std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t.std::int8_t, std::int16_t, std::int32_t, std::int64_t.float, double, long double.std::string. If the string contains whitespace, enclose it in double quotes ("").std::filesystem::path.Once all arguments have been defined, the lector::Arguments::parse() method can be used to parse argc and argv. For example:
arguments.parse(argc, argv);
This populates all arguments with their parsed values and performs strict error checking. See the Error Checking section for details.
Usage information can be obtained via the lector::Arguments::usage_command() and lector::Arguments::usage_options() methods. For example:
std::cout << "Usage: " << arguments.usage_command() << std::endl;
std::cout << "Options: " << std::endl << arguments.usage_options() << std::endl;
Execution information can be obtained via the lector::Arguments::execution() method. For example:
std::cout << "Execution: " << arguments.execution() << std::endl;
See the Command Line section for details.
Individual arguments can be fetched via the lector::Arguments::get() method using the argument’s enumeration value as a template. For example:
const std::filesystem::path& output_directory{
arguments.get<Label::OutputDirectory>().parsed_value().value()};
const std::int32_t iterations{
arguments.get<Label::Iterations>().parsed_or_default_value()};
The Lector library allows you to flexibly run your program from the command line and to conveniently display the usage and execution information of your program. The following examples use the code from the Introduction section.
Display usage information via the lector::Arguments::usage_command() and lector::Arguments::usage_options() methods:
path/to/my_project_main --help
Usage: my_project_main --output_directory <path> [--iterations <number>] [--help]
Options:
-o <path>, --output_directory <path> Output directory. Required.
[-i <number>, --iterations <number>] Number of iterations. Optional. Default: 100.
[-h, --help] Display usage information and exit. Optional.
Display execution information via the lector::Arguments::execution() method:
path/to/my_project_main --output_directory /some/path --iterations 200
Execution: path/to/my_project_main --output_directory /some/path --iterations 200
The output directory is: /some/path
The number of iterations is: 200
Inline key-value pairs of the form key=value are also supported:
path/to/my_project_main --output_directory=/some/path --iterations=200
Execution: path/to/my_project_main --output_directory /some/path --iterations 200
The output directory is: /some/path
The number of iterations is: 200
Arguments can be defined with multiple keys. For example:
path/to/my_project_main -o /some/path -i=200
Execution: path/to/my_project_main --output_directory /some/path --iterations 200
The output directory is: /some/path
The number of iterations is: 200
Keys do not need to start with a hyphen (-); keys can be composed of any characters, including equal signs (=). For example, the following definitions are unusual but perfectly valid:
lector::Arguments arguments{
lector::Argument<Label::OutputDirectory, std::filesystem::path>{
{"o", "=o", "__out_dir__"}, "Output directory. Required."
},
lector::Argument<Label::Iterations, std::int32_t>{
{"=i=", "_it_", "==iterations=="}, "Number of iterations. Optional. Default: 100.", 100
}
};
path/to/my_project_main __out_dir__ /some/path =i= 200
Execution: path/to/my_project_main __out_dir__ /some/path ==iterations== 200
The output directory is: /some/path
The number of iterations is: 200
Enumerations can be used as argument types, but require specializing the lector::Names and lector::Spellings constants for their values. For example:
#ifndef MY_PROJECT_SHAPE_HPP
#define MY_PROJECT_SHAPE_HPP
#include <array>
#include <cstdint>
#include <lector/parse.hpp>
#include <lector/print.hpp>
namespace my_project {
enum class Shape : std::int8_t {Circle, Triangle, Square};
} // namespace my_project
namespace lector {
template <>
inline constexpr std::array<Name<my_project::Shape>, 3> Names<my_project::Shape>{
{
{my_project::Shape::Circle, "Circle"},
{my_project::Shape::Triangle, "Triangle"},
{my_project::Shape::Square, "Square"},
}
};
template <>
inline constexpr std::array<Spelling<my_project::Shape>, 9> Spellings<my_project::Shape>{
{
{"Circle", my_project::Shape::Circle},
{"Triangle", my_project::Shape::Triangle},
{"Square", my_project::Shape::Square},
{"circle", my_project::Shape::Circle},
{"triangle", my_project::Shape::Triangle},
{"square", my_project::Shape::Square},
{"CIRCLE", my_project::Shape::Circle},
{"TRIANGLE", my_project::Shape::Triangle},
{"SQUARE", my_project::Shape::Square},
}
};
} // namespace lector
#endif // MY_PROJECT_SHAPE_HPP
With the above definitions, the lector::print() and lector::parse() methods can now be used with this enumeration. For example:
#include <iostream>
#include <lector/parse.hpp>
#include <lector/print.hpp>
#include "my_project/shape.hpp"
int main() {
const std::string printed_triangle{lector::print(my_project::Shape::Triangle)};
assert(printed_triangle == "Triangle");
const std::optional<my_project::Shape> parsed_triangle{
lector::parse<my_project::Shape>("TRIANGLE")};
assert(parsed_triangle.has_value());
assert(parsed_triangle.value() == my_project::Shape::Triangle);
const std::optional<my_project::Shape> invalid_shape{
lector::parse<my_project::Shape>("Invalid Shape")};
assert(!invalid_shape.has_value());
return EXIT_SUCCESS;
}
The enumeration can also be used as a command line argument. For example:
#include <cstdint>
#include <iostream>
#include <lector/arguments.hpp>
#include <lector/print.hpp>
#include "my_project/shape.hpp"
enum class Label : std::int8_t {FavoriteShape};
int main(int argc, char* argv[]) {
lector::Arguments arguments{
lector::Argument<Label::FavoriteShape, my_project::Shape>{
{"-s", "--shape"}, "Your favorite shape. Optional.", my_project::Shape::Circle
}
};
arguments.parse(argc, argv);
const my_project::Shape shape{
arguments.get<Label::FavoriteShape>().parsed_or_default_value()};
std::cout << "Your favorite shape is: " << lector::print(shape) << std::endl;
return EXIT_SUCCESS;
}
Classes and structures can be used as argument types, but require specializing the input and output stream operators (<< and >>). For example:
#ifndef MY_PROJECT_POINT_HPP
#define MY_PROJECT_POINT_HPP
#include <iostream>
namespace my_project {
struct Point {
double x{0.0};
double y{0.0};
double z{0.0};
};
inline std::istream& operator>>(std::istream& input_stream, Point& point) {
input_stream >> point.x >> point.y >> point.z;
return input_stream;
}
inline std::ostream& operator<<(std::ostream& output_stream, const Point& point) {
output_stream << point.x << " " << point.y << " " << point.z;
return output_stream;
}
} // namespace my_project
#endif // MY_PROJECT_POINT_HPP
With the above definitions, the lector::print() and lector::parse() methods can now be used with this data structure. For example:
#include <iostream>
#include <lector/parse.hpp>
#include <lector/print.hpp>
#include "my_project/point.hpp"
int main() {
const std::string printed_point{lector::print(my_project::Point{1.0, 2.0, 3.0})};
assert(printed_point == "1 2 3");
const std::optional<my_project::Point> parsed_point{
lector::parse<my_project::Point>("4.0 5.0 6.0")};
assert(parsed_point.has_value());
assert(parsed_point.value() == my_project::Point{4.0, 5.0, 6.0});
return EXIT_SUCCESS;
}
The data structure can also be used as a command line argument. For example:
#include <cstdint>
#include <iostream>
#include <lector/arguments.hpp>
#include "my_project/point.hpp"
enum class Label : std::int8_t {FavoritePoint};
int main(int argc, char* argv[]) {
lector::Arguments arguments{
lector::Argument<Label::FavoritePoint, my_project::Point>{
{"-p", "--point"}, "Your favorite point. Optional.", my_project::Point{}
}
};
arguments.parse(argc, argv);
const my_project::Point point{
arguments.get<Label::FavoritePoint>().parsed_or_default_value()};
std::cout << "Your favorite point is: " << point << std::endl;
return EXIT_SUCCESS;
}
The Lector library performs strict error checking when defining command line arguments and again when parsing these arguments from the command line.
The following checks are performed when defining command line arguments:
lector::Arguments constructed from lector::Argument<Label::Help, bool>{ {"-h"}, "Help." } and lector::Argument<Label::Help, bool>{ {"--help"}, "Help." } throws an exception because both arguments use the same label Label::Help.lector::Argument<Label::Iterations, std::int32_t>{ {}, "Iterations." } throws an exception because no keys are defined.lector::Argument<Label::Iterations, std::int32_t>{ {"", "-i"}, "Iterations." } throws an exception because the first key is empty.lector::Argument<Label::Iterations, std::int32_t>{ {"-i", "-i"}, "Iterations." } throws an exception because the key -i is duplicated.lector::Arguments constructed from lector::Argument<Label::Iterations, std::int32_t>{ {"-i"}, "Iterations." } and lector::Argument<Label::Help, bool>{ {"-i"}, "Help." } throws an exception because the two arguments use the same key -i.lector::Argument<Label::Iterations, std::int32_t>{ {"-i"}, "" } throws an exception because the description is empty.lector::Argument<Label::Help, bool>{ {"-h"}, "Help.", true } throws an exception because true was specified as a default value.The following checks are performed when parsing command line arguments. These examples use the code from the Introduction section:
path/to/my_project_main --iterations 200 throws an exception because the required argument --output_directory <path> is missing.path/to/my_project_main --output_directory /tmp --output_directory /home throws an exception because the argument --output_directory <path> is duplicated.path/to/my_project_main --output_directory /tmp --iterations hello throws an exception because hello is not a valid value for the argument --iterations <number>.path/to/my_project_main --output_directory /tmp --iterations throws an exception because the argument --iterations <number> is missing its value.path/to/my_project_main --output_directory /tmp --unknown throws an exception because the argument --unknown is unknown.To check out, build, and test the Lector library for yourself, first clone the Lector library’s repository:
git clone git@github.com:acodcha/lector.git lector
cd lector
If you intend to compute the Lector library’s code coverage, ensure that the LCOV package is installed on your system.
sudo apt install lcov.brew install lcov.choco install lcov.The Lector library currently has 100% code coverage.
Next, refer to the section for your preferred build system:
If using the Bazel build system, build the Lector library with:
bazel build //...
Run all Lector library tests using the Bazel build system with:
bazel test //...
On Linux or macOS, compute the Lector library’s code coverage using the Bazel build system with:
bazel coverage //...
genhtml bazel-out/_coverage/_coverage_report.dat --output-directory coverage
This generates a code coverage report at coverage/index.html. Open this file in any web browser to view the report.
On Windows, to compute the Lector library’s code coverage using the Bazel build system, the Windows MSVC compiler will not work, and the LLVM Clang compiler should be used instead. Ensure that the LLVM library is installed on your system; you can install it using the Chocolatey package manager with choco install llvm. Then, compute the Lector library’s code coverage using the Bazel build system with:
bazel coverage --compiler=clang-cl //...
genhtml bazel-out/_coverage/_coverage_report.dat --output-directory coverage
This generates a code coverage report at coverage/index.html. Open this file in any web browser to view the report.
If using the CMake build system, build the Lector library with:
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
cmake --build build --config Release --parallel
Run all Lector library tests using the CMake build system with:
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release -D LECTOR_TEST=ON
cmake --build build --config Release --parallel
ctest --test-dir build -C Release
On Windows, in order to compute code coverage using the CMake build system, ensure that the OpenCppCoverage utility is installed on your system. Download it from https://github.com/OpenCppCoverage/OpenCppCoverage.
Compute the Lector library’s code coverage using the CMake build system with:
cmake -S . -B build -D CMAKE_BUILD_TYPE=Debug -D LECTOR_TEST=ON -D LECTOR_COVERAGE=ON
cmake --build build --config Debug --parallel
ctest --test-dir build -C Debug
cmake --build build --target coverage
This generates a code coverage report at coverage/index.html. Open this file in any web browser to view the report.
If using the Meson build system, build the Lector library with:
meson wrap install gtest
meson setup build
meson compile -C build
Run all Lector library tests using the Meson build system with:
meson wrap install gtest
meson setup build
meson compile -C build
meson test -C build
On Linux or macOS, compute the Lector library’s code coverage using the Meson build system with:
meson wrap install gtest
meson setup build -Db_coverage=true -Db_coverage_dir=../coverage
meson compile -C build
meson test -C build
meson compile -C build coverage-html
This generates a code coverage report at coverage/index.html. Open this file in any web browser to view the report.
On Windows, in order to compute code coverage using the Meson build system, ensure that the OpenCppCoverage utility is installed on your system. Download it from https://github.com/OpenCppCoverage/OpenCppCoverage.
On Windows, compute the Lector library’s code coverage using the Meson build system with:
meson wrap install gtest
meson setup build
meson compile -C build
meson compile -C build coverage
This generates a code coverage report at coverage/index.html. Open this file in any web browser to view the report.
The Lector library’s documentation can be built locally on your system with the Doxygen automatic source code documentation generator, which must be installed on your system.
sudo apt install doxygen.brew install doxygen.choco install doxygen.install.See the Developer Guide section for cloning the Lector library’s repository. Then, build the Lector library’s documentation with:
doxygen Doxyfile.txt
This builds HTML documentation pages in the docs/html/ directory. Browse the documentation by opening the docs/html/index.html file in any web browser.
Copyright © 2026, Alexandre Coderre-Chabot.
Lector is licensed under the MIT License. See the LICENSE.md file or https://mit-license.org.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: