Skip to content

Commit ae28d03

Browse files
committed
[cli_args] Add unittest
1 parent b8311de commit ae28d03

File tree

10 files changed

+317
-136
lines changed

10 files changed

+317
-136
lines changed

cli_args/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ add_library(cli_args INTERFACE)
1111
target_include_directories(cli_args INTERFACE include "${GSL_INCLUDE_DIR}")
1212

1313
add_subdirectory(samples)
14+
add_subdirectory(test)

cli_args/include/cli_args/cli_args.h

Lines changed: 6 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
/// has been parsed.
4747
/// * cl::app to specify the application the option is intended for
4848

49+
#include "cli_args_config.h"
50+
#include "cli_args_help.h"
4951
#include "cli_args_parser.h"
5052

5153
#include <algorithm>
@@ -55,50 +57,10 @@
5557
#include <set>
5658
#include <vector>
5759

58-
namespace cli_args {
59-
namespace detail {
60-
struct CliLibCfgStd {
61-
using string_span = detail::CliOptConcept::string_span;
62-
63-
static std::ostream &outs() { return std::cout; }
64-
static std::ostream &errs() { return std::cerr; }
65-
66-
template <typename T>
67-
static std::optional<T> parse(const std::string_view &value);
68-
69-
static void report(const cli_args::error::MaxPositionalExceededError &err) {
70-
errs() << "Too many positional arguments given:\n";
71-
for (const std::string_view &arg : err.exceeding)
72-
errs() << arg << '\n';
73-
}
74-
static void report(const cli_args::error::UnknownOptionError &err) {
75-
errs() << "Encountered unknown option " << err.option << '\n';
76-
}
77-
static void report(const cli_args::error::ParseError &err) {}
78-
static void report(const cli_args::error::ValidationError &err) {
79-
const auto &names = err.option.getNames();
80-
errs() << "Missing required value for option \""
81-
<< (names.empty() ? err.option.getMeta() : names.at(0)) << "\"\n";
82-
}
83-
static int handleUnrecognized(const std::string_view &theOption,
84-
const string_span &theValues) {
85-
return onunrecognized()(theOption, theValues);
86-
}
87-
88-
using UnrecognizedCB =
89-
std::function<int(const std::string_view &, const string_span &)>;
90-
91-
static UnrecognizedCB &onunrecognized() {
92-
static UnrecognizedCB callback = [](const std::string_view &,
93-
const string_span &) { return -1; };
94-
return callback;
95-
}
96-
};
97-
} // namespace detail
98-
} // namespace cli_args
99-
60+
#include "parsers/bool.h"
10061
#include "parsers/path.h"
10162
#include "parsers/string.h"
63+
#include "parsers/string_view.h"
10264
#include "parsers/unsigned.h"
10365

