Skip to content

Commit e7e724d

Browse files
fogsong233claude
andauthored
feat(deco): add ability for enum trans and alias (#104)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fbc0422 commit e7e724d

File tree

19 files changed

+1880
-202
lines changed

19 files changed

+1880
-202
lines changed

include/eventide/deco/deco.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22
#include "./detail/backend.h"
3+
#include "./detail/config.h"
34
#include "./detail/decl.h"
45
#include "./detail/descriptor.h"
56
#include "./detail/macro.h"

include/eventide/deco/detail/backend.h

Lines changed: 340 additions & 14 deletions
Large diffs are not rendered by default.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <optional>
5+
#include <string>
6+
#include <utility>
7+
8+
namespace deco::config {
9+
10+
struct PositionStyle {
11+
bool enabled = true;
12+
bool show_label = true;
13+
bool show_source_line = true;
14+
std::size_t max_source_width = 96;
15+
char pointer = '^';
16+
char underline = '~';
17+
};
18+
19+
struct UsageStyle {
20+
bool group_by_category = true;
21+
std::size_t help_column = 32;
22+
std::string options_heading = "Options:";
23+
std::string group_prefix = "Group ";
24+
std::string exclusive_suffix = ", exclusive with other groups";
25+
std::string default_help = "no description provided";
26+
};
27+
28+
struct SubCommandStyle {
29+
bool show_overview = true;
30+
bool show_usage_line = true;
31+
bool show_description = true;
32+
bool align_description = true;
33+
bool show_command_alias = true;
34+
std::string heading = "Subcommands:";
35+
};
36+
37+
struct TextStyle {
38+
PositionStyle diagnostic{};
39+
UsageStyle usage{};
40+
SubCommandStyle subcommand{};
41+
};
42+
43+
struct CompatibleRendererConfig {
44+
PositionStyle diagnostic{};
45+
UsageStyle usage{};
46+
SubCommandStyle subcommand{};
47+
};
48+
49+
struct ModernRendererConfig {
50+
PositionStyle diagnostic{};
51+
UsageStyle usage{};
52+
SubCommandStyle subcommand{};
53+
54+
ModernRendererConfig() {
55+
usage.options_heading = "Options";
56+
subcommand.heading = "Commands";
57+
}
58+
};
59+
60+
struct BuiltInRenderConfig {
61+
CompatibleRendererConfig compatible{};
62+
ModernRendererConfig modern{};
63+
};
64+
65+
struct EnumMetaVarConfig {
66+
bool enabled = true;
67+
std::size_t max_items = 6;
68+
std::string separator = "|";
69+
std::string overflow_suffix = "|...";
70+
};
71+
72+
struct Config {
73+
EnumMetaVarConfig enum_meta_var{};
74+
BuiltInRenderConfig render{};
75+
};
76+
77+
struct ConfigOverride {
78+
std::optional<EnumMetaVarConfig> enum_meta_var;
79+
std::optional<BuiltInRenderConfig> render;
80+
};
81+
82+
inline auto merge(Config base, const ConfigOverride& override_config) -> Config {
83+
if(override_config.enum_meta_var.has_value()) {
84+
base.enum_meta_var = *override_config.enum_meta_var;
85+
}
86+
if(override_config.render.has_value()) {
87+
base.render = *override_config.render;
88+
}
89+
return base;
90+
}
91+
92+
auto get() -> const Config&;
93+
void set(Config config);
94+
void set_render(BuiltInRenderConfig render);
95+
void set_enum_meta_var(EnumMetaVarConfig config);
96+
97+
} // namespace deco::config

include/eventide/deco/detail/decl.h

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include <charconv>
66
#include <concepts>
77
#include <cstdlib>
8-
#include <format>
8+
#include <expected>
99
#include <memory>
1010
#include <optional>
1111
#include <span>
@@ -17,6 +17,7 @@
1717

1818
#include "text.h"
1919
#include "trait.h"
20+
#include "eventide/serde/serde/spelling.h"
2021

2122
namespace deco::decl {
2223

@@ -300,6 +301,69 @@ struct IntoContext {
300301
}
301302
};
302303

304+
using AliasForwardResult = std::expected<std::vector<std::string>, std::string>;
305+
using AliasForwardFn = AliasForwardResult (*)(const backend::ParsedArgumentOwning& arg);
306+
using AliasForwardFnWithContext = AliasForwardResult (*)(const backend::ParsedArgumentOwning& arg,
307+
const IntoContext& context);
308+
309+
struct AliasForwardField {
310+
enum class Kind : char {
311+
None = 0,
312+
Static = 1,
313+
Dynamic = 2,
314+
DynamicWithContext = 3,
315+
};
316+
317+
Kind kind = Kind::None;
318+
std::vector<std::string_view> static_tokens;
319+
AliasForwardFn dynamic = nullptr;
320+
AliasForwardFnWithContext dynamic_with_context = nullptr;
321+
322+
constexpr AliasForwardField() = default;
323+
324+
constexpr auto operator=(std::initializer_list<std::string_view> tokens) -> AliasForwardField& {
325+
kind = Kind::Static;
326+
static_tokens.assign(tokens.begin(), tokens.end());
327+
dynamic = nullptr;
328+
dynamic_with_context = nullptr;
329+
return *this;
330+
}
331+
332+
constexpr auto operator=(std::vector<std::string_view> tokens) -> AliasForwardField& {
333+
kind = Kind::Static;
334+
static_tokens = std::move(tokens);
335+
dynamic = nullptr;
336+
dynamic_with_context = nullptr;
337+
return *this;
338+
}
339+
340+
constexpr auto operator=(AliasForwardFn fn) -> AliasForwardField& {
341+
kind = fn ? Kind::Dynamic : Kind::None;
342+
static_tokens.clear();
343+
dynamic = fn;
344+
dynamic_with_context = nullptr;
345+
return *this;
346+
}
347+
348+
constexpr auto operator=(AliasForwardFnWithContext fn) -> AliasForwardField& {
349+
kind = fn ? Kind::DynamicWithContext : Kind::None;
350+
static_tokens.clear();
351+
dynamic = nullptr;
352+
dynamic_with_context = fn;
353+
return *this;
354+
}
355+
356+
constexpr explicit operator bool() const {
357+
return kind != Kind::None;
358+
}
359+
};
360+
361+
constexpr inline bool is_alias_placeholder_name(std::string_view member_name) {
362+
return member_name.starts_with("__deco_alias_wrapper") ||
363+
(!member_name.empty() &&
364+
std::all_of(member_name.begin(), member_name.end(), [](char ch) { return ch == '_'; }));
365+
}
366+
303367
struct DecoFields {
304368
// if true, it's required if its category occurs in options.
305369
bool required = true;
@@ -309,9 +373,37 @@ struct DecoFields {
309373
constexpr DecoFields() = default;
310374
};
311375

376+
struct MetaVarField {
377+
std::string_view value = "<value>";
378+
bool explicit_value = false;
379+
380+
constexpr MetaVarField() = default;
381+
382+
constexpr MetaVarField(std::string_view value, bool explicit_value = false) :
383+
value(value), explicit_value(explicit_value) {}
384+
385+
constexpr auto operator=(std::string_view new_value) -> MetaVarField& {
386+
value = new_value;
387+
explicit_value = true;
388+
return *this;
389+
}
390+
391+
constexpr auto empty() const -> bool {
392+
return value.empty();
393+
}
394+
395+
constexpr auto is_explicit() const -> bool {
396+
return explicit_value;
397+
}
398+
399+
constexpr operator std::string_view() const {
400+
return value;
401+
}
402+
};
403+
312404
struct CommonOptionFields : DecoFields {
313405
std::string_view help = "not provided";
314-
std::string_view meta_var = "<value>";
406+
MetaVarField meta_var{};
315407

316408
constexpr CommonOptionFields() = default;
317409
};
@@ -371,7 +463,7 @@ struct ConfigFields {
371463
ConfigOverrideField<bool> required = true;
372464
ConfigOverrideField<CategoryRef> category = default_category;
373465
ConfigOverrideField<std::string_view> help = "not provided";
374-
ConfigOverrideField<std::string_view> meta_var = "<value>";
466+
ConfigOverrideField<MetaVarField> meta_var = MetaVarField{"<value>", false};
375467

376468
enum class Type : char {
377469
Start = 0,
@@ -420,6 +512,33 @@ struct MultiFields : NamedOptionFields {
420512
constexpr MultiFields() = default;
421513
};
422514

515+
struct AliasFields : NamedOptionFields {
516+
AliasForwardField forward;
517+
constexpr AliasFields() = default;
518+
};
519+
520+
struct FlagAliasFields : AliasFields {
521+
constexpr static DecoType deco_field_ty = DecoType::Flag;
522+
constexpr FlagAliasFields() = default;
523+
};
524+
525+
struct KVAliasFields : AliasFields {
526+
constexpr static DecoType deco_field_ty = DecoType::KV;
527+
char style = KVStyle::Separate;
528+
constexpr KVAliasFields() = default;
529+
};
530+
531+
struct CommaJoinedAliasFields : AliasFields {
532+
constexpr static DecoType deco_field_ty = DecoType::CommaJoined;
533+
constexpr CommaJoinedAliasFields() = default;
534+
};
535+
536+
struct MultiAliasFields : AliasFields {
537+
constexpr static DecoType deco_field_ty = DecoType::Multi;
538+
unsigned arg_num = 1;
539+
constexpr MultiAliasFields() = default;
540+
};
541+
423542
struct DecoOptionBase {
424543
constexpr virtual ~DecoOptionBase() = default;
425544
// return error message if parsing fails, otherwise return std::nullopt
@@ -525,6 +644,27 @@ inline bool iequals_ascii(std::string_view lhs, std::string_view rhs) {
525644
return true;
526645
}
527646

647+
template <typename EnumTy>
648+
std::string format_invalid_enum_value(std::string_view text) {
649+
std::string message = "invalid enum value: ";
650+
message += text;
651+
652+
const auto& values = eventide::serde::spelling::enum_strings<EnumTy>();
653+
if(values.empty()) {
654+
return message;
655+
}
656+
657+
message += " (supported: ";
658+
for(std::size_t i = 0; i < values.size(); ++i) {
659+
if(i != 0) {
660+
message += ", ";
661+
}
662+
message += values[i];
663+
}
664+
message += ")";
665+
return message;
666+
}
667+
528668
template <typename ResTy>
529669
std::optional<std::string> parse_primitive_scalar(ResTy& out, std::string_view text) {
530670
if constexpr(std::same_as<ResTy, bool>) {
@@ -551,6 +691,12 @@ std::optional<std::string> parse_primitive_scalar(ResTy& out, std::string_view t
551691
return std::nullopt;
552692
} else if constexpr(std::same_as<ResTy, long double>) {
553693
return "unsupported floating-point type: long double";
694+
} else if constexpr(std::is_enum_v<ResTy>) {
695+
if(auto parsed = eventide::serde::spelling::map_string_to_enum<ResTy>(text)) {
696+
out = *parsed;
697+
return std::nullopt;
698+
}
699+
return format_invalid_enum_value<ResTy>(text);
554700
} else if constexpr(std::floating_point<ResTy>) {
555701
std::string copy(text);
556702
char* parse_end = nullptr;

0 commit comments

Comments
 (0)