lector

Lector

Linux macOS Windows

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:

§1. Introduction

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.

(Back to Top)

§2. Configuration

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.

Second, ensure that a C++ compiler with support for the C++17 standard or any more recent standard is installed on your system.

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:

All of the Lector library’s contents are cleanly encapsulated within the lector:: namespace.

(Back to Top)

§2.1. Configuration: Bazel

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.

(Back to Configuration)

§2.2. Configuration: CMake

To use the Lector library in one of your CMake C++ projects, first ensure that the CMake build system is installed on your system.

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.

(Back to Configuration)

§2.3. Configuration: Meson

To use the Lector library in one of your Meson C++ projects, first ensure that the Meson build system is installed on your system.

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.

(Back to Configuration)

§3. User Guide

This section contains a comprehensive guide for using the Lector library in your C++ projects.

(Back to Top)

§3.1. User Guide: Arguments

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:

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()};

(Back to User Guide)

§3.2. User Guide: Command Line

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

(Back to User Guide)

§3.3. User Guide: Enumerations

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;
}

(Back to User Guide)

§3.4. User Guide: Classes and Structures

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;
}

(Back to User Guide)

§3.5. User Guide: Error Checking

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:

The following checks are performed when parsing command line arguments. These examples use the code from the Introduction section:

(Back to User Guide)

§4. Developer Guide

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.

The Lector library currently has 100% code coverage.

Next, refer to the section for your preferred build system:

(Back to Top)

§4.1. Developer Guide: Bazel

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.

(Back to Developer Guide)

§4.2. Developer Guide: CMake

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.

(Back to Developer Guide)

§4.3. Developer Guide: Meson

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.

(Back to Developer Guide)

§5. Documentation

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.

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.

(Back to Top)

§6. License

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:

(Back to Top)