Skip to content

Commit

Permalink
[Help] Sort options!
Browse files Browse the repository at this point in the history
  • Loading branch information
Sirraide committed Jul 28, 2024
1 parent 1520159 commit 102adcf
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 11 deletions.
67 changes: 62 additions & 5 deletions include/clopts.hh
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,11 @@ constexpr void While(bool& cond, auto&& lambda) {
(impl.template operator()<pack>() and ...);
}

// TODO: Use pack indexing once the syntax is fixed and compilers
// have actually started defining __cpp_pack_indexing.
template <std::size_t i, typename... pack>
using nth_type = std::tuple_element_t<i, std::tuple<pack...>>;

/// Check if an option is a regular option.
template <typename opt>
struct regular_option {
Expand Down Expand Up @@ -606,6 +611,52 @@ struct filter_impl<cond, list<processed...>> {
template <template <typename> typename cond, typename... types>
using filter = typename filter_impl<cond, list<>, types...>::type;

/// See that one talk (by Daisy Hollman, I think) about how this works.
template <template <typename> typename get_key, typename... types>
struct sort_impl {
private:
static constexpr auto sorter = []<std::size_t ...i>(std::index_sequence<i...>) {
static constexpr auto sorted = [] {
std::array indices{i...};
std::array lookup_table{get_key<types>::value...};
std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) {
return lookup_table[a] < lookup_table[b];
});
return indices;
}();
return list<nth_type<sorted[i], types...>...>{};
};

public:
using type = decltype(sorter(std::index_sequence_for<types...>()));
};

template <template <typename> typename get_key, typename... types>
struct sort_impl<get_key, list<types...>> {
using type = typename sort_impl<get_key, types...>::type;
};

// Special case because an array of size 0 is not going to work...
template <template <typename> typename get_key>
struct sort_impl<get_key, list<>> { using type = list<>; };

/// Sort a type list. The trick here is to sort the indices.
template <template <typename> typename get_key, typename type_list>
using sort = typename sort_impl<get_key, type_list>::type;

/// ===========================================================================
/// Sort/filter helpers.
/// ===========================================================================
template <typename opt>
struct get_option_name {
static constexpr std::string_view value = opt::name.sv();
};

template <typename opt>
struct is_values_option {
static constexpr bool value = opt::is_values;
};

/// ===========================================================================
/// Main implementation.
/// ===========================================================================
Expand Down Expand Up @@ -757,7 +808,7 @@ class clopts_impl<list<opts...>, list<special...>> {
// TODO: Use pack indexing once the syntax is fixed and compilers
// have actually started defining __cpp_pack_indexing.
template <static_string name>
using opt_by_name = std::tuple_element_t<optindex<name>(), std::tuple<opts...>>;
using opt_by_name = nth_type<optindex<name>(), opts...>;

// I hate not having pack indexing.
template <std::size_t i, static_string str, static_string... strs>
Expand Down Expand Up @@ -973,13 +1024,19 @@ private:

/// Create the help message.
static constexpr auto make_help_message() -> help_string_t { // clang-format off
using positional = filter<is_positional, opts...>;
using non_positional = filter<is_not_positional, opts...>;
using positional_unsorted = filter<is_positional, opts...>;
using positional = sort<get_option_name, positional_unsorted>;
using non_positional = sort<get_option_name, filter<is_not_positional, opts...>>;
using values_opts = sort<get_option_name, filter<is_values_option, opts...>>;
help_string_t msg{};

/// Append the positional options.
///
/// Do NOT sort them here as this is where we print in what order
/// they’re supposed to appear in, so sorting would be very stupid
/// here.
bool have_positional_opts = false;
positional::each([&]<typename opt> {
positional_unsorted::each([&]<typename opt> {
have_positional_opts = true;
if (not opt::is_required) msg.append("[");
msg.append("<");
Expand Down Expand Up @@ -1067,7 +1124,7 @@ private:
/// If we have any values<> types, print their supported values.
if constexpr ((opts::is_values or ...)) {
msg.append("\nSupported option values:\n");
Foreach<opts...>([&] <typename opt> {
values_opts::each([&] <typename opt> {
if constexpr (opt::is_values) {
msg.append(" ");
msg.append(opt::name.arr, opt::name.len);
Expand Down
12 changes: 6 additions & 6 deletions test/test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -845,26 +845,26 @@ TEST_CASE("Help message is formatted correctly") {
option<"--str", "Description of parameter --str", std::string>,
option<"--int", "Description of parameter --int", std::int64_t>,
flag<"--flag", "Description of parameter --flag">,
option<"--int-values", "Description of parameter --int-values", values<1, 2, 3, 4, 5>>,
option<"--str-values", "Description of parameter --str-values", values<"foo", "bar", "baz">>,
option<"--int-values", "Description of parameter --int-values", values<1, 2, 3, 4, 5>>,
overridable<"--ref", "Description of reference parameter", ref<double, "--int">>,
help<>
>;

static constexpr auto expected = R"help(<pos> [<int-pos>] [options]
Arguments:
<pos> Description of parameter pos
<int-pos> Description of parameter int-pos
<pos> Description of parameter pos
Options:
--str Description of parameter --str
--int Description of parameter --int
--flag Description of parameter --flag
--help Print this help information
--int Description of parameter --int
--int-values Description of parameter --int-values
--str-values Description of parameter --str-values
--ref Description of reference parameter
--help Print this help information
--str Description of parameter --str
--str-values Description of parameter --str-values
Supported option values:
--int-values: 1, 2, 3, 4, 5
Expand Down

0 comments on commit 102adcf

Please sign in to comment.