10466
namespace cli_args {
@@ -108,68 +70,9 @@ template <typename T> using list = api<cfg>::list<T>;
10870
template <typename AppTag = void>
10971
struct ParseArgs : public api<cfg>::ParseArgs<AppTag> {
11072
using base_t = api<cfg>::ParseArgs<AppTag>;
111-
ParseArgs(int argc, const char **argv, int offset = 1)
112-
: base_t(argc, argv, offset) {}
73+
ParseArgs(int argc, const char **argv, const cfg &config = {})
74+
: base_t(argc, argv, config) {}
11375
};
114-
115-
/// TODO Allow influencing display order
116-
template <typename AppTag = void>
117-
inline void PrintHelp(const char *tool, const char *desc, std::ostream &os) {
118-
using namespace ::cli_args::detail;
119-
const CliOptRegistry<AppTag> &registry = CliOptRegistry<AppTag>::get();
120-
const auto display = [](const detail::CliOptConcept &opt, std::ostream &os) {
121-
if (!opt.getNames().empty() && opt.getNames().at(0) != "") {
122-
const detail::CliOptConcept::string_span names = opt.getNames();
123-
const auto nameBegin = names.begin(), nameEnd = names.end();
124-
unsigned firstColWidth = 0,
125-
maxFirstColWidth = 18; // FIXME this is a pretty arbitrary value
126-
for (auto nameIt = nameBegin; nameIt != nameEnd; ++nameIt) {
127-
if (nameIt != nameBegin) {
128-
os << ", ";
129-
firstColWidth += 2;
130-
}
131-
if (nameIt->size() == 1)
132-
os << "-";
133-
else
134-
os << "--";
135-
os << *nameIt;
136-
firstColWidth += 1 + nameIt->size() + (nameIt->size() > 1);
137-
}
138-
std::ios_base::fmtflags defaultFmtFlags(os.flags());
139-
os << std::setw(maxFirstColWidth - firstColWidth) << "" << std::setw(0)
140-
<< opt.getDescription();
141-
os.flags(defaultFmtFlags);
142-
} else
143-
os << opt.getMeta();
144-
};
145-
// FIXME this is still in MVP state
146-
os << "Usage: " << tool << " [OPTION]...";
147-
const CliOptConcept *eatAll = nullptr;
148-
if (registry.hasUnnamed()) {
149-
eatAll = &registry.getUnnamed();
150-
os << " ";
151-
if (!eatAll->isRequired())
152-
os << "[";
153-
display(*eatAll, os);
154-
if (!eatAll->isRequired())
155-
os << "]";
156-
os << "...";
157-
}
158-
os << "\n"
159-
<< "Options:\n";
160-
std::vector<CliOptConcept *> optsSorted(registry.begin(), registry.end());
161-
std::sort(optsSorted.begin(), optsSorted.end(),
162-
[](CliOptConcept *A, CliOptConcept *B) {
163-
return A->getShortestName().compare(B->getShortestName()) < 0;
164-
});
165-
for (const CliOptConcept *opt : optsSorted) {
166-
if (opt == eatAll)
167-
continue;
168-
os << " ";
169-
display(*opt, os);
170-
os << '\n';
171-
}
172-
}
17376
} // namespace cli_args
17477

17578
#endif // CLI_ARGS_CLI_ARGS_H
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#ifndef CLI_ARGS_CLI_ARGS_CONFIG_H
2+
#define CLI_ARGS_CLI_ARGS_CONFIG_H
3+
4+
#include <iostream>
5+
#include <optional>
6+
7+
#include "cli_args/cli_args_base.h"
8+
#include "cli_args/cli_args_errors.h"
9+
10+
namespace cli_args {
11+
namespace detail {
12+
13+
/// Bare-bones do-nothing implementation of the CliLibCfg concept
14+
struct CliLibCfgBase {
15+
CliLibCfgBase() = default;
16+
CliLibCfgBase(int offset) : offset(offset) {}
17+
18+
void report(const cli_args::error::MaxPositionalExceededError &err) const {}
19+
void report(const cli_args::error::UnknownOptionError &err) const {}
20+
void report(const cli_args::error::ParseError &err) const {}
21+
void report(const cli_args::error::ValidationError &err) const {}
22+
23+
using string_span = detail::CliOptConcept::string_span;
24+
25+
int handle_unrecognized(const std::string_view &, const string_span &) const {
26+
return -1;
27+
}
28+
29+
int offset{1};
30+
};
31+
32+
/// Basic implementation of the CliLibCfg concept sufficient for most use cases
33+
struct CliLibCfgStd : public CliLibCfgBase {
34+
template <typename T>
35+
static std::optional<T> parse(const std::string_view &value);
36+
37+
static void report(const cli_args::error::MaxPositionalExceededError &err) {
38+
errs() << "Too many positional arguments given:\n";
39+
for (const std::string_view &arg : err.exceeding)
40+
errs() << arg << '\n';
41+
}
42+
static void report(const cli_args::error::UnknownOptionError &err) {
43+
errs() << "Encountered unknown option " << err.option << '\n';
44+
}
45+
static void report(const cli_args::error::ParseError &err) {}
46+
static void report(const cli_args::error::ValidationError &err) {
47+
const auto &names = err.option->getNames();
48+
errs() << "Missing required value for option \""
49+
<< (names.empty() ? err.option->getMeta() : names.at(0)) << "\"\n";
50+
}
51+
52+
int handle_unrecognized(const std::string_view &opt,
53+
const string_span &vals) const {
54+
return unrecognized_handler(opt, vals);
55+
}
56+
57+
using callback_unrecognized =
58+
std::function<int(const std::string_view &, const string_span &)>;
59+
60+
static callback_unrecognized &onunrecognized() {
61+
static callback_unrecognized callback =
62+
[](const std::string_view &, const string_span &) { return -1; };
63+
return callback;
64+
}
65+
66+
// Can also be set on the config instance
67+
callback_unrecognized &unrecognized_handler{onunrecognized()};
68+
69+
static std::ostream &errs() { return std::cerr; }
70+
};
71+
} // namespace detail
72+
} // namespace cli_args
73+
#endif // CLI_ARGS_CLI_ARGS_CONFIG_H

