Skip to content

Commit

Permalink
cmdline: Add a command-line parser and tests for it
Browse files Browse the repository at this point in the history
This parser accepts a command-line in a format similar to Linux's:
"foo bar=baz qux=12".
  • Loading branch information
qookei committed Apr 26, 2023
1 parent a3692f2 commit 22f5a2d
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
126 changes: 126 additions & 0 deletions include/frg/cmdline.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#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) {
if (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
};
}

inline auto store_true(bool &here) {
return option::fn_type{
[] (frg::string_view value, void *ctx) {
*static_cast<bool *>(ctx) = true;
},
&here,
false
};
}

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 split_on = spc;
if (spc == size_t(-1))
split_on = cmdline.size();

auto arg = cmdline.sub_string(0, 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
41 changes: 41 additions & 0 deletions tests/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,44 @@ TEST(bitset, count) {
EXPECT_FALSE(a.all());
EXPECT_FALSE(a.none());
}

#include <frg/array.hpp>
#include <frg/cmdline.hpp>

TEST(cmdline, basic_cmdline) {
bool foo = false, bar = false;
frg::string_view v1{};
uint32_t v2 = 0;

frg::array args = {
frg::option{"foo", frg::store_true(foo)},
frg::option{"bar", frg::store_true(bar)},
frg::option{"baz", frg::as_string_view(v1)},
frg::option{"qux", frg::as_number(v2)},
};

frg::parse_arguments("foo baz=yoo qux=1234", args);

ASSERT_TRUE(foo);
ASSERT_FALSE(bar);
ASSERT_EQ(v1, "yoo");
ASSERT_EQ(v2, 1234);
}

TEST(cmdline, multiple_option_spans) {
bool nosmp = false;
frg::string_view init_exec{};

frg::array cpu_args = {
frg::option{"x86.nosmp", frg::store_true(nosmp)}
};

frg::array init_args = {
frg::option{"init.exec", frg::as_string_view(init_exec)}
};

frg::parse_arguments("x86.nosmp init.exec=/sbin/posix-subsystem", init_args, cpu_args);

ASSERT_TRUE(nosmp);
ASSERT_EQ(init_exec, "/sbin/posix-subsystem");
}

0 comments on commit 22f5a2d

Please sign in to comment.