Skip to content

Commit 3906df6

Browse files
committed
wip
1 parent c09f57f commit 3906df6

File tree

5 files changed

+399
-2
lines changed

5 files changed

+399
-2
lines changed

src/clis/ls/main.cpp

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1+
#include <karm-cli/args.h>
12
#include <karm-sys/dir.h>
23
#include <karm-sys/entry.h>
34

4-
Async::Task<> entryPointAsync(Sys::Context &) {
5+
Async::Task<> entryPointAsync(Sys::Context &ctx) {
6+
auto allFlag = Cli::flag('a', "all"s, "Do not ignore entries starting with ."s);
7+
8+
Cli::Command cmd{
9+
"ls"s,
10+
NONE,
11+
"List directory contents."s,
12+
{allFlag}
13+
};
14+
15+
co_trya$(cmd.execAsync(ctx));
16+
17+
if (not cmd)
18+
co_return Ok();
19+
520
auto url = co_try$(Mime::parseUrlOrPath("."));
621
auto dir = co_try$(Sys::Dir::open(url));
7-
for (auto const &entry : dir.entries())
22+
23+
for (auto const &entry : dir.entries()) {
24+
if (!allFlag && entry.name[0] == '.')
25+
continue;
826
Sys::println(entry.name);
27+
}
28+
929
co_return Ok();
1030
}

src/libs/karm-cli/args.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include "args.h"
2+
3+
namespace Karm::Cli {
4+
5+
void tokenize(Str arg, Vec<Token> &out) {
6+
if (arg == "--"s) {
7+
out.pushBack(Token::EXTRA);
8+
} else if (arg == "-"s) {
9+
out.pushBack("-"s);
10+
} else if (startWith(arg, "--"s) == Match::PARTIAL) {
11+
out.emplaceBack(Token::OPTION, next(arg, 2));
12+
} else if (startWith(arg, "-"s) == Match::PARTIAL) {
13+
Str flags = next(arg, 1);
14+
for (auto r : iterRunes(flags))
15+
out.emplaceBack(r);
16+
} else {
17+
out.pushBack(arg);
18+
}
19+
}
20+
21+
void tokenize(Slice<Str> args, Vec<Token> &out) {
22+
for (auto &arg : args)
23+
tokenize(arg, out);
24+
}
25+
26+
void tokenize(int argc, char **argv, Vec<Token> &out) {
27+
for (int i = 0; i < argc; ++i)
28+
tokenize(Str::fromNullterminated(argv[i]), out);
29+
}
30+
31+
} // namespace Karm::Cli

src/libs/karm-cli/args.h

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
#pragma once
2+
3+
#include <karm-base/func.h>
4+
#include <karm-base/opt.h>
5+
#include <karm-base/rc.h>
6+
#include <karm-base/string.h>
7+
#include <karm-base/vec.h>
8+
#include <karm-sys/context.h>
9+
10+
namespace Karm::Cli {
11+
12+
struct Token {
13+
enum struct Kind {
14+
OPERAND,
15+
OPTION,
16+
FLAG,
17+
EXTRA,
18+
19+
_LEN,
20+
};
21+
22+
using enum Kind;
23+
24+
Kind kind;
25+
Rune flag = 0;
26+
Str value;
27+
28+
Token(Str value)
29+
: Token(Kind::OPERAND, value) {}
30+
31+
Token(Kind kind, Str value)
32+
: kind(kind), value(value) {}
33+
34+
Token(Kind kind)
35+
: kind(kind), value("") {}
36+
37+
Token(Rune flag)
38+
: kind(Kind::FLAG), flag(flag), value("") {}
39+
40+
bool operator==(Token const &other) const = default;
41+
42+
void repr(Io::Emit &e) const {
43+
if (kind == Kind::FLAG)
44+
e("(token {} {#c})", kind, flag);
45+
else
46+
e("(token {} {#})", kind, value);
47+
}
48+
};
49+
50+
void tokenize(Str arg, Vec<Token> &out);
51+
52+
void tokenize(Slice<Str> args, Vec<Token> &out);
53+
54+
void tokenize(int argc, char **argv, Vec<Token> &out);
55+
56+
enum struct OptionKind {
57+
OPTION,
58+
OPERAND,
59+
EXTRA,
60+
};
61+
62+
template <typename T>
63+
struct ValueParser;
64+
65+
template <>
66+
struct ValueParser<bool> {
67+
static Res<bool> parse(Cursor<Token> &) {
68+
return Ok(true);
69+
}
70+
};
71+
72+
struct _OptionInfo {
73+
OptionKind kind;
74+
Opt<Rune> shortName;
75+
String longName;
76+
String description;
77+
78+
virtual ~_OptionInfo() = default;
79+
80+
virtual void eval(Cursor<Token> &c) = 0;
81+
};
82+
83+
template <typename T>
84+
struct OptionInfo : public _OptionInfo {
85+
Opt<T> defaultValue;
86+
Opt<T> value;
87+
88+
void eval(Cursor<Token> &c) override {
89+
co_try$(value = ValueParser<T>::parse(c));
90+
}
91+
};
92+
93+
template <typename T>
94+
struct Field {
95+
Strong<OptionInfo<T>> infos;
96+
97+
Field(Strong<OptionInfo<T>> store) : infos(std::move(store)) {}
98+
99+
operator T() const {
100+
return infos->value.unwrapOr(infos->defaultValue.unwrapOr(T{}));
101+
}
102+
103+
operator Strong<_OptionInfo>() {
104+
return infos;
105+
}
106+
};
107+
108+
using Flag = Field<bool>;
109+
110+
static inline Flag flag(Opt<Rune> shortName, String longName, String description) {
111+
return makeStrong<OptionInfo<bool>>(OptionKind::OPTION, shortName, longName, description, false);
112+
}
113+
114+
template <typename T>
115+
static inline Field<T> option(Opt<Rune> shortName, String longName, String description, Opt<T> defaultValue = NONE) {
116+
return makeStrong<OptionInfo<T>>(OptionKind::OPTION, shortName, longName, description, defaultValue);
117+
}
118+
119+
template <typename T>
120+
static inline Field<T> operand(String longName, String description, T defaultValue = {}) {
121+
return makeStrong<OptionInfo<T>>(OptionKind::OPERAND, NONE, longName, description, defaultValue);
122+
}
123+
124+
static inline Field<Vec<Str>> extra(String description) {
125+
return makeStrong<OptionInfo<Vec<Str>>>(OptionKind::EXTRA, NONE, ""s, description, Vec<Str>{});
126+
}
127+
128+
struct Command {
129+
using Callback = Func<Async::Task<>(Sys::Context &)>;
130+
131+
struct Props {
132+
};
133+
134+
String longName;
135+
Opt<Rune> shortName;
136+
String description = ""s;
137+
Vec<Strong<_OptionInfo>> options;
138+
Opt<Callback> callbackAsync;
139+
140+
Vec<Strong<Command>> _commands;
141+
142+
Field<bool> _help = flag('h', "help"s, "Show this help message and exit."s);
143+
Field<bool> _usage = flag('u', "usage"s, "Show usage message and exit."s);
144+
145+
bool _invoked = false;
146+
147+
Command(
148+
String longName,
149+
Opt<Rune> shortName = NONE,
150+
String description = ""s,
151+
Vec<Strong<_OptionInfo>> options = {},
152+
Opt<Callback> callbackAsync = NONE
153+
)
154+
: longName(std::move(longName)),
155+
shortName(shortName),
156+
description(std::move(description)),
157+
options(std::move(options)),
158+
callbackAsync(std::move(callbackAsync)) {}
159+
160+
Command &subCommand(
161+
String longName,
162+
Opt<Rune> shortName = NONE,
163+
String description = ""s,
164+
Vec<Strong<_OptionInfo>> options = {},
165+
Opt<Callback> callbackAsync = NONE
166+
) {
167+
auto cmd = makeStrong<Command>(longName, shortName, description, options, std::move(callbackAsync));
168+
_commands.pushBack(cmd);
169+
return *cmd;
170+
}
171+
172+
template <typename T>
173+
Field<T> option(
174+
Opt<Rune> shortName,
175+
String longName,
176+
String description,
177+
Opt<T> defaultValue = NONE
178+
) {
179+
auto store = makeStrong<OptionInfo<T>>();
180+
store->kind = OptionKind::OPTION;
181+
store->shortName = shortName;
182+
store->longName = longName;
183+
store->description = description;
184+
store->defaultValue = defaultValue;
185+
options.pushBack(store);
186+
return {store};
187+
}
188+
189+
template <typename T>
190+
void option(Field<T> field) {
191+
options.pushBack(field.infos);
192+
}
193+
194+
Res<> _showHelp() {
195+
return Ok();
196+
}
197+
198+
Res<> _showUsage() {
199+
return Ok();
200+
}
201+
202+
Res<> _evalParams(Cursor<Token> &c) {
203+
while (not c.ended()) {
204+
c.next();
205+
}
206+
207+
return Ok();
208+
}
209+
210+
Async::Task<> execAsync(Sys::Context &ctx) {
211+
auto args = Sys::useArgs(ctx);
212+
Vec<Token> tokens;
213+
for (usize i = 0; i < args.len(); ++i)
214+
tokenize(args[i], tokens);
215+
return execAsync(ctx, tokens);
216+
}
217+
218+
Async::Task<> execAsync(Sys::Context &ctx, Slice<Str> args) {
219+
Vec<Token> tokens;
220+
tokenize(args, tokens);
221+
return execAsync(ctx, tokens);
222+
}
223+
224+
Async::Task<> execAsync(Sys::Context &ctx, Slice<Token> tokens) {
225+
Cursor<Token> c = tokens;
226+
co_try$(_evalParams(c));
227+
228+
if (_help) {
229+
co_try$(_showHelp());
230+
co_return Ok();
231+
}
232+
233+
if (_usage) {
234+
co_try$(_showUsage());
235+
co_return Ok();
236+
}
237+
238+
_invoked = true;
239+
if (callbackAsync)
240+
co_trya$(callbackAsync.unwrap()(ctx));
241+
242+
for (auto &cmd : _commands)
243+
co_trya$(cmd->execAsync(ctx));
244+
245+
co_return Ok();
246+
}
247+
248+
operator bool() const {
249+
return _invoked;
250+
}
251+
};
252+
253+
} // namespace Karm::Cli

src/libs/karm-cli/tests/manifest.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1",
3+
"id": "karm-cli.tests",
4+
"type": "lib",
5+
"props": {
6+
"cpp-excluded": true
7+
},
8+
"requires": [
9+
"karm-cli",
10+
"karm-test"
11+
],
12+
"injects": [
13+
"__tests__"
14+
]
15+
}

0 commit comments

Comments
 (0)