mt-lang 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/index.html +4 -3
- data/lib/milk_tea/base.rb +1 -1
- data/std/asset_pack.mt +267 -0
- data/std/async/libuv_runtime.mt +539 -0
- data/std/async/mailbox.mt +191 -0
- data/std/async/runtime.mt +87 -0
- data/std/async.mt +87 -0
- data/std/base64.mt +185 -0
- data/std/behavior_tree.mt +396 -0
- data/std/binary.mt +314 -0
- data/std/binary_heap.mt +157 -0
- data/std/bitset.mt +239 -0
- data/std/box2d.mt +535 -0
- data/std/bytes.mt +46 -0
- data/std/c/box2d.mt +1254 -0
- data/std/c/cgltf.mt +647 -0
- data/std/c/cjson.mt +106 -0
- data/std/c/crypto.mt +11 -0
- data/std/c/crypto_support.h +51 -0
- data/std/c/ctype.mt +19 -0
- data/std/c/ctype_bindgen.h +29 -0
- data/std/c/curl.mt +1223 -0
- data/std/c/enet.mt +473 -0
- data/std/c/errno.mt +41 -0
- data/std/c/errno_bindgen.h +53 -0
- data/std/c/flecs.mt +2945 -0
- data/std/c/fs.linux.mt +43 -0
- data/std/c/fs.windows.mt +43 -0
- data/std/c/fs_support.h +954 -0
- data/std/c/gl.mt +2065 -0
- data/std/c/gl_registry_helpers.h +10748 -0
- data/std/c/glfw.mt +499 -0
- data/std/c/libc.mt +167 -0
- data/std/c/libuv.mt +1491 -0
- data/std/c/math.mt +22 -0
- data/std/c/math_bindgen.h +31 -0
- data/std/c/miniaudio.mt +3420 -0
- data/std/c/pcre2.mt +735 -0
- data/std/c/process.mt +58 -0
- data/std/c/process_support.h +1147 -0
- data/std/c/raygui.mt +1602 -0
- data/std/c/raylib.mt +1245 -0
- data/std/c/raymath.mt +163 -0
- data/std/c/rlgl.mt +434 -0
- data/std/c/rpng.mt +60 -0
- data/std/c/rres.mt +187 -0
- data/std/c/sdl3.mt +4132 -0
- data/std/c/sqlite3.mt +951 -0
- data/std/c/stb_image.mt +52 -0
- data/std/c/stb_image_resize2.mt +128 -0
- data/std/c/stb_image_write.mt +19 -0
- data/std/c/stb_rect_pack.mt +38 -0
- data/std/c/stb_truetype.mt +175 -0
- data/std/c/stb_vorbis.mt +31 -0
- data/std/c/stdio.mt +90 -0
- data/std/c/steamworks.h +5769 -0
- data/std/c/steamworks.mt +4771 -0
- data/std/c/string.mt +16 -0
- data/std/c/string_bindgen.h +36 -0
- data/std/c/sync.mt +33 -0
- data/std/c/sync_support.h +219 -0
- data/std/c/terminal.mt +24 -0
- data/std/c/terminal_support.h +408 -0
- data/std/c/time.mt +26 -0
- data/std/c/tls.mt +24 -0
- data/std/c/tls_support.h +626 -0
- data/std/c/tracy.mt +53 -0
- data/std/c/zlib.mt +20 -0
- data/std/c/zlib_support.h +239 -0
- data/std/c/zstd.mt +231 -0
- data/std/cell.mt +60 -0
- data/std/cgltf.mt +115 -0
- data/std/cjson.mt +90 -0
- data/std/cli.mt +768 -0
- data/std/cookie.mt +198 -0
- data/std/counter.mt +160 -0
- data/std/crypto.mt +74 -0
- data/std/cstring.mt +13 -0
- data/std/ctype.mt +52 -0
- data/std/curl/runtime.mt +261 -0
- data/std/curl.mt +38 -0
- data/std/deque.mt +348 -0
- data/std/encoding.mt +93 -0
- data/std/enet.mt +133 -0
- data/std/env.mt +41 -0
- data/std/errno.mt +47 -0
- data/std/flecs.mt +978 -0
- data/std/fmt.mt +271 -0
- data/std/fs.linux.mt +696 -0
- data/std/fs.windows.mt +711 -0
- data/std/fsm.mt +267 -0
- data/std/gl.mt +2062 -0
- data/std/glfw.mt +471 -0
- data/std/goap.mt +342 -0
- data/std/graph.mt +521 -0
- data/std/gzip.mt +77 -0
- data/std/hash.mt +429 -0
- data/std/http/server.mt +810 -0
- data/std/http.mt +1227 -0
- data/std/intern.mt +53 -0
- data/std/jobs.mt +288 -0
- data/std/json.mt +627 -0
- data/std/libc.mt +34 -0
- data/std/libuv.mt +554 -0
- data/std/linear_algebra.mt +243 -0
- data/std/linked_map.mt +372 -0
- data/std/linked_map_view.mt +49 -0
- data/std/linked_set.mt +117 -0
- data/std/log.mt +69 -0
- data/std/map.mt +418 -0
- data/std/math.mt +24 -0
- data/std/mem/arena.mt +137 -0
- data/std/mem/endian.mt +51 -0
- data/std/mem/heap.mt +298 -0
- data/std/mem/pool.mt +164 -0
- data/std/mem/stack.mt +47 -0
- data/std/mem/tracking.mt +119 -0
- data/std/miniaudio.mt +1299 -0
- data/std/multiset.mt +191 -0
- data/std/net/channel.mt +727 -0
- data/std/net/clock.mt +237 -0
- data/std/net/discovery.mt +277 -0
- data/std/net/lobby.mt +813 -0
- data/std/net/manager.mt +539 -0
- data/std/net/mux.mt +839 -0
- data/std/net/nat.mt +122 -0
- data/std/net/packet.mt +219 -0
- data/std/net/punch.mt +162 -0
- data/std/net/rpc.mt +119 -0
- data/std/net/session.mt +1157 -0
- data/std/net/stun.mt +252 -0
- data/std/net/sync.mt +183 -0
- data/std/net/turn.mt +459 -0
- data/std/net.mt +2944 -0
- data/std/oauth2.mt +408 -0
- data/std/option.mt +51 -0
- data/std/ordered_map.mt +497 -0
- data/std/ordered_set.mt +348 -0
- data/std/path.mt +368 -0
- data/std/pcre2/runtime.mt +35 -0
- data/std/pcre2.mt +78 -0
- data/std/priority_queue.mt +55 -0
- data/std/process.mt +837 -0
- data/std/queue.mt +55 -0
- data/std/random.mt +146 -0
- data/std/raygui.mt +99 -0
- data/std/raylib/debug_console.mt +71 -0
- data/std/raylib/easing.mt +388 -0
- data/std/raylib/packed_assets.mt +255 -0
- data/std/raylib/runtime.mt +30 -0
- data/std/raylib/tracy_gpu.mt +37 -0
- data/std/raylib.mt +1510 -0
- data/std/raymath.mt +153 -0
- data/std/result.mt +89 -0
- data/std/rlgl.mt +268 -0
- data/std/rpng.mt +46 -0
- data/std/rres.mt +36 -0
- data/std/sdl3/runtime.mt +54 -0
- data/std/sdl3.mt +1731 -0
- data/std/serialize.mt +68 -0
- data/std/set.mt +124 -0
- data/std/spatial.mt +176 -0
- data/std/sqlite3.mt +151 -0
- data/std/stack.mt +55 -0
- data/std/stb_image.mt +47 -0
- data/std/stb_image_resize2.mt +41 -0
- data/std/stb_image_write.mt +17 -0
- data/std/stb_rect_pack.mt +15 -0
- data/std/stb_truetype.mt +77 -0
- data/std/stb_vorbis.mt +16 -0
- data/std/stdio.mt +88 -0
- data/std/steamworks.mt +1542 -0
- data/std/str.mt +293 -0
- data/std/string.mt +234 -0
- data/std/sync.mt +194 -0
- data/std/tar.mt +704 -0
- data/std/terminal.mt +1002 -0
- data/std/testing.mt +266 -0
- data/std/thread.mt +120 -0
- data/std/time.mt +105 -0
- data/std/tls.mt +616 -0
- data/std/toml.mt +1310 -0
- data/std/tracy.mt +42 -0
- data/std/uri.mt +118 -0
- data/std/url.mt +372 -0
- data/std/vec.mt +433 -0
- data/std/zstd.mt +94 -0
- metadata +187 -2
data/std/cli.mt
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
import std.fmt as fmt
|
|
2
|
+
import std.str as text
|
|
3
|
+
import std.string as string
|
|
4
|
+
import std.vec as vec
|
|
5
|
+
|
|
6
|
+
const byte_equals: ubyte = 61ub
|
|
7
|
+
|
|
8
|
+
public enum OptionKind: int
|
|
9
|
+
flag = 0
|
|
10
|
+
value = 1
|
|
11
|
+
|
|
12
|
+
public struct OptionSpec:
|
|
13
|
+
long_name: str
|
|
14
|
+
short_name: Option[str]
|
|
15
|
+
kind: OptionKind
|
|
16
|
+
help: str
|
|
17
|
+
value_name: Option[str]
|
|
18
|
+
default_value: Option[str]
|
|
19
|
+
required: bool
|
|
20
|
+
|
|
21
|
+
public struct CommandSpec:
|
|
22
|
+
name: str
|
|
23
|
+
summary: str
|
|
24
|
+
help: str
|
|
25
|
+
options: span[OptionSpec]
|
|
26
|
+
positional_help: Option[str]
|
|
27
|
+
|
|
28
|
+
public struct AppSpec:
|
|
29
|
+
name: str
|
|
30
|
+
summary: str
|
|
31
|
+
options: span[OptionSpec]
|
|
32
|
+
commands: span[CommandSpec]
|
|
33
|
+
positional_help: Option[str]
|
|
34
|
+
|
|
35
|
+
public struct OptionMatch:
|
|
36
|
+
name: string.String
|
|
37
|
+
present: bool
|
|
38
|
+
value: string.String
|
|
39
|
+
has_value: bool
|
|
40
|
+
|
|
41
|
+
public struct Match:
|
|
42
|
+
command_name: string.String
|
|
43
|
+
has_command: bool
|
|
44
|
+
options: vec.Vec[OptionMatch]
|
|
45
|
+
positionals: vec.Vec[string.String]
|
|
46
|
+
show_help: bool
|
|
47
|
+
|
|
48
|
+
public struct Error:
|
|
49
|
+
message: string.String
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
function create_error(message: str) -> Error:
|
|
53
|
+
return Error(message = string.String.from_str(message))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
function create_option_match(spec: OptionSpec) -> OptionMatch:
|
|
57
|
+
var value = string.String.create()
|
|
58
|
+
var has_value = false
|
|
59
|
+
match spec.default_value:
|
|
60
|
+
Option.some as payload:
|
|
61
|
+
value = string.String.from_str(payload.value)
|
|
62
|
+
has_value = true
|
|
63
|
+
Option.none:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
return OptionMatch(
|
|
67
|
+
name = string.String.from_str(spec.long_name),
|
|
68
|
+
present = false,
|
|
69
|
+
value = value,
|
|
70
|
+
has_value = has_value
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
function create_match() -> Match:
|
|
75
|
+
return Match(
|
|
76
|
+
command_name = string.String.create(),
|
|
77
|
+
has_command = false,
|
|
78
|
+
options = vec.Vec[OptionMatch].create(),
|
|
79
|
+
positionals = vec.Vec[string.String].create(),
|
|
80
|
+
show_help = false
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
function append_line(output: ref[string.String], line: str) -> void:
|
|
85
|
+
output.append_format(f"#{line}\n")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
function append_switch_label(output: ref[string.String], spec: OptionSpec) -> void:
|
|
89
|
+
match spec.short_name:
|
|
90
|
+
Option.some as payload:
|
|
91
|
+
output.append_format(f"-#{payload.value}, ")
|
|
92
|
+
Option.none:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
output.append_format(f"--#{spec.long_name}")
|
|
96
|
+
|
|
97
|
+
if spec.kind == OptionKind.value:
|
|
98
|
+
match spec.value_name:
|
|
99
|
+
Option.some as payload:
|
|
100
|
+
output.append_format(f" #{payload.value}")
|
|
101
|
+
Option.none:
|
|
102
|
+
output.append(" VALUE")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
function append_option_help(output: ref[string.String], spec: OptionSpec) -> void:
|
|
106
|
+
output.append(" ")
|
|
107
|
+
append_switch_label(output, spec)
|
|
108
|
+
output.append_format(f" - #{spec.help}")
|
|
109
|
+
match spec.default_value:
|
|
110
|
+
Option.some as payload:
|
|
111
|
+
output.append_format(f" (default: #{payload.value})")
|
|
112
|
+
Option.none:
|
|
113
|
+
pass
|
|
114
|
+
if spec.required:
|
|
115
|
+
output.append(" (required)")
|
|
116
|
+
output.append("\n")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
function append_help_header(output: ref[string.String], title: str, summary: str, usage: str) -> void:
|
|
120
|
+
append_line(output, title)
|
|
121
|
+
append_line(output, usage)
|
|
122
|
+
append_line(output, "")
|
|
123
|
+
append_line(output, summary)
|
|
124
|
+
append_line(output, "")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
function append_commands_section(output: ref[string.String], commands: span[CommandSpec]) -> void:
|
|
128
|
+
if commands.len == 0:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
append_line(output, "Commands:")
|
|
132
|
+
var index: ptr_uint = 0
|
|
133
|
+
while index < commands.len:
|
|
134
|
+
let spec = unsafe: read(commands.data + index)
|
|
135
|
+
output.append_format(f" #{spec.name} - #{spec.summary}\n")
|
|
136
|
+
index += 1
|
|
137
|
+
append_line(output, "")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
function append_options_section(
|
|
141
|
+
output: ref[string.String],
|
|
142
|
+
app_options: span[OptionSpec],
|
|
143
|
+
command_options: span[OptionSpec]
|
|
144
|
+
) -> void:
|
|
145
|
+
append_line(output, "Options:")
|
|
146
|
+
append_line(output, " -h, --help - Show this help")
|
|
147
|
+
|
|
148
|
+
var index: ptr_uint = 0
|
|
149
|
+
while index < app_options.len:
|
|
150
|
+
append_option_help(output, unsafe: read(app_options.data + index))
|
|
151
|
+
index += 1
|
|
152
|
+
|
|
153
|
+
index = 0
|
|
154
|
+
while index < command_options.len:
|
|
155
|
+
append_option_help(output, unsafe: read(command_options.data + index))
|
|
156
|
+
index += 1
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
function short_name_valid(short_name: str) -> bool:
|
|
160
|
+
return short_name.len == 1
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
function find_command(commands: span[CommandSpec], name: str) -> ptr[CommandSpec]?:
|
|
164
|
+
var index: ptr_uint = 0
|
|
165
|
+
while index < commands.len:
|
|
166
|
+
let candidate = unsafe: commands.data + index
|
|
167
|
+
if unsafe: read(candidate).name.equal(name):
|
|
168
|
+
return candidate
|
|
169
|
+
index += 1
|
|
170
|
+
|
|
171
|
+
return null
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
function find_long_option(
|
|
175
|
+
app_options: span[OptionSpec],
|
|
176
|
+
command_options: span[OptionSpec],
|
|
177
|
+
name: str
|
|
178
|
+
) -> ptr[OptionSpec]?:
|
|
179
|
+
var index: ptr_uint = 0
|
|
180
|
+
while index < command_options.len:
|
|
181
|
+
let candidate = unsafe: command_options.data + index
|
|
182
|
+
if unsafe: read(candidate).long_name.equal(name):
|
|
183
|
+
return candidate
|
|
184
|
+
index += 1
|
|
185
|
+
|
|
186
|
+
index = 0
|
|
187
|
+
while index < app_options.len:
|
|
188
|
+
let candidate = unsafe: app_options.data + index
|
|
189
|
+
if unsafe: read(candidate).long_name.equal(name):
|
|
190
|
+
return candidate
|
|
191
|
+
index += 1
|
|
192
|
+
|
|
193
|
+
return null
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
function find_short_option(
|
|
197
|
+
app_options: span[OptionSpec],
|
|
198
|
+
command_options: span[OptionSpec],
|
|
199
|
+
name: str
|
|
200
|
+
) -> ptr[OptionSpec]?:
|
|
201
|
+
var index: ptr_uint = 0
|
|
202
|
+
while index < command_options.len:
|
|
203
|
+
let candidate = unsafe: command_options.data + index
|
|
204
|
+
match unsafe: read(candidate).short_name:
|
|
205
|
+
Option.some as payload:
|
|
206
|
+
if payload.value.equal(name):
|
|
207
|
+
return candidate
|
|
208
|
+
Option.none:
|
|
209
|
+
pass
|
|
210
|
+
index += 1
|
|
211
|
+
|
|
212
|
+
index = 0
|
|
213
|
+
while index < app_options.len:
|
|
214
|
+
let candidate = unsafe: app_options.data + index
|
|
215
|
+
match unsafe: read(candidate).short_name:
|
|
216
|
+
Option.some as payload:
|
|
217
|
+
if payload.value.equal(name):
|
|
218
|
+
return candidate
|
|
219
|
+
Option.none:
|
|
220
|
+
pass
|
|
221
|
+
index += 1
|
|
222
|
+
|
|
223
|
+
return null
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
function find_option_match(matches: vec.Vec[OptionMatch], name: str) -> ptr[OptionMatch]?:
|
|
227
|
+
var index: ptr_uint = 0
|
|
228
|
+
while true:
|
|
229
|
+
if index >= matches.len():
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
let candidate = matches.get(index) else:
|
|
233
|
+
return null
|
|
234
|
+
|
|
235
|
+
if unsafe: read(candidate).name.as_str().equal(name):
|
|
236
|
+
return candidate
|
|
237
|
+
|
|
238
|
+
index += 1
|
|
239
|
+
|
|
240
|
+
return null
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
function append_option_specs(matches: ptr[vec.Vec[OptionMatch]], specs: span[OptionSpec]) -> Result[bool, Error]:
|
|
244
|
+
var index: ptr_uint = 0
|
|
245
|
+
while index < specs.len:
|
|
246
|
+
let spec = unsafe: read(specs.data + index)
|
|
247
|
+
|
|
248
|
+
if spec.long_name.len == 0:
|
|
249
|
+
return Result[bool, Error].failure(error= create_error("cli option long name cannot be empty"))
|
|
250
|
+
|
|
251
|
+
match spec.short_name:
|
|
252
|
+
Option.some as payload:
|
|
253
|
+
if not short_name_valid(payload.value):
|
|
254
|
+
return Result[
|
|
255
|
+
bool,
|
|
256
|
+
Error
|
|
257
|
+
].failure(error= create_error("cli short option names must be exactly one character"))
|
|
258
|
+
Option.none:
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
let current = unsafe: read(matches)
|
|
262
|
+
if find_option_match(current, spec.long_name) != null:
|
|
263
|
+
return Result[
|
|
264
|
+
bool,
|
|
265
|
+
Error
|
|
266
|
+
].failure(error= create_error("cli option names must be unique within the active command"))
|
|
267
|
+
|
|
268
|
+
unsafe: read(matches).push(create_option_match(spec))
|
|
269
|
+
index += 1
|
|
270
|
+
|
|
271
|
+
return Result[bool, Error].success(value= true)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
function set_option_flag(result: ptr[Match], spec: OptionSpec) -> Result[bool, Error]:
|
|
275
|
+
let current = unsafe: read(result)
|
|
276
|
+
let option_match = find_option_match(current.options, spec.long_name) else:
|
|
277
|
+
return Result[bool, Error].failure(error= create_error("cli parser lost option state"))
|
|
278
|
+
|
|
279
|
+
unsafe:
|
|
280
|
+
read(option_match).present = true
|
|
281
|
+
if spec.kind == OptionKind.flag:
|
|
282
|
+
read(option_match).has_value = false
|
|
283
|
+
read(option_match).value.clear()
|
|
284
|
+
|
|
285
|
+
return Result[bool, Error].success(value= true)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
function set_option_value(result: ptr[Match], spec: OptionSpec, value: str) -> Result[bool, Error]:
|
|
289
|
+
let current = unsafe: read(result)
|
|
290
|
+
let option_match = find_option_match(current.options, spec.long_name) else:
|
|
291
|
+
return Result[bool, Error].failure(error= create_error("cli parser lost option state"))
|
|
292
|
+
|
|
293
|
+
unsafe:
|
|
294
|
+
read(option_match).present = true
|
|
295
|
+
read(option_match).has_value = true
|
|
296
|
+
read(option_match).value.assign(value)
|
|
297
|
+
|
|
298
|
+
return Result[bool, Error].success(value= true)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
function require_value_arg(option_name: str) -> Error:
|
|
302
|
+
let message = fmt.format(f"missing value for #{option_name}")
|
|
303
|
+
return Error(message = message)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
function required_option_error(option_name: str) -> Error:
|
|
307
|
+
let message = fmt.format(f"required option #{option_name} is missing")
|
|
308
|
+
return Error(message = message)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
function unknown_option_error(option_name: str) -> Error:
|
|
312
|
+
let message = fmt.format(f"unknown option #{option_name}")
|
|
313
|
+
return Error(message = message)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
function unknown_command_error(command_name: str) -> Error:
|
|
317
|
+
let message = fmt.format(f"unknown command #{command_name}")
|
|
318
|
+
return Error(message = message)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
function option_takes_value(spec: OptionSpec) -> bool:
|
|
322
|
+
return spec.kind == OptionKind.value
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
function split_long_option(arg: str) -> Option[ptr_uint]:
|
|
326
|
+
match arg.find_byte(byte_equals):
|
|
327
|
+
Option.some as payload:
|
|
328
|
+
if payload.value > 2:
|
|
329
|
+
return Option[ptr_uint].some(value= payload.value)
|
|
330
|
+
Option.none:
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
return Option[ptr_uint].none
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
function usage_for_app(app: AppSpec) -> string.String:
|
|
337
|
+
var usage = string.String.create()
|
|
338
|
+
usage.append_format(f"Usage: #{app.name}")
|
|
339
|
+
if app.options.len != 0:
|
|
340
|
+
usage.append(" [options]")
|
|
341
|
+
if app.commands.len != 0:
|
|
342
|
+
usage.append(" <command>")
|
|
343
|
+
else:
|
|
344
|
+
match app.positional_help:
|
|
345
|
+
Option.some as payload:
|
|
346
|
+
usage.append(" ")
|
|
347
|
+
usage.append(payload.value)
|
|
348
|
+
Option.none:
|
|
349
|
+
pass
|
|
350
|
+
return usage
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
function usage_for_command(app: AppSpec, command: CommandSpec) -> string.String:
|
|
354
|
+
var usage = string.String.create()
|
|
355
|
+
usage.append_format(f"Usage: #{app.name} #{command.name}")
|
|
356
|
+
if app.options.len != 0 or command.options.len != 0:
|
|
357
|
+
usage.append(" [options]")
|
|
358
|
+
match command.positional_help:
|
|
359
|
+
Option.some as payload:
|
|
360
|
+
usage.append(" ")
|
|
361
|
+
usage.append(payload.value)
|
|
362
|
+
Option.none:
|
|
363
|
+
pass
|
|
364
|
+
return usage
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
function validate_required_options(
|
|
368
|
+
result: Match,
|
|
369
|
+
app_options: span[OptionSpec],
|
|
370
|
+
command_options: span[OptionSpec]
|
|
371
|
+
) -> Result[bool, Error]:
|
|
372
|
+
var index: ptr_uint = 0
|
|
373
|
+
while index < app_options.len:
|
|
374
|
+
let spec = unsafe: read(app_options.data + index)
|
|
375
|
+
if spec.required:
|
|
376
|
+
let option_match = find_option_match(result.options, spec.long_name) else:
|
|
377
|
+
return Result[bool, Error].failure(error= create_error("cli parser lost global option state"))
|
|
378
|
+
|
|
379
|
+
let current = unsafe: read(option_match)
|
|
380
|
+
if not current.present and not current.has_value:
|
|
381
|
+
return Result[bool, Error].failure(error= required_option_error(spec.long_name))
|
|
382
|
+
index += 1
|
|
383
|
+
|
|
384
|
+
index = 0
|
|
385
|
+
while index < command_options.len:
|
|
386
|
+
let spec = unsafe: read(command_options.data + index)
|
|
387
|
+
if spec.required:
|
|
388
|
+
let option_match = find_option_match(result.options, spec.long_name) else:
|
|
389
|
+
return Result[bool, Error].failure(error= create_error("cli parser lost command option state"))
|
|
390
|
+
|
|
391
|
+
let current = unsafe: read(option_match)
|
|
392
|
+
if not current.present and not current.has_value:
|
|
393
|
+
return Result[bool, Error].failure(error= required_option_error(spec.long_name))
|
|
394
|
+
index += 1
|
|
395
|
+
|
|
396
|
+
return Result[bool, Error].success(value= true)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
public function flag_option(long_name: str, short_name: Option[str], help: str) -> OptionSpec:
|
|
400
|
+
return OptionSpec(
|
|
401
|
+
long_name = long_name,
|
|
402
|
+
short_name = short_name,
|
|
403
|
+
kind = OptionKind.flag,
|
|
404
|
+
help = help,
|
|
405
|
+
value_name = Option[str].none,
|
|
406
|
+
default_value = Option[str].none,
|
|
407
|
+
required = false
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
public function value_option(
|
|
412
|
+
long_name: str,
|
|
413
|
+
short_name: Option[str],
|
|
414
|
+
value_name: str,
|
|
415
|
+
help: str,
|
|
416
|
+
required: bool,
|
|
417
|
+
default_value: Option[str]
|
|
418
|
+
) -> OptionSpec:
|
|
419
|
+
return OptionSpec(
|
|
420
|
+
long_name = long_name,
|
|
421
|
+
short_name = short_name,
|
|
422
|
+
kind = OptionKind.value,
|
|
423
|
+
help = help,
|
|
424
|
+
value_name = Option[str].some(value= value_name),
|
|
425
|
+
default_value = default_value,
|
|
426
|
+
required = required
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
public function command_spec(
|
|
431
|
+
name: str,
|
|
432
|
+
summary: str,
|
|
433
|
+
help: str,
|
|
434
|
+
options: span[OptionSpec],
|
|
435
|
+
positional_help: Option[str]
|
|
436
|
+
) -> CommandSpec:
|
|
437
|
+
return CommandSpec(
|
|
438
|
+
name = name,
|
|
439
|
+
summary = summary,
|
|
440
|
+
help = help,
|
|
441
|
+
options = options,
|
|
442
|
+
positional_help = positional_help
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
public function app_spec(
|
|
447
|
+
name: str,
|
|
448
|
+
summary: str,
|
|
449
|
+
options: span[OptionSpec],
|
|
450
|
+
commands: span[CommandSpec],
|
|
451
|
+
positional_help: Option[str]
|
|
452
|
+
) -> AppSpec:
|
|
453
|
+
return AppSpec(
|
|
454
|
+
name = name,
|
|
455
|
+
summary = summary,
|
|
456
|
+
options = options,
|
|
457
|
+
commands = commands,
|
|
458
|
+
positional_help = positional_help
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
function parse_long_option_impl(
|
|
463
|
+
result: ptr[Match],
|
|
464
|
+
app_options: span[OptionSpec],
|
|
465
|
+
command_options: span[OptionSpec],
|
|
466
|
+
arg: str,
|
|
467
|
+
next_arg: Option[str]
|
|
468
|
+
) -> Result[ptr_uint, Error]:
|
|
469
|
+
let split_index = split_long_option(arg)
|
|
470
|
+
var option_name = arg.slice(2, arg.len - 2)
|
|
471
|
+
var inline_value = Option[str].none
|
|
472
|
+
match split_index:
|
|
473
|
+
Option.some as payload:
|
|
474
|
+
option_name = arg.slice(2, payload.value - 2)
|
|
475
|
+
inline_value = Option[str].some(value= arg.slice(payload.value + 1, arg.len - payload.value - 1))
|
|
476
|
+
Option.none:
|
|
477
|
+
pass
|
|
478
|
+
|
|
479
|
+
let spec_ptr = find_long_option(app_options, command_options, option_name) else:
|
|
480
|
+
return Result[ptr_uint, Error].failure(error= unknown_option_error(arg))
|
|
481
|
+
|
|
482
|
+
let spec = unsafe: read(spec_ptr)
|
|
483
|
+
if option_takes_value(spec):
|
|
484
|
+
var consumed = 1z
|
|
485
|
+
|
|
486
|
+
match inline_value:
|
|
487
|
+
Option.some as inline_payload:
|
|
488
|
+
match set_option_value(result, spec, inline_payload.value):
|
|
489
|
+
Result.failure as set_error:
|
|
490
|
+
return Result[ptr_uint, Error].failure(error= set_error.error)
|
|
491
|
+
Result.success:
|
|
492
|
+
return Result[ptr_uint, Error].success(value= consumed)
|
|
493
|
+
Option.none:
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
let value_arg = next_arg else:
|
|
497
|
+
let missing = fmt.format(f"--#{spec.long_name}")
|
|
498
|
+
return Result[ptr_uint, Error].failure(error= require_value_arg(missing.as_str()))
|
|
499
|
+
|
|
500
|
+
match set_option_value(result, spec, value_arg):
|
|
501
|
+
Result.failure as payload:
|
|
502
|
+
return Result[ptr_uint, Error].failure(error= payload.error)
|
|
503
|
+
Result.success:
|
|
504
|
+
consumed = 2z
|
|
505
|
+
return Result[ptr_uint, Error].success(value= consumed)
|
|
506
|
+
|
|
507
|
+
match inline_value:
|
|
508
|
+
Option.some:
|
|
509
|
+
return Result[ptr_uint, Error].failure(error= create_error("flag options do not accept inline values"))
|
|
510
|
+
Option.none:
|
|
511
|
+
pass
|
|
512
|
+
|
|
513
|
+
match set_option_flag(result, spec):
|
|
514
|
+
Result.failure as payload:
|
|
515
|
+
return Result[ptr_uint, Error].failure(error= payload.error)
|
|
516
|
+
Result.success:
|
|
517
|
+
return Result[ptr_uint, Error].success(value= 1z)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
function parse_short_option_impl(
|
|
521
|
+
result: ptr[Match],
|
|
522
|
+
app_options: span[OptionSpec],
|
|
523
|
+
command_options: span[OptionSpec],
|
|
524
|
+
arg: str,
|
|
525
|
+
next_arg: Option[str]
|
|
526
|
+
) -> Result[ptr_uint, Error]:
|
|
527
|
+
var short_index: ptr_uint = 1
|
|
528
|
+
while short_index < arg.len:
|
|
529
|
+
let short_name = arg.slice(short_index, 1)
|
|
530
|
+
|
|
531
|
+
let spec_ptr = find_short_option(app_options, command_options, short_name) else:
|
|
532
|
+
return Result[ptr_uint, Error].failure(error= unknown_option_error(arg))
|
|
533
|
+
|
|
534
|
+
let spec = unsafe: read(spec_ptr)
|
|
535
|
+
if option_takes_value(spec):
|
|
536
|
+
var consumed = 1z
|
|
537
|
+
|
|
538
|
+
if short_index + 1 < arg.len:
|
|
539
|
+
let value = arg.slice(short_index + 1, arg.len - short_index - 1)
|
|
540
|
+
match set_option_value(result, spec, value):
|
|
541
|
+
Result.failure as payload:
|
|
542
|
+
return Result[ptr_uint, Error].failure(error= payload.error)
|
|
543
|
+
Result.success:
|
|
544
|
+
return Result[ptr_uint, Error].success(value= consumed)
|
|
545
|
+
|
|
546
|
+
let value_arg = next_arg else:
|
|
547
|
+
let missing = fmt.format(f"-#{short_name}")
|
|
548
|
+
return Result[ptr_uint, Error].failure(error= require_value_arg(missing.as_str()))
|
|
549
|
+
|
|
550
|
+
match set_option_value(result, spec, value_arg):
|
|
551
|
+
Result.failure as payload:
|
|
552
|
+
return Result[ptr_uint, Error].failure(error= payload.error)
|
|
553
|
+
Result.success:
|
|
554
|
+
consumed = 2z
|
|
555
|
+
return Result[ptr_uint, Error].success(value= consumed)
|
|
556
|
+
|
|
557
|
+
match set_option_flag(result, spec):
|
|
558
|
+
Result.failure as payload:
|
|
559
|
+
return Result[ptr_uint, Error].failure(error= payload.error)
|
|
560
|
+
Result.success:
|
|
561
|
+
pass
|
|
562
|
+
|
|
563
|
+
short_index += 1
|
|
564
|
+
|
|
565
|
+
return Result[ptr_uint, Error].success(value= 1z)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
public function parse(app: AppSpec, args: span[str]) -> Result[Match, Error]:
|
|
569
|
+
var result = create_match()
|
|
570
|
+
|
|
571
|
+
match append_option_specs(ptr_of(result.options), app.options):
|
|
572
|
+
Result.failure as append_error_payload:
|
|
573
|
+
result.release()
|
|
574
|
+
return Result[Match, Error].failure(error= append_error_payload.error)
|
|
575
|
+
Result.success:
|
|
576
|
+
pass
|
|
577
|
+
|
|
578
|
+
var command_options: span[OptionSpec] = zero[span[OptionSpec]]
|
|
579
|
+
var index: ptr_uint = 0
|
|
580
|
+
var end_of_options = false
|
|
581
|
+
|
|
582
|
+
while index < args.len:
|
|
583
|
+
let arg = unsafe: read(args.data + index)
|
|
584
|
+
|
|
585
|
+
if not end_of_options and arg.equal("--"):
|
|
586
|
+
end_of_options = true
|
|
587
|
+
index += 1
|
|
588
|
+
continue
|
|
589
|
+
|
|
590
|
+
if not end_of_options and (arg.equal("--help") or arg.equal("-h")):
|
|
591
|
+
result.show_help = true
|
|
592
|
+
return Result[Match, Error].success(value= result)
|
|
593
|
+
|
|
594
|
+
if not end_of_options and arg.starts_with("--") and arg.len > 2:
|
|
595
|
+
var next_arg = Option[str].none
|
|
596
|
+
if index + 1 < args.len:
|
|
597
|
+
next_arg = Option[str].some(value = unsafe: read(args.data + index + 1))
|
|
598
|
+
|
|
599
|
+
match parse_long_option_impl(ptr_of(result), app.options, command_options, arg, next_arg):
|
|
600
|
+
Result.failure as payload:
|
|
601
|
+
result.release()
|
|
602
|
+
return Result[Match, Error].failure(error= payload.error)
|
|
603
|
+
Result.success as consumed_payload:
|
|
604
|
+
index += consumed_payload.value
|
|
605
|
+
|
|
606
|
+
continue
|
|
607
|
+
|
|
608
|
+
if not end_of_options and arg.starts_with("-") and arg.len > 1:
|
|
609
|
+
if arg.equal("-h"):
|
|
610
|
+
result.show_help = true
|
|
611
|
+
return Result[Match, Error].success(value= result)
|
|
612
|
+
|
|
613
|
+
var next_arg = Option[str].none
|
|
614
|
+
if index + 1 < args.len:
|
|
615
|
+
next_arg = Option[str].some(value = unsafe: read(args.data + index + 1))
|
|
616
|
+
|
|
617
|
+
match parse_short_option_impl(ptr_of(result), app.options, command_options, arg, next_arg):
|
|
618
|
+
Result.failure as payload:
|
|
619
|
+
result.release()
|
|
620
|
+
return Result[Match, Error].failure(error= payload.error)
|
|
621
|
+
Result.success as consumed_payload:
|
|
622
|
+
index += consumed_payload.value
|
|
623
|
+
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
if not result.has_command and app.commands.len != 0:
|
|
627
|
+
let command_ptr = find_command(app.commands, arg) else:
|
|
628
|
+
result.release()
|
|
629
|
+
return Result[Match, Error].failure(error= unknown_command_error(arg))
|
|
630
|
+
|
|
631
|
+
let command = unsafe: read(command_ptr)
|
|
632
|
+
result.has_command = true
|
|
633
|
+
result.command_name.assign(command.name)
|
|
634
|
+
command_options = command.options
|
|
635
|
+
match append_option_specs(ptr_of(result.options), command.options):
|
|
636
|
+
Result.failure as append_error_payload:
|
|
637
|
+
result.release()
|
|
638
|
+
return Result[Match, Error].failure(error= append_error_payload.error)
|
|
639
|
+
Result.success:
|
|
640
|
+
pass
|
|
641
|
+
index += 1
|
|
642
|
+
continue
|
|
643
|
+
|
|
644
|
+
result.positionals.push(string.String.from_str(arg))
|
|
645
|
+
index += 1
|
|
646
|
+
|
|
647
|
+
match validate_required_options(result, app.options, command_options):
|
|
648
|
+
Result.failure as validate_payload:
|
|
649
|
+
result.release()
|
|
650
|
+
return Result[Match, Error].failure(error= validate_payload.error)
|
|
651
|
+
Result.success:
|
|
652
|
+
pass
|
|
653
|
+
|
|
654
|
+
return Result[Match, Error].success(value= result)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
public function render_help(app: AppSpec) -> string.String:
|
|
658
|
+
var output = string.String.create()
|
|
659
|
+
var title = fmt.format(f"#{app.name} - #{app.summary}")
|
|
660
|
+
var usage = usage_for_app(app)
|
|
661
|
+
append_help_header(ref_of(output), title.as_str(), app.summary, usage.as_str())
|
|
662
|
+
append_commands_section(ref_of(output), app.commands)
|
|
663
|
+
append_options_section(ref_of(output), app.options, zero[span[OptionSpec]])
|
|
664
|
+
title.release()
|
|
665
|
+
usage.release()
|
|
666
|
+
return output
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
public function render_command_help(app: AppSpec, command_name: str) -> Result[string.String, Error]:
|
|
670
|
+
let command_ptr = find_command(app.commands, command_name) else:
|
|
671
|
+
return Result[string.String, Error].failure(error= unknown_command_error(command_name))
|
|
672
|
+
|
|
673
|
+
let command = unsafe: read(command_ptr)
|
|
674
|
+
var output = string.String.create()
|
|
675
|
+
var title = fmt.format(f"#{app.name} #{command.name} - #{command.help}")
|
|
676
|
+
var usage = usage_for_command(app, command)
|
|
677
|
+
append_help_header(ref_of(output), title.as_str(), command.help, usage.as_str())
|
|
678
|
+
append_options_section(ref_of(output), app.options, command.options)
|
|
679
|
+
title.release()
|
|
680
|
+
usage.release()
|
|
681
|
+
return Result[string.String, Error].success(value= output)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
extending OptionMatch:
|
|
685
|
+
public editable function release() -> void:
|
|
686
|
+
this.name.release()
|
|
687
|
+
this.value.release()
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
extending Match:
|
|
691
|
+
public function command() -> Option[str]:
|
|
692
|
+
if not this.has_command:
|
|
693
|
+
return Option[str].none
|
|
694
|
+
|
|
695
|
+
return Option[str].some(value= this.command_name.as_str())
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
public function help_requested() -> bool:
|
|
699
|
+
return this.show_help
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
public function supplied(name: str) -> bool:
|
|
703
|
+
let option_match = find_option_match(this.options, name) else:
|
|
704
|
+
return false
|
|
705
|
+
|
|
706
|
+
return unsafe: read(option_match).present
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
public function flag(name: str) -> bool:
|
|
710
|
+
return this.supplied(name)
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
public function value(name: str) -> Option[str]:
|
|
714
|
+
let option_match = find_option_match(this.options, name) else:
|
|
715
|
+
return Option[str].none
|
|
716
|
+
|
|
717
|
+
unsafe:
|
|
718
|
+
if not read(option_match).has_value:
|
|
719
|
+
return Option[str].none
|
|
720
|
+
|
|
721
|
+
return Option[str].some(value= read(option_match).value.as_str())
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
public function positionals_len() -> ptr_uint:
|
|
725
|
+
return this.positionals.len()
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
public function positional(index: ptr_uint) -> Option[str]:
|
|
729
|
+
let value = this.positionals.get(index) else:
|
|
730
|
+
return Option[str].none
|
|
731
|
+
|
|
732
|
+
unsafe:
|
|
733
|
+
return Option[str].some(value= read(value).as_str())
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
public editable function release() -> void:
|
|
737
|
+
this.command_name.release()
|
|
738
|
+
|
|
739
|
+
var option_index: ptr_uint = 0
|
|
740
|
+
while option_index < this.options.len():
|
|
741
|
+
let value = this.options.get(option_index)
|
|
742
|
+
if value == null:
|
|
743
|
+
break
|
|
744
|
+
|
|
745
|
+
unsafe:
|
|
746
|
+
read(ptr[OptionMatch]<-value).release()
|
|
747
|
+
|
|
748
|
+
option_index += 1
|
|
749
|
+
|
|
750
|
+
this.options.release()
|
|
751
|
+
|
|
752
|
+
var positional_index: ptr_uint = 0
|
|
753
|
+
while positional_index < this.positionals.len():
|
|
754
|
+
let value = this.positionals.get(positional_index)
|
|
755
|
+
if value == null:
|
|
756
|
+
break
|
|
757
|
+
|
|
758
|
+
unsafe:
|
|
759
|
+
read(ptr[string.String]<-value).release()
|
|
760
|
+
|
|
761
|
+
positional_index += 1
|
|
762
|
+
|
|
763
|
+
this.positionals.release()
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
extending Error:
|
|
767
|
+
public editable function release() -> void:
|
|
768
|
+
this.message.release()
|