-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmdline: Add a command-line parser and tests for it
This parser accepts a command-line in a format similar to Linux's: "foo bar=baz qux=12".
- Loading branch information
Showing
3 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
#ifndef FRG_CMDLINE_HPP | ||
#define FRG_CMDLINE_HPP | ||
|
||
#include <frg/macros.hpp> | ||
#include <frg/string.hpp> | ||
#include <frg/tuple.hpp> | ||
|
||
#include <concepts> | ||
|
||
namespace frg FRG_VISIBILITY { | ||
|
||
struct option { | ||
struct fn_type { | ||
void (*ptr)(frg::string_view, void *); | ||
void *ctx; | ||
bool has_arg; | ||
}; | ||
|
||
frg::string_view opt; | ||
fn_type fn; | ||
|
||
void apply(frg::string_view value) { | ||
FRG_ASSERT(fn.ptr); | ||
fn.ptr(value, fn.ctx); | ||
} | ||
}; | ||
|
||
template <typename T> | ||
auto as_number(T &here) { | ||
return option::fn_type{ | ||
[] (frg::string_view value, void *ctx) { | ||
auto n = value.to_number<T>(); | ||
if (n) | ||
*static_cast<T *>(ctx) = n.value(); | ||
}, | ||
&here, | ||
true | ||
}; | ||
} | ||
|
||
inline auto as_string_view(frg::string_view &here) { | ||
return option::fn_type{ | ||
[] (frg::string_view value, void *ctx) { | ||
*static_cast<frg::string_view *>(ctx) = value; | ||
}, | ||
&here, | ||
true | ||
}; | ||
} | ||
|
||
template<typename T, T value> | ||
auto store_const(bool &here) { | ||
return option::fn_type{ | ||
[] (frg::string_view, void *ctx) { | ||
*static_cast<T *>(ctx) = value; | ||
}, | ||
&here, | ||
false | ||
}; | ||
} | ||
|
||
inline auto store_true(bool &here) { | ||
return store_const<bool, true>(here); | ||
} | ||
|
||
inline auto store_false(bool &here) { | ||
return store_const<bool, false>(here); | ||
} | ||
|
||
template <typename T> | ||
concept option_span = requires (T t) { | ||
{ t.data() } -> std::convertible_to<option *>; | ||
{ t.size() } -> std::convertible_to<size_t>; | ||
}; | ||
|
||
|
||
template <option_span ...Ts> | ||
inline void parse_arguments(frg::string_view cmdline, Ts &&...args) { | ||
auto try_apply_arg = [&] (frg::string_view arg, option opt) -> bool { | ||
auto eq = arg.find_first('='); | ||
|
||
if (eq == size_t(-1)) { | ||
if (opt.fn.has_arg) | ||
return false; | ||
|
||
if (opt.opt != arg) | ||
return false; | ||
|
||
opt.apply({}); | ||
} else { | ||
if (!opt.fn.has_arg) | ||
return false; | ||
|
||
auto name = arg.sub_string(0, eq); | ||
auto val = arg.sub_string(eq + 1, arg.size() - eq -1); | ||
|
||
if (opt.opt != name) | ||
return false; | ||
|
||
opt.apply(val); | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
auto try_arg_span = [&] (frg::string_view arg, auto span) -> bool { | ||
for (size_t i = 0; i < span.size(); i++) { | ||
if (try_apply_arg(arg, span.data()[i])) | ||
return true; | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
while (true) { | ||
size_t spc = cmdline.find_first(' '); | ||
size_t opening_quote = cmdline.find_first('\"'); | ||
size_t closing_quote = size_t(-1); | ||
bool quoted = false; | ||
|
||
if(opening_quote < spc) { | ||
quoted = true; | ||
|
||
closing_quote = opening_quote + 1 + cmdline.sub_string(opening_quote + 1, cmdline.size() - opening_quote - 1).find_first('\"'); | ||
spc = closing_quote + 1 + cmdline.sub_string(closing_quote + 1, cmdline.size() - closing_quote - 1).find_first(' '); | ||
} | ||
|
||
size_t split_on = spc; | ||
if (spc == size_t(-1)) | ||
split_on = cmdline.size(); | ||
|
||
auto arg = cmdline.sub_string(quoted ? (opening_quote + 1) : 0, quoted ? (closing_quote - opening_quote - 1) : split_on); | ||
|
||
(try_arg_span(arg, args) || ...); | ||
|
||
if (spc != size_t(-1)) { | ||
cmdline = cmdline.sub_string(spc + 1, cmdline.size() - spc - 1); | ||
} else { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
} // namespace frg | ||
|
||
#endif // FRG_CMDLINE_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters