Skip to content

Commit 5b714bb

Browse files
authored
Merge pull request #15 from CLIUtils/basic-enum
Adding enum support
2 parents c435239 + 0973f34 commit 5b714bb

File tree

6 files changed

+263
-16
lines changed

6 files changed

+263
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
## Version 1.1 (in progress)
2+
3+
* Added simple support for enumerations, allow non-printable objects [#12](https://github.com/CLIUtils/CLI11/issues/12)
24
* Added `app.parse_order()` with original parse order ([#13](https://github.com/CLIUtils/CLI11/issues/13), [#16](https://github.com/CLIUtils/CLI11/pull/16))
35
* Added `prefix_command()`, which is like `allow_extras` but instantly stops and returns. ([#8](https://github.com/CLIUtils/CLI11/issues/8), [#17](https://github.com/CLIUtils/CLI11/pull/17))
46
* Removed Windows error ([#10](https://github.com/CLIUtils/CLI11/issues/10), [#20](https://github.com/CLIUtils/CLI11/pull/20))
57
* Some improvements to CMake, detect Python and no dependencies on Python 2 (like Python 3) ([#18](https://github.com/CLIUtils/CLI11/issues/18), [#21](https://github.com/CLIUtils/CLI11/pull/21))
68

7-
89
## Version 1.0
910
* Cleanup using `clang-tidy` and `clang-format`
1011
* Small improvements to Timers, easier to subclass Error

examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ add_cli_exe(subcommands subcommands.cpp)
1919
add_cli_exe(groups groups.cpp)
2020
add_cli_exe(inter_argument_order inter_argument_order.cpp)
2121
add_cli_exe(prefix_command prefix_command.cpp)
22+
add_cli_exe(enum enum.cpp)

examples/enum.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <CLI/CLI.hpp>
2+
3+
enum Level : std::int32_t {
4+
High,
5+
Medium,
6+
Low
7+
};
8+
9+
int main(int argc, char** argv) {
10+
CLI::App app;
11+
12+
Level level;
13+
app.add_set("-l,--level", level, {High, Medium, Low}, "Level settings")
14+
->set_type_name("enum/Level in {High=0, Medium=1, Low=2}");
15+
16+
try {
17+
app.parse(argc, argv);
18+
} catch (CLI::Error const& e) {
19+
app.exit(e);
20+
}
21+
return 0;
22+
}
23+

include/CLI/App.hpp

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,20 +225,39 @@ class App {
225225
} else
226226
throw OptionAlreadyAdded(myopt.get_name());
227227
}
228+
229+
228230

229-
/// Add option for non-vectors
231+
/// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
230232
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
231233
Option *add_option(std::string name,
232234
T &variable, ///< The variable to set
233-
std::string description = "",
234-
bool defaulted = false) {
235+
std::string description = "") {
235236

236237
CLI::callback_t fun = [&variable](CLI::results_t res) {
237238
if(res.size() != 1)
238239
return false;
239240
return detail::lexical_cast(res[0], variable);
240241
};
241242

243+
Option *opt = add_option(name, fun, description, false);
244+
opt->set_custom_option(detail::type_name<T>());
245+
return opt;
246+
}
247+
248+
/// Add option for non-vectors with a default print
249+
template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
250+
Option *add_option(std::string name,
251+
T &variable, ///< The variable to set
252+
std::string description,
253+
bool defaulted) {
254+
255+
CLI::callback_t fun = [&variable](CLI::results_t res) {
256+
if(res.size() != 1)
257+
return false;
258+
return detail::lexical_cast(res[0], variable);
259+
};
260+
242261
Option *opt = add_option(name, fun, description, defaulted);
243262
opt->set_custom_option(detail::type_name<T>());
244263
if(defaulted) {
@@ -248,13 +267,34 @@ class App {
248267
}
249268
return opt;
250269
}
251-
270+
271+
/// Add option for vectors (no default)
272+
template <typename T>
273+
Option *add_option(std::string name,
274+
std::vector<T> &variable, ///< The variable vector to set
275+
std::string description = "") {
276+
277+
CLI::callback_t fun = [&variable](CLI::results_t res) {
278+
bool retval = true;
279+
variable.clear();
280+
for(const auto &a : res) {
281+
variable.emplace_back();
282+
retval &= detail::lexical_cast(a, variable.back());
283+
}
284+
return (!variable.empty()) && retval;
285+
};
286+
287+
Option *opt = add_option(name, fun, description, false);
288+
opt->set_custom_option(detail::type_name<T>(), -1, true);
289+
return opt;
290+
}
291+
252292
/// Add option for vectors
253293
template <typename T>
254294
Option *add_option(std::string name,
255295
std::vector<T> &variable, ///< The variable vector to set
256-
std::string description = "",
257-
bool defaulted = false) {
296+
std::string description,
297+
bool defaulted) {
258298

259299
CLI::callback_t fun = [&variable](CLI::results_t res) {
260300
bool retval = true;
@@ -323,13 +363,12 @@ class App {
323363
return opt;
324364
}
325365

326-
/// Add set of options
366+
/// Add set of options (No default)
327367
template <typename T>
328368
Option *add_set(std::string name,
329369
T &member, ///< The selected member of the set
330370
std::set<T> options, ///< The set of posibilities
331-
std::string description = "",
332-
bool defaulted = false) {
371+
std::string description = "") {
333372

334373
CLI::callback_t fun = [&member, options](CLI::results_t res) {
335374
if(res.size() != 1) {
@@ -341,6 +380,31 @@ class App {
341380
return std::find(std::begin(options), std::end(options), member) != std::end(options);
342381
};
343382

383+
Option *opt = add_option(name, fun, description, false);
384+
std::string typeval = detail::type_name<T>();
385+
typeval += " in {" + detail::join(options) + "}";
386+
opt->set_custom_option(typeval);
387+
return opt;
388+
}
389+
390+
/// Add set of options
391+
template <typename T>
392+
Option *add_set(std::string name,
393+
T &member, ///< The selected member of the set
394+
std::set<T> options, ///< The set of posibilities
395+
std::string description,
396+
bool defaulted) {
397+
398+
CLI::callback_t fun = [&member, options](CLI::results_t res) {
399+
if(res.size() != 1) {
400+
return false;
401+
}
402+
bool retval = detail::lexical_cast(res[0], member);
403+
if(!retval)
404+
return false;
405+
return std::find(std::begin(options), std::end(options), member) != std::end(options);
406+
};
407+
344408
Option *opt = add_option(name, fun, description, defaulted);
345409
std::string typeval = detail::type_name<T>();
346410
typeval += " in {" + detail::join(options) + "}";
@@ -353,12 +417,11 @@ class App {
353417
return opt;
354418
}
355419

356-
/// Add set of options, string only, ignore case
420+
/// Add set of options, string only, ignore case (no default)
357421
Option *add_set_ignore_case(std::string name,
358422
std::string &member, ///< The selected member of the set
359423
std::set<std::string> options, ///< The set of posibilities
360-
std::string description = "",
361-
bool defaulted = false) {
424+
std::string description = "") {
362425

363426
CLI::callback_t fun = [&member, options](CLI::results_t res) {
364427
if(res.size() != 1) {
@@ -376,6 +439,37 @@ class App {
376439
}
377440
};
378441

442+
Option *opt = add_option(name, fun, description, false);
443+
std::string typeval = detail::type_name<std::string>();
444+
typeval += " in {" + detail::join(options) + "}";
445+
opt->set_custom_option(typeval);
446+
447+
return opt;
448+
}
449+
450+
/// Add set of options, string only, ignore case
451+
Option *add_set_ignore_case(std::string name,
452+
std::string &member, ///< The selected member of the set
453+
std::set<std::string> options, ///< The set of posibilities
454+
std::string description,
455+
bool defaulted) {
456+
457+
CLI::callback_t fun = [&member, options](CLI::results_t res) {
458+
if(res.size() != 1) {
459+
return false;
460+
}
461+
member = detail::to_lower(res[0]);
462+
auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
463+
return detail::to_lower(val) == member;
464+
});
465+
if(iter == std::end(options))
466+
return false;
467+
else {
468+
member = *iter;
469+
return true;
470+
}
471+
};
472+
379473
Option *opt = add_option(name, fun, description, defaulted);
380474
std::string typeval = detail::type_name<std::string>();
381475
typeval += " in {" + detail::join(options) + "}";

include/CLI/TypeTools.hpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ constexpr const char *type_name() {
7474

7575
// Lexical cast
7676

77-
/// Integers
78-
template <typename T, enable_if_t<std::is_integral<T>::value, detail::enabler> = detail::dummy>
77+
/// Integers / enums
78+
template <typename T, enable_if_t<std::is_integral<T>::value
79+
|| std::is_enum<T>::value
80+
, detail::enabler> = detail::dummy>
7981
bool lexical_cast(std::string input, T &output) {
8082
try {
8183
output = static_cast<T>(std::stoll(input));
@@ -103,7 +105,9 @@ bool lexical_cast(std::string input, T &output) {
103105
/// String and similar
104106
template <
105107
typename T,
106-
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value, detail::enabler> = detail::dummy>
108+
enable_if_t<!std::is_floating_point<T>::value
109+
&& !std::is_integral<T>::value
110+
&& !std::is_enum<T>::value, detail::enabler> = detail::dummy>
107111
bool lexical_cast(std::string input, T &output) {
108112
output = input;
109113
return true;

tests/AppTest.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,35 @@ TEST_F(TApp, DefaultOpts) {
203203
EXPECT_EQ("9", s);
204204
}
205205

206+
TEST_F(TApp, EnumTest) {
207+
enum Level : std::int32_t {
208+
High,
209+
Medium,
210+
Low
211+
};
212+
Level level = Level::Low;
213+
app.add_option("--level", level);
214+
215+
args = {"--level", "1"};
216+
run();
217+
EXPECT_EQ(level, Level::Medium);
218+
}
219+
220+
TEST_F(TApp, NewEnumTest) {
221+
enum class Level2 : std::int32_t {
222+
High,
223+
Medium,
224+
Low
225+
};
226+
Level2 level = Level2::Low;
227+
app.add_option("--level", level);
228+
229+
args = {"--level", "1"};
230+
run();
231+
EXPECT_EQ(level, Level2::Medium);
232+
}
233+
234+
206235
TEST_F(TApp, RequiredFlags) {
207236
app.add_flag("-a")->required();
208237
app.add_flag("-b")->mandatory(); // Alternate term
@@ -391,6 +420,47 @@ TEST_F(TApp, InSet) {
391420
EXPECT_THROW(run(), CLI::ConversionError);
392421
}
393422

423+
TEST_F(TApp, InSetWithDefault) {
424+
425+
std::string choice = "one";
426+
app.add_set("-q,--quick", choice, {"one", "two", "three"}, "", true);
427+
428+
run();
429+
EXPECT_EQ("one", choice);
430+
app.reset();
431+
432+
args = {"--quick", "two"};
433+
434+
run();
435+
EXPECT_EQ("two", choice);
436+
437+
app.reset();
438+
439+
args = {"--quick", "four"};
440+
EXPECT_THROW(run(), CLI::ConversionError);
441+
}
442+
443+
444+
TEST_F(TApp, InCaselessSetWithDefault) {
445+
446+
std::string choice = "one";
447+
app.add_set_ignore_case("-q,--quick", choice, {"one", "two", "three"}, "", true);
448+
449+
run();
450+
EXPECT_EQ("one", choice);
451+
app.reset();
452+
453+
args = {"--quick", "tWo"};
454+
455+
run();
456+
EXPECT_EQ("two", choice);
457+
458+
app.reset();
459+
460+
args = {"--quick", "four"};
461+
EXPECT_THROW(run(), CLI::ConversionError);
462+
}
463+
394464
TEST_F(TApp, InIntSet) {
395465

396466
int choice;
@@ -462,6 +532,20 @@ TEST_F(TApp, VectorFixedString) {
462532
EXPECT_EQ(answer, strvec);
463533
}
464534

535+
536+
TEST_F(TApp, VectorDefaultedFixedString) {
537+
std::vector<std::string> strvec{"one"};
538+
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
539+
540+
CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3);
541+
EXPECT_EQ(3, opt->get_expected());
542+
543+
args = {"--string", "mystring", "mystring2", "mystring3"};
544+
run();
545+
EXPECT_EQ((size_t)3, app.count("--string"));
546+
EXPECT_EQ(answer, strvec);
547+
}
548+
465549
TEST_F(TApp, VectorUnlimString) {
466550
std::vector<std::string> strvec;
467551
std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@@ -808,3 +892,43 @@ TEST_F(TApp, CheckSubcomFail) {
808892

809893
EXPECT_THROW(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError);
810894
}
895+
896+
// Added to test defaults on dual method
897+
TEST_F(TApp, OptionWithDefaults) {
898+
int someint=2;
899+
app.add_option("-a", someint, "", true);
900+
901+
args = {"-a1", "-a2"};
902+
903+
EXPECT_THROW(run(), CLI::ConversionError);
904+
}
905+
906+
// Added to test defaults on dual method
907+
TEST_F(TApp, SetWithDefaults) {
908+
int someint=2;
909+
app.add_set("-a", someint, {1,2,3,4}, "", true);
910+
911+
args = {"-a1", "-a2"};
912+
913+
EXPECT_THROW(run(), CLI::ConversionError);
914+
}
915+
916+
// Added to test defaults on dual method
917+
TEST_F(TApp, SetWithDefaultsConversion) {
918+
int someint=2;
919+
app.add_set("-a", someint, {1,2,3,4}, "", true);
920+
921+
args = {"-a", "hi"};
922+
923+
EXPECT_THROW(run(), CLI::ConversionError);
924+
}
925+
926+
// Added to test defaults on dual method
927+
TEST_F(TApp, SetWithDefaultsIC) {
928+
std::string someint="ho";
929+
app.add_set_ignore_case("-a", someint, {"Hi", "Ho"}, "", true);
930+
931+
args = {"-aHi", "-aHo"};
932+
933+
EXPECT_THROW(run(), CLI::ConversionError);
934+
}

0 commit comments

Comments
 (0)