cli_args/include/cli_args/cli_args_errors.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
namespace cli_args {
88
namespace error {
99
struct MaxPositionalExceededError {
10-
const gsl::span<std::string_view> exceeding;
10+
gsl::span<std::string_view> exceeding;
1111
};
1212
struct UnknownOptionError {
13-
const std::string_view option;
13+
std::string_view option;
1414
};
1515
struct ParseError {
16-
const std::string_view name;
17-
const gsl::span<std::string_view> values;
18-
const cli_args::detail::CliOptConcept &option;
16+
std::string_view name;
17+
gsl::span<std::string_view> values;
18+
const cli_args::detail::CliOptConcept *option;
1919
};
2020
struct ValidationError {
21-
const cli_args::detail::CliOptConcept &option;
21+
const cli_args::detail::CliOptConcept *option;
2222
};
2323
} // namespace error
2424
} // namespace cli_args
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#ifndef CLI_ARGS_CLI_ARGS_HELP_H
2+
#define CLI_ARGS_CLI_ARGS_HELP_H
3+
4+
#include <algorithm>
5+
#include <iomanip>
6+
#include <ostream>
7+
#include <vector>
8+
9+
namespace cli_args {
10+
/// TODO Allow influencing display order
11+
template <typename AppTag = void>
12+
inline void PrintHelp(const char *tool, const char *desc, std::ostream &os) {
13+
using namespace ::cli_args::detail;
14+
const CliOptRegistry<AppTag> &registry = CliOptRegistry<AppTag>::get();
15+
const auto display = [](const detail::CliOptConcept &opt, std::ostream &os) {
16+
if (!opt.getNames().empty() && opt.getNames().at(0) != "") {
17+
const detail::CliOptConcept::string_span names = opt.getNames();
18+
const auto nameBegin = names.begin(), nameEnd = names.end();
19+
unsigned firstColWidth = 0,
20+
maxFirstColWidth = 18; // FIXME this is a pretty arbitrary value
21+
for (auto nameIt = nameBegin; nameIt != nameEnd; ++nameIt) {
22+
if (nameIt != nameBegin) {
23+
os << ", ";
24+
firstColWidth += 2;
25+
}
26+
if (nameIt->size() == 1)
27+
os << "-";
28+
else
29+
os << "--";
30+
os << *nameIt;
31+
firstColWidth += 1 + nameIt->size() + (nameIt->size() > 1);
32+
}
33+
std::ios_base::fmtflags defaultFmtFlags(os.flags());
34+
os << std::setw(maxFirstColWidth - firstColWidth) << "" << std::setw(0)
35+
<< opt.getDescription();
36+
os.flags(defaultFmtFlags);
37+
} else
38+
os << opt.getMeta();
39+
};
40+
// FIXME this is still in MVP state
41+
os << "Usage: " << tool << " [OPTION]...";
42+
const CliOptConcept *eatAll = nullptr;
43+
if (registry.hasUnnamed()) {
44+
eatAll = &registry.getUnnamed();
45+
os << " ";
46+
if (!eatAll->isRequired())
47+
os << "[";
48+
display(*eatAll, os);
49+
if (!eatAll->isRequired())
50+
os << "]";
51+
os << "...";
52+
}
53+
os << "\n"
54+
<< "Options:\n";
55+
std::vector<CliOptConcept *> optsSorted(registry.begin(), registry.end());
56+
std::sort(optsSorted.begin(), optsSorted.end(),
57+
[](CliOptConcept *A, CliOptConcept *B) {
58+
return A->getShortestName().compare(B->getShortestName()) < 0;
59+
});
60+
for (const CliOptConcept *opt : optsSorted) {
61+
if (opt == eatAll)
62+
continue;
63+
os << " ";
64+
display(*opt, os);
65+
os << '\n';
66+
}
67+
}
68+
} // namespace cli_args
69+
#endif // CLI_ARGS_CLI_ARGS_HELP_H

0 commit comments

Comments
 (0)