Skip to content

Commit c5800be

Browse files
committed
Introduce a minimal command-line option parsing library
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 01c95ca commit c5800be

File tree

12 files changed

+746
-0
lines changed

12 files changed

+746
-0
lines changed

.github/workflows/website-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ jobs:
2828
-DSOURCEMETA_CORE_JSONPOINTER:BOOL=OFF
2929
-DSOURCEMETA_CORE_YAML:BOOL=OFF
3030
-DSOURCEMETA_CORE_EXTENSION_ALTERSCHEMA:BOOL=OFF
31+
-DSOURCEMETA_CORE_EXTENSION_OPTIONS:BOOL=OFF
3132
-DSOURCEMETA_CORE_DOCS:BOOL=ON
3233
- run: cmake --build ./build --config Release --target doxygen

.github/workflows/website-deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
-DSOURCEMETA_CORE_JSONPOINTER:BOOL=OFF
3939
-DSOURCEMETA_CORE_YAML:BOOL=OFF
4040
-DSOURCEMETA_CORE_EXTENSION_ALTERSCHEMA:BOOL=OFF
41+
-DSOURCEMETA_CORE_EXTENSION_OPTIONS:BOOL=OFF
4142
-DSOURCEMETA_CORE_DOCS:BOOL=ON
4243
- run: cmake --build ./build --config Release --target doxygen
4344
- name: Setup Pages

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ option(SOURCEMETA_CORE_JSONPOINTER "Build the Sourcemeta Core JSON Pointer libra
1616
option(SOURCEMETA_CORE_JSONL "Build the Sourcemeta Core JSONL library" ON)
1717
option(SOURCEMETA_CORE_YAML "Build the Sourcemeta Core YAML library" ON)
1818
option(SOURCEMETA_CORE_EXTENSION_ALTERSCHEMA "Build the Sourcemeta Core AlterSchema library" ON)
19+
option(SOURCEMETA_CORE_EXTENSION_OPTIONS "Build the Sourcemeta Core Options library" ON)
1920
option(SOURCEMETA_CORE_TESTS "Build the Sourcemeta Core tests" OFF)
2021
option(SOURCEMETA_CORE_BENCHMARK "Build the Sourcemeta Core benchmarks" OFF)
2122
option(SOURCEMETA_CORE_DOCS "Build the Sourcemeta Core docs" OFF)
@@ -115,6 +116,10 @@ if(SOURCEMETA_CORE_EXTENSION_ALTERSCHEMA)
115116
add_subdirectory(src/extension/alterschema)
116117
endif()
117118

119+
if(SOURCEMETA_CORE_EXTENSION_OPTIONS)
120+
add_subdirectory(src/extension/options)
121+
endif()
122+
118123
if(SOURCEMETA_CORE_ADDRESS_SANITIZER)
119124
sourcemeta_sanitizer(TYPE address)
120125
elseif(SOURCEMETA_CORE_UNDEFINED_SANITIZER)
@@ -198,6 +203,10 @@ if(SOURCEMETA_CORE_TESTS)
198203
add_subdirectory(test/alterschema)
199204
endif()
200205

206+
if(SOURCEMETA_CORE_EXTENSION_OPTIONS)
207+
add_subdirectory(test/options)
208+
endif()
209+
201210
if(PROJECT_IS_TOP_LEVEL)
202211
# Otherwise we need the child project to link
203212
# against the sanitizers too.

config.cmake.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ if(NOT SOURCEMETA_CORE_COMPONENTS)
1717
list(APPEND SOURCEMETA_CORE_COMPONENTS jsonschema)
1818
list(APPEND SOURCEMETA_CORE_COMPONENTS yaml)
1919
list(APPEND SOURCEMETA_CORE_COMPONENTS alterschema)
20+
list(APPEND SOURCEMETA_CORE_COMPONENTS options)
2021
endif()
2122

2223
include(CMakeFindDependencyMacro)
@@ -70,6 +71,8 @@ foreach(component ${SOURCEMETA_CORE_COMPONENTS})
7071
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
7172
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonschema.cmake")
7273
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_alterschema.cmake")
74+
elseif(component STREQUAL "options")
75+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_options.cmake")
7376
else()
7477
message(FATAL_ERROR "Unknown Sourcemeta Core component: ${component}")
7578
endif()

src/extension/options/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME options
2+
PRIVATE_HEADERS error.h SOURCES options.cc)
3+
4+
if(SOURCEMETA_CORE_INSTALL)
5+
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME options)
6+
endif()
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#ifndef SOURCEMETA_CORE_OPTIONS_H_
2+
#define SOURCEMETA_CORE_OPTIONS_H_
3+
4+
#ifndef SOURCEMETA_CORE_OPTIONS_EXPORT
5+
#include <sourcemeta/core/options_export.h>
6+
#endif
7+
8+
// NOLINTBEGIN(misc-include-cleaner)
9+
#include <sourcemeta/core/options_error.h>
10+
// NOLINTEND(misc-include-cleaner)
11+
12+
#include <cstddef> // std::size_t
13+
#include <initializer_list> // std::initializer_list
14+
#include <memory> // std::unique_ptr
15+
#include <string> // std::string
16+
#include <string_view> // std::string_view
17+
#include <unordered_map> // std::unordered_map
18+
#include <unordered_set> // std::unordered_set
19+
#include <vector> // std::vector
20+
21+
/// @defgroup options Options
22+
/// @brief A simple and minimalistic UNIX-style command-line parsing library
23+
///
24+
/// This functionality is included as follows:
25+
///
26+
/// ```cpp
27+
/// #include <sourcemeta/core/options.h>
28+
/// ```
29+
30+
namespace sourcemeta::core {
31+
32+
/// @ingroup options
33+
/// Command-line option parsing modifiers
34+
struct OptionsModifiers {
35+
/// Ignore the first N command-line arguments
36+
std::size_t skip{0};
37+
};
38+
39+
/// @ingroup options
40+
///
41+
/// This class performs basic command-line argument parsing based on options,
42+
/// flags, and aliases. For example:
43+
///
44+
/// ```cpp
45+
/// #include <sourcemeta/core/options.h>
46+
/// #include <cstdlib>
47+
/// #include <iostream>
48+
///
49+
/// auto main(int argc, char *argv[]) -> int {
50+
/// sourcemeta::core::Options app;
51+
/// app.option("name", {"n"});
52+
/// app.flag("shout", {"s"});
53+
/// app.parse(argc, argv);
54+
///
55+
/// if (!app.contains("name")) {
56+
/// std::cerr << "Missing name\n";
57+
/// return EXIT_FAILURE;
58+
/// }
59+
///
60+
/// std::cerr << "Hello, " << app.at("name").front();
61+
/// if (app.contains("shout")) {
62+
/// std::cerr << "!\n";
63+
/// } else {
64+
/// std::cerr << "\n";
65+
/// }
66+
///
67+
/// return EXIT_SUCCESS;
68+
/// }
69+
/// ```
70+
class SOURCEMETA_CORE_OPTIONS_EXPORT Options {
71+
public:
72+
Options() = default;
73+
// Disallow copies given the unique pointers
74+
Options(const Options &) = delete;
75+
auto operator=(const Options &) -> Options & = delete;
76+
Options(Options &&) noexcept = default;
77+
auto operator=(Options &&) noexcept -> Options & = default;
78+
79+
/// Declare a new option, which must take a value
80+
auto option(std::string &&name, std::initializer_list<std::string> aliases)
81+
-> void;
82+
83+
/// Declare a new flag, which must not take a value
84+
auto flag(std::string &&name, std::initializer_list<std::string> aliases)
85+
-> void;
86+
87+
/// Access the values (if any) set for an option or flag, by its main name
88+
[[nodiscard]] auto at(std::string_view name) const
89+
-> const std::vector<std::string_view> &;
90+
91+
/// Check if an option or flag was set, by its main name
92+
[[nodiscard]] auto contains(std::string_view name) const -> bool;
93+
94+
/// Access the positional arguments, if any
95+
[[nodiscard]] auto positional() const
96+
-> const std::vector<std::string_view> &;
97+
98+
/// Parse program arguments given the declared options and flags
99+
auto parse(const int argc,
100+
// We want to be compatible with `main`s `argv`
101+
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
102+
const char *const argv[], const OptionsModifiers options = {})
103+
-> void;
104+
105+
private:
106+
// Exporting symbols that depends on the standard C++ library is considered
107+
// safe.
108+
// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
109+
#if defined(_MSC_VER)
110+
#pragma warning(disable : 4251 4275)
111+
#endif
112+
static constexpr std::string_view POSITIONAL_ARGUMENT_NAME{""};
113+
static constexpr std::vector<std::string_view> EMPTY{};
114+
115+
std::vector<std::unique_ptr<std::string>> storage;
116+
std::unordered_map<std::string_view, std::string_view> aliases_;
117+
std::unordered_map<std::string_view, std::vector<std::string_view>> options_;
118+
std::unordered_set<std::string_view> flags;
119+
#if defined(_MSC_VER)
120+
#pragma warning(default : 4251 4275)
121+
#endif
122+
};
123+
124+
} // namespace sourcemeta::core
125+
126+
#endif // SOURCEMETA_CORE_OPTIONS_H_
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#ifndef SOURCEMETA_CORE_OPTIONS_ERROR_H_
2+
#define SOURCEMETA_CORE_OPTIONS_ERROR_H_
3+
4+
#ifndef SOURCEMETA_CORE_OPTIONS_EXPORT
5+
#include <sourcemeta/core/options_export.h>
6+
#endif
7+
8+
#include <stdexcept> // std::runtime_error
9+
#include <string> // std::string
10+
#include <utility> // std::move
11+
12+
namespace sourcemeta::core {
13+
14+
// Exporting symbols that depends on the standard C++ library is considered
15+
// safe.
16+
// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
17+
#if defined(_MSC_VER)
18+
#pragma warning(disable : 4251 4275)
19+
#endif
20+
21+
/// @ingroup options
22+
/// This class represents a general options error
23+
struct SOURCEMETA_CORE_OPTIONS_EXPORT OptionError : public std::runtime_error {
24+
explicit OptionError(const std::string &message)
25+
: std::runtime_error{message} {}
26+
};
27+
28+
/// @ingroup options
29+
/// This class represents a unknown option error
30+
struct SOURCEMETA_CORE_OPTIONS_EXPORT OptionsUnknownOptionError
31+
: public OptionError {
32+
explicit OptionsUnknownOptionError(std::string name)
33+
: OptionError{"Unknown option"}, name_{std::move(name)} {}
34+
[[nodiscard]] auto name() const -> const auto & { return this->name_; }
35+
36+
private:
37+
std::string name_;
38+
};
39+
40+
/// @ingroup options
41+
/// This class represents a value being passed to a flag
42+
struct SOURCEMETA_CORE_OPTIONS_EXPORT OptionUnexpectedValueFlagError
43+
: public OptionError {
44+
explicit OptionUnexpectedValueFlagError(std::string name)
45+
: OptionError{"Cannot pass a value to a flag"}, name_{std::move(name)} {}
46+
[[nodiscard]] auto name() const -> const auto & { return this->name_; }
47+
48+
private:
49+
std::string name_;
50+
};
51+
52+
/// @ingroup options
53+
/// This class represents a missing value from an option
54+
struct SOURCEMETA_CORE_OPTIONS_EXPORT OptionMissingOptionValueError
55+
: public OptionError {
56+
explicit OptionMissingOptionValueError(std::string name)
57+
: OptionError{"This option takes a value"}, name_{std::move(name)} {}
58+
[[nodiscard]] auto name() const -> const auto & { return this->name_; }
59+
60+
private:
61+
std::string name_;
62+
};
63+
64+
#if defined(_MSC_VER)
65+
#pragma warning(default : 4251 4275)
66+
#endif
67+
68+
} // namespace sourcemeta::core
69+
70+
#endif

0 commit comments

Comments
 (0)