Skip to content

Commit b5b3e3f

Browse files
committed
argument parser documentation
1 parent bd7109b commit b5b3e3f

File tree

2 files changed

+143
-72
lines changed

2 files changed

+143
-72
lines changed

include/scl/cmdline.h

Lines changed: 127 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
#include <iostream>
2424
#include <optional>
2525
#include <stdexcept>
26-
#include <string>
2726
#include <string_view>
2827
#include <unordered_map>
2928
#include <variant>
@@ -32,65 +31,81 @@
3231
namespace scl {
3332

3433
/**
35-
* @brief Simple command line argument parser.
34+
* @brief Container for arguments and flags parsed by ProgramOptions::Parser.
3635
*
37-
* ProgramOptions allows defining and parsing options for a program in a limited
38-
* manner using a builder pattern. For example:
36+
* ProgramOptions holds the result after parsing the stuff in <code>argv</code>;
37+
* typically, this would be options, flags and so on.
3938
*
39+
* The interface of ProgramOptions is pretty intuitive
4040
* @code
41-
* auto p = ProgramOptions::Parser("some description")
42-
* .add(ProgramArg::required("foo", "int", "foo description"))
43-
* .add(ProgramArg::optional("bar", "bool", "123"))
44-
* .add(ProgramFlag("flag"))
45-
* .parse(argc, argv);
41+
* ProgramOptions opts = ... //
42+
*
43+
* // check if "-foo 123" was passed in argv
44+
* opts.has("foo");
45+
* int foo = opts.get<int>("foo");
46+
* assert(foo == 123);
47+
*
48+
* // the non templated version of get simply returns the argument as
49+
* // an std::string_view
50+
* std::string_view foo_str = opts.get("foo");
51+
* assert(foo_str == "123");
52+
*
53+
* // check if "-some_flag" was passed in argv
54+
* opts.flagSet("some_flag");
4655
* @endcode
4756
*
48-
* The above snippet will parse the <code>argv</code> argument vector passed to
49-
* a program looking for arguments <code>-foo value</code> and
50-
* <code>flag</code>. The <code>bar</code> is optional and if not explicitly
51-
* supplied, gets the default value <code>"123"</code>.
57+
* ProgramOptions::get stores argument values internally as
58+
* strings. Specialization is, of course, allowed.
59+
*
60+
* @code
61+
* struct FooStruct {
62+
* int x;
63+
* int y;
64+
* };
65+
*
66+
* template <>
67+
* FooStruct scl::ProgramOptions::get<FooStruct>(std::string_view name) const {
68+
* FooStruct r;
69+
* const auto arg_val = m_args.at(name);
70+
* // turn the string arg_val into a FooStruct
71+
* return r;
72+
* }
73+
*
74+
* // this will enable us to write
75+
* FooStruct x = opts.get<FooStruct>();
76+
* @endcode
77+
*
78+
* Specializations exist for <code>int</code>, <code>std::size_t</code> and
79+
* <code>bool</code>. For the latter, the strings "1" and "true" are treated as
80+
* <code>true</code>, while everything else is treated as <code>false</code>.
5281
*/
5382
class ProgramOptions {
5483
public:
5584
class Parser;
5685

5786
/**
5887
* @brief Check if some argument has been provided.
59-
* @param name the name of the argument.
60-
* @return true if the argument was set, false otherwise.
6188
*/
6289
bool has(std::string_view name) const {
6390
return m_args.find(name) != m_args.end();
6491
}
6592

6693
/**
6794
* @brief Check if a flag has been set.
68-
* @param name the name of the flag.
69-
* @return true if the flag was set, false otherwise.
7095
*/
7196
bool flagSet(std::string_view name) const {
7297
return m_flags.find(name) != m_flags.end();
7398
}
7499

75100
/**
76101
* @brief Get the raw value of an argument.
77-
* @param name the name of the argument.
78-
* @return the value of the argument, as is.
79102
*/
80103
std::string_view get(std::string_view name) const {
81104
return m_args.at(name);
82105
}
83106

84107
/**
85108
* @brief Get the value of an argument with conversion.
86-
* @tparam T the type to convert the argument to.
87-
* @param name the name of the argument.
88-
* @return the value of the argument after conversion.
89-
*
90-
* Specializations exist for this function for <code>bool</code>,
91-
* <code>int</code> and <code>std::size_t</code>. It is possible to provide
92-
* custom specializations that can be used to turn a string into any kind of
93-
* object.
94109
*/
95110
template <typename T>
96111
T get(std::string_view name) const;
@@ -105,41 +120,27 @@ class ProgramOptions {
105120
std::unordered_map<std::string_view, bool> m_flags;
106121
};
107122

108-
/**
109-
* @brief Specialization of CmdArgs::Get for <code>bool</code>.
110-
*/
111123
template <>
112-
inline bool ProgramOptions::get<bool>(std::string_view name) const {
113-
const auto v = m_args.at(name);
114-
return v == "1" || v == "true";
115-
}
124+
bool ProgramOptions::get<bool>(std::string_view name) const;
116125

117-
/**
118-
* @brief Specialization for CmdArgs::Get for <code>int</code>.
119-
*/
120126
template <>
121-
inline int ProgramOptions::get<int>(std::string_view name) const {
122-
return std::stoi(m_args.at(name).data());
123-
}
127+
int ProgramOptions::get<int>(std::string_view name) const;
124128

125-
/**
126-
* @brief Specialization of CmdArgs::Get for <code>std::size_t</code>.
127-
*/
128129
template <>
129-
inline std::size_t ProgramOptions::get<std::size_t>(
130-
std::string_view name) const {
131-
return std::stoul(m_args.at(name).data());
132-
}
130+
std::size_t ProgramOptions::get<std::size_t>(std::string_view name) const;
133131

134132
/**
135133
* @brief An command-line argument definition.
136134
*/
137135
struct ProgramArg {
138136
/**
139137
* @brief Create a required command-line argument.
140-
* @param name the name.
141-
* @param type_hint a string describing the expected type. E.g., "int".
142-
* @param description a short description.
138+
*
139+
* This creates a required program argument. Adding a ProgramArg to a
140+
* ProgramOptions::Parser through this call with a \p name value of
141+
* <code>"something"</code> means that the caller of our program must supply
142+
* <code>-something</code> when calling our program. The type hint is purely
143+
* cosmetic.
143144
*/
144145
static ProgramArg required(std::string_view name,
145146
std::string_view type_hint,
@@ -149,10 +150,9 @@ struct ProgramArg {
149150

150151
/**
151152
* @brief Create an optional command-line argument.
152-
* @param name the name.
153-
* @param type_hint a string describing the expected type. E.g., "int".
154-
* @param default_value an optional default value.
155-
* @param description a short description.
153+
*
154+
* This creates an optional program argument. In case the argument is not
155+
* supplied by the caller of our program, the \p default_value will be used.
156156
*/
157157
static ProgramArg optional(std::string_view name,
158158
std::string_view type_hint,
@@ -193,8 +193,6 @@ struct ProgramArg {
193193
struct ProgramFlag {
194194
/**
195195
* @brief Create a flag argument.
196-
* @param name the name of the flag.
197-
* @param description a description.
198196
*/
199197
ProgramFlag(std::string_view name, std::string_view description = "")
200198
: name(name), description(description) {}
@@ -211,22 +209,90 @@ struct ProgramFlag {
211209
};
212210

213211
/**
214-
* @brief Argument parser.
212+
* @brief Argument parser for command-line options.
215213
*
216-
* The parser accepts argument defintions (through the Add functions) and
217-
* parses the arguments provided to the main function into a CmdArgs object.
214+
* Parser provides a builder for constructing a ProgramOptions object based on
215+
* the stuff in <code>argv</code>.
216+
*
217+
* Parser permits the user to create three different types of program arguments:
218+
* - ProgramArg::required creates an option which accepts one argument, and
219+
* which must be provided for the program to Parser::parse to work.
220+
* - ProgramArg::optional creates an option which accepts one argument, and
221+
* which is optional.
222+
* - ProgramFlag creates a "flag", or toggle argument.
223+
*
224+
* @code
225+
* // example.cc
226+
* #include <scl/cmdline.h>
227+
* #include <iostream>
228+
*
229+
* using namespace scl;
230+
*
231+
* int main(int argc, char** argv) {
232+
* auto parser = ProgramOptions::Parser("super awesome program")
233+
* .add(ProgramArg::required("foo", "int", "foo"))
234+
* .add(ProgramArg::optional("bar", "bool", "true", "bar"))
235+
* .add(ProgramFlag("baz", "baz"));
236+
*
237+
* auto opts = parser.parse(argc, argv);
238+
*
239+
* std::cout << "foo = " << opts.get<int>("foo") << "\n";
240+
* std::cout << "bar = " << std::boolalpha << opts.get<bool>("bar") << "\n";
241+
* std::cout << "baz set? " << std::boolalpha << opts.flagSet("baz") << "\n";
242+
* }
243+
* @endcode
244+
*
245+
* \code{.unparsed}
246+
* $ g++ example.cc -lscl
247+
* $ ./a.out
248+
* ERROR: missing required argument
249+
* Usage: ./a.out -foo int [options ...]
250+
*
251+
* super awesome program
252+
*
253+
* Required arguments
254+
* -foo 'int' foo.
255+
*
256+
* Optional arguments
257+
* -bar 'bool' bar. [default=true]
258+
*
259+
* Flags
260+
* -baz baz.
261+
*
262+
* $ ./a.out -help
263+
* Usage: ./a.out -foo int [options ...]
264+
*
265+
* super awesome program
266+
*
267+
* Required arguments
268+
* -foo 'int' foo.
269+
*
270+
* Optional arguments
271+
* -bar 'bool' bar. [default=true]
272+
*
273+
* Flags
274+
* -baz baz.
275+
*
276+
* $ ./a.out -foo 42
277+
* foo = 42
278+
* bar = true
279+
* baz set? false
280+
* $ ./a.out -foo 100 -bar false -baz
281+
* foo = 100
282+
* bar = false
283+
* baz set? true
284+
* $
285+
* \endcode
218286
*/
219287
class ProgramOptions::Parser {
220288
public:
221289
/**
222290
* @brief Create a command-line argument parser.
223-
* @param description a short description of the program.
224291
*/
225292
Parser(std::string_view description = "") : m_description(description) {}
226293

227294
/**
228295
* @brief Define an argument.
229-
* @param def an argument definition.
230296
*/
231297
Parser& add(const ProgramArg& def) {
232298
m_args.emplace_back(def);
@@ -235,7 +301,6 @@ class ProgramOptions::Parser {
235301

236302
/**
237303
* @brief Define a flag argument.
238-
* @param flag a flag definition.
239304
*/
240305
Parser& add(const ProgramFlag& flag) {
241306
m_flags.emplace_back(flag);
@@ -244,22 +309,12 @@ class ProgramOptions::Parser {
244309

245310
/**
246311
* @brief Parse arguments.
247-
* @param argc the number of arguments.
248-
* @param argv the arguments.
249-
* @return the program options, or an error message.
250-
*
251-
* The \p argc and \p argv are assumed to be the inputs to a programs main
252-
* function.
253312
*/
254313
std::variant<ProgramOptions, std::string_view> parseArguments(int argc,
255314
char* argv[]);
256315

257316
/**
258317
* @brief Parse arguments.
259-
* @param argc the number of arguments.
260-
* @param argv the arguments.
261-
* @param exit_on_error whether to std::exit when parsing fails
262-
* @return a set of program options.
263318
*/
264319
ProgramOptions parse(int argc, char* argv[], bool exit_on_error = true) {
265320
auto opts = parseArguments(argc, argv);

src/scl/cmdline.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@
1919

2020
#include <vector>
2121

22+
template <>
23+
bool scl::ProgramOptions::get<bool>(std::string_view name) const {
24+
const auto v = m_args.at(name);
25+
return v == "1" || v == "true";
26+
}
27+
28+
template <>
29+
int scl::ProgramOptions::get<int>(std::string_view name) const {
30+
return std::stoi(m_args.at(name).data());
31+
}
32+
33+
template <>
34+
std::size_t scl::ProgramOptions::get<std::size_t>(std::string_view name) const {
35+
return std::stoul(m_args.at(name).data());
36+
}
37+
2238
bool scl::ProgramOptions::Parser::isArg(std::string_view name) const {
2339
return std::any_of(m_args.begin(), m_args.end(), [&](auto a) {
2440
return a.name == name;

0 commit comments

Comments
 (0)