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.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/docs/index.html +4 -3
  3. data/lib/milk_tea/base.rb +1 -1
  4. data/std/asset_pack.mt +267 -0
  5. data/std/async/libuv_runtime.mt +539 -0
  6. data/std/async/mailbox.mt +191 -0
  7. data/std/async/runtime.mt +87 -0
  8. data/std/async.mt +87 -0
  9. data/std/base64.mt +185 -0
  10. data/std/behavior_tree.mt +396 -0
  11. data/std/binary.mt +314 -0
  12. data/std/binary_heap.mt +157 -0
  13. data/std/bitset.mt +239 -0
  14. data/std/box2d.mt +535 -0
  15. data/std/bytes.mt +46 -0
  16. data/std/c/box2d.mt +1254 -0
  17. data/std/c/cgltf.mt +647 -0
  18. data/std/c/cjson.mt +106 -0
  19. data/std/c/crypto.mt +11 -0
  20. data/std/c/crypto_support.h +51 -0
  21. data/std/c/ctype.mt +19 -0
  22. data/std/c/ctype_bindgen.h +29 -0
  23. data/std/c/curl.mt +1223 -0
  24. data/std/c/enet.mt +473 -0
  25. data/std/c/errno.mt +41 -0
  26. data/std/c/errno_bindgen.h +53 -0
  27. data/std/c/flecs.mt +2945 -0
  28. data/std/c/fs.linux.mt +43 -0
  29. data/std/c/fs.windows.mt +43 -0
  30. data/std/c/fs_support.h +954 -0
  31. data/std/c/gl.mt +2065 -0
  32. data/std/c/gl_registry_helpers.h +10748 -0
  33. data/std/c/glfw.mt +499 -0
  34. data/std/c/libc.mt +167 -0
  35. data/std/c/libuv.mt +1491 -0
  36. data/std/c/math.mt +22 -0
  37. data/std/c/math_bindgen.h +31 -0
  38. data/std/c/miniaudio.mt +3420 -0
  39. data/std/c/pcre2.mt +735 -0
  40. data/std/c/process.mt +58 -0
  41. data/std/c/process_support.h +1147 -0
  42. data/std/c/raygui.mt +1602 -0
  43. data/std/c/raylib.mt +1245 -0
  44. data/std/c/raymath.mt +163 -0
  45. data/std/c/rlgl.mt +434 -0
  46. data/std/c/rpng.mt +60 -0
  47. data/std/c/rres.mt +187 -0
  48. data/std/c/sdl3.mt +4132 -0
  49. data/std/c/sqlite3.mt +951 -0
  50. data/std/c/stb_image.mt +52 -0
  51. data/std/c/stb_image_resize2.mt +128 -0
  52. data/std/c/stb_image_write.mt +19 -0
  53. data/std/c/stb_rect_pack.mt +38 -0
  54. data/std/c/stb_truetype.mt +175 -0
  55. data/std/c/stb_vorbis.mt +31 -0
  56. data/std/c/stdio.mt +90 -0
  57. data/std/c/steamworks.h +5769 -0
  58. data/std/c/steamworks.mt +4771 -0
  59. data/std/c/string.mt +16 -0
  60. data/std/c/string_bindgen.h +36 -0
  61. data/std/c/sync.mt +33 -0
  62. data/std/c/sync_support.h +219 -0
  63. data/std/c/terminal.mt +24 -0
  64. data/std/c/terminal_support.h +408 -0
  65. data/std/c/time.mt +26 -0
  66. data/std/c/tls.mt +24 -0
  67. data/std/c/tls_support.h +626 -0
  68. data/std/c/tracy.mt +53 -0
  69. data/std/c/zlib.mt +20 -0
  70. data/std/c/zlib_support.h +239 -0
  71. data/std/c/zstd.mt +231 -0
  72. data/std/cell.mt +60 -0
  73. data/std/cgltf.mt +115 -0
  74. data/std/cjson.mt +90 -0
  75. data/std/cli.mt +768 -0
  76. data/std/cookie.mt +198 -0
  77. data/std/counter.mt +160 -0
  78. data/std/crypto.mt +74 -0
  79. data/std/cstring.mt +13 -0
  80. data/std/ctype.mt +52 -0
  81. data/std/curl/runtime.mt +261 -0
  82. data/std/curl.mt +38 -0
  83. data/std/deque.mt +348 -0
  84. data/std/encoding.mt +93 -0
  85. data/std/enet.mt +133 -0
  86. data/std/env.mt +41 -0
  87. data/std/errno.mt +47 -0
  88. data/std/flecs.mt +978 -0
  89. data/std/fmt.mt +271 -0
  90. data/std/fs.linux.mt +696 -0
  91. data/std/fs.windows.mt +711 -0
  92. data/std/fsm.mt +267 -0
  93. data/std/gl.mt +2062 -0
  94. data/std/glfw.mt +471 -0
  95. data/std/goap.mt +342 -0
  96. data/std/graph.mt +521 -0
  97. data/std/gzip.mt +77 -0
  98. data/std/hash.mt +429 -0
  99. data/std/http/server.mt +810 -0
  100. data/std/http.mt +1227 -0
  101. data/std/intern.mt +53 -0
  102. data/std/jobs.mt +288 -0
  103. data/std/json.mt +627 -0
  104. data/std/libc.mt +34 -0
  105. data/std/libuv.mt +554 -0
  106. data/std/linear_algebra.mt +243 -0
  107. data/std/linked_map.mt +372 -0
  108. data/std/linked_map_view.mt +49 -0
  109. data/std/linked_set.mt +117 -0
  110. data/std/log.mt +69 -0
  111. data/std/map.mt +418 -0
  112. data/std/math.mt +24 -0
  113. data/std/mem/arena.mt +137 -0
  114. data/std/mem/endian.mt +51 -0
  115. data/std/mem/heap.mt +298 -0
  116. data/std/mem/pool.mt +164 -0
  117. data/std/mem/stack.mt +47 -0
  118. data/std/mem/tracking.mt +119 -0
  119. data/std/miniaudio.mt +1299 -0
  120. data/std/multiset.mt +191 -0
  121. data/std/net/channel.mt +727 -0
  122. data/std/net/clock.mt +237 -0
  123. data/std/net/discovery.mt +277 -0
  124. data/std/net/lobby.mt +813 -0
  125. data/std/net/manager.mt +539 -0
  126. data/std/net/mux.mt +839 -0
  127. data/std/net/nat.mt +122 -0
  128. data/std/net/packet.mt +219 -0
  129. data/std/net/punch.mt +162 -0
  130. data/std/net/rpc.mt +119 -0
  131. data/std/net/session.mt +1157 -0
  132. data/std/net/stun.mt +252 -0
  133. data/std/net/sync.mt +183 -0
  134. data/std/net/turn.mt +459 -0
  135. data/std/net.mt +2944 -0
  136. data/std/oauth2.mt +408 -0
  137. data/std/option.mt +51 -0
  138. data/std/ordered_map.mt +497 -0
  139. data/std/ordered_set.mt +348 -0
  140. data/std/path.mt +368 -0
  141. data/std/pcre2/runtime.mt +35 -0
  142. data/std/pcre2.mt +78 -0
  143. data/std/priority_queue.mt +55 -0
  144. data/std/process.mt +837 -0
  145. data/std/queue.mt +55 -0
  146. data/std/random.mt +146 -0
  147. data/std/raygui.mt +99 -0
  148. data/std/raylib/debug_console.mt +71 -0
  149. data/std/raylib/easing.mt +388 -0
  150. data/std/raylib/packed_assets.mt +255 -0
  151. data/std/raylib/runtime.mt +30 -0
  152. data/std/raylib/tracy_gpu.mt +37 -0
  153. data/std/raylib.mt +1510 -0
  154. data/std/raymath.mt +153 -0
  155. data/std/result.mt +89 -0
  156. data/std/rlgl.mt +268 -0
  157. data/std/rpng.mt +46 -0
  158. data/std/rres.mt +36 -0
  159. data/std/sdl3/runtime.mt +54 -0
  160. data/std/sdl3.mt +1731 -0
  161. data/std/serialize.mt +68 -0
  162. data/std/set.mt +124 -0
  163. data/std/spatial.mt +176 -0
  164. data/std/sqlite3.mt +151 -0
  165. data/std/stack.mt +55 -0
  166. data/std/stb_image.mt +47 -0
  167. data/std/stb_image_resize2.mt +41 -0
  168. data/std/stb_image_write.mt +17 -0
  169. data/std/stb_rect_pack.mt +15 -0
  170. data/std/stb_truetype.mt +77 -0
  171. data/std/stb_vorbis.mt +16 -0
  172. data/std/stdio.mt +88 -0
  173. data/std/steamworks.mt +1542 -0
  174. data/std/str.mt +293 -0
  175. data/std/string.mt +234 -0
  176. data/std/sync.mt +194 -0
  177. data/std/tar.mt +704 -0
  178. data/std/terminal.mt +1002 -0
  179. data/std/testing.mt +266 -0
  180. data/std/thread.mt +120 -0
  181. data/std/time.mt +105 -0
  182. data/std/tls.mt +616 -0
  183. data/std/toml.mt +1310 -0
  184. data/std/tracy.mt +42 -0
  185. data/std/uri.mt +118 -0
  186. data/std/url.mt +372 -0
  187. data/std/vec.mt +433 -0
  188. data/std/zstd.mt +94 -0
  189. metadata +187 -2
data/std/tar.mt ADDED
@@ -0,0 +1,704 @@
1
+ import std.bytes as bytes
2
+ import std.fs as fs
3
+ import std.gzip as gzip
4
+ import std.mem.heap as heap
5
+ import std.path as path_ops
6
+ import std.str as text
7
+ import std.string as string
8
+ import std.vec as vec
9
+
10
+ const block_size: ptr_uint = 512
11
+ const file_type_flag: ubyte = 48
12
+ const directory_type_flag: ubyte = 53
13
+ const zero_byte: ubyte = 0
14
+ const slash_byte: ubyte = 47
15
+ const space_byte: ubyte = 32
16
+
17
+ public struct Error:
18
+ message: string.String
19
+
20
+ enum EntryKind: int
21
+ file = 0
22
+ directory = 1
23
+
24
+ struct UstarPath:
25
+ prefix_start: ptr_uint
26
+ prefix_len: ptr_uint
27
+ name_start: ptr_uint
28
+ name_len: ptr_uint
29
+
30
+ struct ParsedEntry:
31
+ path: string.String
32
+ kind: EntryKind
33
+ mode: int
34
+ size: ptr_uint
35
+ data_offset: ptr_uint
36
+
37
+
38
+ function error_message(message: str) -> Error:
39
+ return Error(message = string.String.from_str(message))
40
+
41
+
42
+ function take_vec_bytes(values: ref[vec.Vec[ubyte]]) -> bytes.Bytes:
43
+ let data = values.data
44
+ let len = values.len
45
+ values.data = null
46
+ values.len = 0
47
+ values.capacity = 0
48
+
49
+ if data == null:
50
+ if len != 0:
51
+ fatal(c"tar.take_vec_bytes missing storage")
52
+
53
+ return bytes.Bytes.empty()
54
+
55
+ return bytes.Bytes(data = data, len = len)
56
+
57
+
58
+ function take_fs_error(raw: fs.Error) -> Error:
59
+ let message = string.String.from_str(raw.message.as_str())
60
+ var owned = raw
61
+ owned.release()
62
+ return Error(message = message)
63
+
64
+
65
+ function take_gzip_error(raw: gzip.Error) -> Error:
66
+ let message = string.String.from_str(raw.message.as_str())
67
+ var owned = raw
68
+ owned.release()
69
+ return Error(message = message)
70
+
71
+
72
+ function release_string_values(values: ref[vec.Vec[string.String]]) -> void:
73
+ var index: ptr_uint = 0
74
+ while index < values.len():
75
+ let value_ptr = values.get(index) else:
76
+ fatal(c"tar.release_string_values missing value")
77
+
78
+ unsafe:
79
+ read(value_ptr).release()
80
+
81
+ index += 1
82
+
83
+ values.release()
84
+
85
+
86
+ function compare_text(left: str, right: str) -> int:
87
+ var index: ptr_uint = 0
88
+ var shared_len = left.len
89
+ if right.len < shared_len:
90
+ shared_len = right.len
91
+
92
+ while index < shared_len:
93
+ let left_value = left.byte_at(index)
94
+ let right_value = right.byte_at(index)
95
+ if left_value < right_value:
96
+ return -1
97
+ if left_value > right_value:
98
+ return 1
99
+ index += 1
100
+
101
+ if left.len < right.len:
102
+ return -1
103
+ if left.len > right.len:
104
+ return 1
105
+
106
+ return 0
107
+
108
+
109
+ function sort_string_values(values: ref[vec.Vec[string.String]]) -> void:
110
+ var index: ptr_uint = 1
111
+ while index < values.len():
112
+ var current = index
113
+ while current > 0:
114
+ let previous_ptr = values.get(current - 1) else:
115
+ fatal(c"tar.sort_string_values missing previous value")
116
+ let current_ptr = values.get(current) else:
117
+ fatal(c"tar.sort_string_values missing current value")
118
+
119
+ var should_swap = false
120
+ unsafe:
121
+ should_swap = compare_text(read(previous_ptr).as_str(), read(current_ptr).as_str()) > 0
122
+ if should_swap:
123
+ values.swap(current - 1, current)
124
+ else:
125
+ break
126
+
127
+ current -= 1
128
+
129
+ index += 1
130
+
131
+
132
+ function copy_sorted_entry_names(entries: fs.Entries, include_hidden: bool) -> Result[vec.Vec[string.String], Error]:
133
+ var values = vec.Vec[string.String].create()
134
+
135
+ var index: ptr_uint = 0
136
+ while index < entries.len():
137
+ match entries.get(index):
138
+ Option.none:
139
+ release_string_values(ref_of(values))
140
+ return Result[
141
+ vec.Vec[string.String],
142
+ Error
143
+ ].failure(error = error_message("tar list entries missing name"))
144
+ Option.some as payload:
145
+ if include_hidden or not payload.value.starts_with("."):
146
+ values.push(string.String.from_str(payload.value))
147
+
148
+ index += 1
149
+
150
+ sort_string_values(ref_of(values))
151
+ return Result[vec.Vec[string.String], Error].success(value = values)
152
+
153
+
154
+ function append_zero_span(output: ref[vec.Vec[ubyte]], count: ptr_uint) -> void:
155
+ if count == 0:
156
+ return
157
+
158
+ var zeroes = zero[array[ubyte, 512]]
159
+ output.append_span(span[ubyte](data = ptr_of(zeroes[0]), len = count))
160
+
161
+
162
+ function append_padding(output: ref[vec.Vec[ubyte]], size_bytes: ptr_uint) -> void:
163
+ let remainder = size_bytes % block_size
164
+ if remainder == 0:
165
+ return
166
+
167
+ append_zero_span(output, block_size - remainder)
168
+
169
+
170
+ function append_end_blocks(output: ref[vec.Vec[ubyte]]) -> void:
171
+ append_zero_span(output, block_size)
172
+ append_zero_span(output, block_size)
173
+
174
+
175
+ function split_ustar_path(path: str) -> Result[UstarPath, Error]:
176
+ if path.len == 0:
177
+ return Result[UstarPath, Error].failure(error = error_message("tar entry path cannot be empty"))
178
+ if path.len <= 100:
179
+ return Result[UstarPath, Error].success(value = UstarPath(
180
+ prefix_start = 0,
181
+ prefix_len = 0,
182
+ name_start = 0,
183
+ name_len = path.len
184
+ ))
185
+ if path.len > 255:
186
+ return Result[UstarPath, Error].failure(error = error_message("tar entry path exceeds ustar length limits"))
187
+
188
+ var index = path.len
189
+ while index > 0:
190
+ index -= 1
191
+ if path.byte_at(index) == slash_byte:
192
+ let prefix_len = index
193
+ let name_start = index + 1
194
+ let name_len = path.len - name_start
195
+ if prefix_len != 0 and prefix_len <= 155 and name_len <= 100:
196
+ return Result[UstarPath, Error].success(value = UstarPath(
197
+ prefix_start = 0,
198
+ prefix_len = prefix_len,
199
+ name_start = name_start,
200
+ name_len = name_len
201
+ ))
202
+
203
+ return Result[
204
+ UstarPath,
205
+ Error
206
+ ].failure(error = error_message("tar entry path cannot be represented in ustar format"))
207
+
208
+
209
+ function write_path_field(buffer: ptr[ubyte], offset: ptr_uint, value: str, start: ptr_uint, len: ptr_uint) -> void:
210
+ var index: ptr_uint = 0
211
+ while index < len:
212
+ unsafe:
213
+ read(buffer + offset + index) = value.byte_at(start + index)
214
+ index += 1
215
+
216
+
217
+ function write_octal_digits(buffer: ptr[ubyte], offset: ptr_uint, digits_len: ptr_uint, value: ptr_uint) -> bool:
218
+ var digits = zero[array[ubyte, 32]]
219
+ var count: ptr_uint = 0
220
+ var remaining = value
221
+
222
+ if remaining == 0:
223
+ digits[0] = 48
224
+ count = 1
225
+ else:
226
+ while remaining != 0:
227
+ if count >= 32:
228
+ return false
229
+
230
+ let digit = remaining % 8
231
+ digits[count] = ubyte<-(48z + digit)
232
+ remaining /= 8
233
+ count += 1
234
+
235
+ if count > digits_len:
236
+ return false
237
+
238
+ var index: ptr_uint = 0
239
+ while index < digits_len:
240
+ unsafe:
241
+ read(buffer + offset + index) = 48
242
+ index += 1
243
+
244
+ index = 0
245
+ var out_index = digits_len
246
+ while index < count:
247
+ out_index -= 1
248
+ unsafe:
249
+ read(buffer + offset + out_index) = digits[index]
250
+ index += 1
251
+
252
+ return true
253
+
254
+
255
+ function write_octal_field(buffer: ptr[ubyte], offset: ptr_uint, field_len: ptr_uint, value: ptr_uint) -> bool:
256
+ if field_len < 2:
257
+ return false
258
+
259
+ if not write_octal_digits(buffer, offset, field_len - 1, value):
260
+ return false
261
+
262
+ unsafe:
263
+ read(buffer + offset + field_len - 1) = zero_byte
264
+ return true
265
+
266
+
267
+ function write_checksum_field(buffer: ptr[ubyte], offset: ptr_uint, value: ptr_uint) -> bool:
268
+ if not write_octal_digits(buffer, offset, 6, value):
269
+ return false
270
+
271
+ unsafe:
272
+ read(buffer + offset + 6) = zero_byte
273
+ read(buffer + offset + 7) = space_byte
274
+ return true
275
+
276
+
277
+ function header_checksum(buffer: ptr[ubyte]) -> ptr_uint:
278
+ var sum: ptr_uint = 0
279
+ var index: ptr_uint = 0
280
+ while index < block_size:
281
+ unsafe:
282
+ sum += ptr_uint<-read(buffer + index)
283
+ index += 1
284
+
285
+ return sum
286
+
287
+
288
+ function append_header(
289
+ output: ref[vec.Vec[ubyte]],
290
+ archive_path: str,
291
+ kind: EntryKind,
292
+ mode: int,
293
+ size_bytes: ptr_uint
294
+ ) -> Result[bool, Error]:
295
+ let parts = split_ustar_path(archive_path)?
296
+ var header = zero[array[ubyte, 512]]
297
+ let header_ptr = ptr_of(header[0])
298
+
299
+ write_path_field(header_ptr, 0, archive_path, parts.name_start, parts.name_len)
300
+ if not write_octal_field(header_ptr, 100, 8, ptr_uint<-(mode & 4095)):
301
+ return Result[bool, Error].failure(error = error_message("tar mode exceeds field capacity"))
302
+ if not write_octal_field(header_ptr, 108, 8, 0):
303
+ return Result[bool, Error].failure(error = error_message("tar uid exceeds field capacity"))
304
+ if not write_octal_field(header_ptr, 116, 8, 0):
305
+ return Result[bool, Error].failure(error = error_message("tar gid exceeds field capacity"))
306
+
307
+ var stored_size: ptr_uint = 0
308
+ if kind == EntryKind.file:
309
+ stored_size = size_bytes
310
+
311
+ if not write_octal_field(header_ptr, 124, 12, stored_size):
312
+ return Result[bool, Error].failure(error = error_message("tar size exceeds field capacity"))
313
+ if not write_octal_field(header_ptr, 136, 12, 0):
314
+ return Result[bool, Error].failure(error = error_message("tar mtime exceeds field capacity"))
315
+
316
+ var checksum_index: ptr_uint = 148
317
+ while checksum_index < 156:
318
+ header[checksum_index] = space_byte
319
+ checksum_index += 1
320
+
321
+ if kind == EntryKind.directory:
322
+ header[156] = directory_type_flag
323
+ else:
324
+ header[156] = file_type_flag
325
+
326
+ header[257] = 117
327
+ header[258] = 115
328
+ header[259] = 116
329
+ header[260] = 97
330
+ header[261] = 114
331
+ header[263] = 48
332
+ header[264] = 48
333
+
334
+ if parts.prefix_len != 0:
335
+ write_path_field(header_ptr, 345, archive_path, parts.prefix_start, parts.prefix_len)
336
+
337
+ if not write_checksum_field(header_ptr, 148, header_checksum(header_ptr)):
338
+ return Result[bool, Error].failure(error = error_message("tar checksum exceeds field capacity"))
339
+
340
+ output.append_array(header)
341
+ return Result[bool, Error].success(value = true)
342
+
343
+
344
+ function append_file_entry(source_path: str, archive_path: str, output: ref[vec.Vec[ubyte]]) -> Result[bool, Error]:
345
+ match fs.metadata(source_path):
346
+ Result.failure as payload:
347
+ return Result[bool, Error].failure(error = take_fs_error(payload.error))
348
+ Result.success as metadata_payload:
349
+ let metadata = metadata_payload.value
350
+ match append_header(output, archive_path, EntryKind.file, metadata.mode, metadata.size):
351
+ Result.failure as payload:
352
+ return Result[bool, Error].failure(error = payload.error)
353
+ Result.success:
354
+ pass
355
+
356
+ match fs.read_bytes(source_path):
357
+ Result.failure as payload:
358
+ return Result[bool, Error].failure(error = take_fs_error(payload.error))
359
+ Result.success as payload:
360
+ var data = payload.value
361
+ defer data.release()
362
+ output.append_span(data.as_span())
363
+ append_padding(output, data.len)
364
+ return Result[bool, Error].success(value = true)
365
+
366
+
367
+ function append_directory_tree(
368
+ source_path: str,
369
+ archive_path: str,
370
+ include_hidden: bool,
371
+ output: ref[vec.Vec[ubyte]]
372
+ ) -> Result[bool, Error]:
373
+ if archive_path.len != 0:
374
+ match fs.metadata(source_path):
375
+ Result.failure as payload:
376
+ return Result[bool, Error].failure(error = take_fs_error(payload.error))
377
+ Result.success as payload:
378
+ let metadata = payload.value
379
+ match append_header(output, archive_path, EntryKind.directory, metadata.mode, 0):
380
+ Result.failure as header_payload:
381
+ return Result[bool, Error].failure(error = header_payload.error)
382
+ Result.success:
383
+ pass
384
+
385
+ match fs.list_entries(source_path):
386
+ Result.failure as payload:
387
+ return Result[bool, Error].failure(error = take_fs_error(payload.error))
388
+ Result.success as payload:
389
+ var entries = payload.value
390
+ defer entries.release()
391
+
392
+ match copy_sorted_entry_names(entries, include_hidden):
393
+ Result.failure as names_payload:
394
+ return Result[bool, Error].failure(error = names_payload.error)
395
+ Result.success as names_payload:
396
+ var names = names_payload.value
397
+ defer release_string_values(ref_of(names))
398
+
399
+ var index: ptr_uint = 0
400
+ while index < names.len():
401
+ let name_ptr = names.get(index) else:
402
+ return Result[bool, Error].failure(error = error_message("tar entry name missing"))
403
+
404
+ unsafe:
405
+ let child_name = read(name_ptr).as_str()
406
+ var child_source = path_ops.join(source_path, child_name)
407
+ var child_archive = string.String.create()
408
+ if archive_path.len == 0:
409
+ child_archive = string.String.from_str(child_name)
410
+ else:
411
+ child_archive = path_ops.join(archive_path, child_name)
412
+
413
+ if fs.is_directory(child_source.as_str()):
414
+ match append_directory_tree(
415
+ child_source.as_str(),
416
+ child_archive.as_str(),
417
+ include_hidden,
418
+ output
419
+ ):
420
+ Result.failure as child_payload:
421
+ child_source.release()
422
+ child_archive.release()
423
+ return Result[bool, Error].failure(error = child_payload.error)
424
+ Result.success:
425
+ pass
426
+ else if fs.is_file(child_source.as_str()):
427
+ match append_file_entry(child_source.as_str(), child_archive.as_str(), output):
428
+ Result.failure as child_payload:
429
+ child_source.release()
430
+ child_archive.release()
431
+ return Result[bool, Error].failure(error = child_payload.error)
432
+ Result.success:
433
+ pass
434
+
435
+ child_source.release()
436
+ child_archive.release()
437
+
438
+ index += 1
439
+
440
+ return Result[bool, Error].success(value = true)
441
+
442
+
443
+ function block_is_zero(input: span[ubyte], offset: ptr_uint) -> bool:
444
+ var index: ptr_uint = 0
445
+ while index < block_size:
446
+ unsafe:
447
+ if read(input.data + offset + index) != 0:
448
+ return false
449
+ index += 1
450
+
451
+ return true
452
+
453
+
454
+ function padded_size(size_bytes: ptr_uint) -> ptr_uint:
455
+ let remainder = size_bytes % block_size
456
+ if remainder == 0:
457
+ return size_bytes
458
+
459
+ return size_bytes + (block_size - remainder)
460
+
461
+
462
+ function parse_octal_field(input: span[ubyte], offset: ptr_uint, field_len: ptr_uint) -> Result[ptr_uint, Error]:
463
+ var index: ptr_uint = 0
464
+ while index < field_len:
465
+ unsafe:
466
+ let current = read(input.data + offset + index)
467
+ if current == zero_byte or current == space_byte:
468
+ index += 1
469
+ continue
470
+ break
471
+
472
+ var value: ptr_uint = 0
473
+ while index < field_len:
474
+ unsafe:
475
+ let current = read(input.data + offset + index)
476
+ if current == zero_byte or current == space_byte:
477
+ break
478
+ if current < 48 or current > 55:
479
+ return Result[
480
+ ptr_uint,
481
+ Error
482
+ ].failure(error = error_message("tar numeric field contains non-octal digits"))
483
+ let digit = ptr_uint<-(current - 48ub)
484
+ if value > (heap.ptr_uint_max - digit) / 8:
485
+ return Result[
486
+ ptr_uint,
487
+ Error
488
+ ].failure(error = error_message("tar numeric field exceeds ptr_uint range"))
489
+ value = value * 8 + digit
490
+
491
+ index += 1
492
+
493
+ return Result[ptr_uint, Error].success(value = value)
494
+
495
+
496
+ function read_text_field(input: span[ubyte], offset: ptr_uint, field_len: ptr_uint) -> Result[string.String, Error]:
497
+ var used: ptr_uint = 0
498
+ while used < field_len:
499
+ unsafe:
500
+ if read(input.data + offset + used) == zero_byte:
501
+ break
502
+ used += 1
503
+
504
+ if used == 0:
505
+ return Result[string.String, Error].success(value = string.String.create())
506
+
507
+ let copied = heap.must_alloc[ubyte](used)
508
+ unsafe:
509
+ heap.copy_bytes(copied, input.data + offset, used)
510
+
511
+ let owned = string.String(data = copied, len = used, capacity = used, owns_storage = true)
512
+ match text.utf8_byte_span_as_str(span[ubyte](data = copied, len = used)):
513
+ Option.some:
514
+ return Result[string.String, Error].success(value = owned)
515
+ Option.none:
516
+ var invalid = owned
517
+ invalid.release()
518
+ return Result[string.String, Error].failure(error = error_message("tar text field must be valid UTF-8"))
519
+
520
+
521
+ function parse_entry(input: span[ubyte], offset: ptr_uint) -> Result[ParsedEntry, Error]:
522
+ var name = read_text_field(input, offset + 0, 100)?
523
+ defer name.release()
524
+
525
+ var prefix = read_text_field(input, offset + 345, 155)?
526
+ defer prefix.release()
527
+
528
+ let name_text = name.as_str()
529
+ if name_text.len == 0:
530
+ return Result[ParsedEntry, Error].failure(error = error_message("tar entry missing name"))
531
+
532
+ var full_path = string.String.create()
533
+ if prefix.is_empty():
534
+ full_path = string.String.from_str(name_text)
535
+ else:
536
+ full_path = path_ops.join(prefix.as_str(), name_text)
537
+
538
+ match parse_octal_field(input, offset + 100, 8):
539
+ Result.failure as payload:
540
+ full_path.release()
541
+ return Result[ParsedEntry, Error].failure(error = payload.error)
542
+ Result.success as mode_payload:
543
+ match parse_octal_field(input, offset + 124, 12):
544
+ Result.failure as payload:
545
+ full_path.release()
546
+ return Result[ParsedEntry, Error].failure(error = payload.error)
547
+ Result.success as size_payload:
548
+ var kind = EntryKind.file
549
+ unsafe:
550
+ let type_flag = read(input.data + offset + 156)
551
+ if type_flag == directory_type_flag:
552
+ kind = EntryKind.directory
553
+ else if type_flag != file_type_flag and type_flag != zero_byte:
554
+ full_path.release()
555
+ return Result[
556
+ ParsedEntry,
557
+ Error
558
+ ].failure(error = error_message("tar entry type is not supported"))
559
+
560
+ let data_offset = offset + block_size
561
+ if size_payload.value > input.len - data_offset:
562
+ full_path.release()
563
+ return Result[
564
+ ParsedEntry,
565
+ Error
566
+ ].failure(error = error_message("tar entry payload exceeds archive bounds"))
567
+
568
+ return Result[ParsedEntry, Error].success(value = ParsedEntry(
569
+ path = full_path,
570
+ kind = kind,
571
+ mode = int<-mode_payload.value,
572
+ size = size_payload.value,
573
+ data_offset = data_offset
574
+ ))
575
+
576
+
577
+ function checked_destination_path(destination_root: str, relative_path: str) -> Result[string.String, Error]:
578
+ if relative_path.len == 0:
579
+ return Result[string.String, Error].failure(error = error_message("tar extract path cannot be empty"))
580
+ if path_ops.is_absolute(relative_path):
581
+ return Result[string.String, Error].failure(error = error_message("tar extract path must be relative"))
582
+
583
+ var destination_path = path_ops.join(destination_root, relative_path)
584
+ match path_ops.relative_path(destination_path.as_str(), destination_root):
585
+ Option.none:
586
+ destination_path.release()
587
+ return Result[
588
+ string.String,
589
+ Error
590
+ ].failure(error = error_message("tar extract path escapes destination root"))
591
+ Option.some as payload:
592
+ var normalized = payload.value
593
+ defer normalized.release()
594
+ let normalized_path = normalized.as_str()
595
+ let escapes_parent = normalized_path.len == 2 and normalized_path.byte_at(0) == 46 and normalized_path.byte_at(1) == 46
596
+ if escapes_parent or normalized_path.starts_with("../"):
597
+ destination_path.release()
598
+ return Result[
599
+ string.String,
600
+ Error
601
+ ].failure(error = error_message("tar extract path escapes destination root"))
602
+
603
+ return Result[string.String, Error].success(value = destination_path)
604
+
605
+
606
+ extending Error:
607
+ public editable function release() -> void:
608
+ this.message.release()
609
+
610
+
611
+ extending ParsedEntry:
612
+ public editable function release() -> void:
613
+ this.path.release()
614
+
615
+
616
+ public function archive_directory(
617
+ root_path: str,
618
+ archive_root_name: str,
619
+ include_hidden: bool
620
+ ) -> Result[bytes.Bytes, Error]:
621
+ if not fs.is_directory(root_path):
622
+ return Result[
623
+ bytes.Bytes,
624
+ Error
625
+ ].failure(error = error_message("tar.archive_directory requires a directory source"))
626
+ if archive_root_name.len != 0 and path_ops.is_absolute(archive_root_name):
627
+ return Result[bytes.Bytes, Error].failure(error = error_message("tar archive root name must be relative"))
628
+
629
+ var output = vec.Vec[ubyte].create()
630
+ match append_directory_tree(root_path, archive_root_name, include_hidden, ref_of(output)):
631
+ Result.failure as payload:
632
+ output.release()
633
+ return Result[bytes.Bytes, Error].failure(error = payload.error)
634
+ Result.success:
635
+ pass
636
+
637
+ append_end_blocks(ref_of(output))
638
+ return Result[bytes.Bytes, Error].success(value = take_vec_bytes(ref_of(output)))
639
+
640
+
641
+ public function archive_directory_gzip(
642
+ root_path: str,
643
+ archive_root_name: str,
644
+ include_hidden: bool
645
+ ) -> Result[bytes.Bytes, Error]:
646
+ match archive_directory(root_path, archive_root_name, include_hidden):
647
+ Result.failure as payload:
648
+ return Result[bytes.Bytes, Error].failure(error = payload.error)
649
+ Result.success as payload:
650
+ var archive = payload.value
651
+ defer archive.release()
652
+ match gzip.compress_bytes(archive.as_span()):
653
+ Result.failure as gzip_payload:
654
+ return Result[bytes.Bytes, Error].failure(error = take_gzip_error(gzip_payload.error))
655
+ Result.success as gzip_payload:
656
+ return Result[bytes.Bytes, Error].success(value = gzip_payload.value)
657
+
658
+
659
+ public function extract(archive: span[ubyte], destination_root: str) -> Result[bool, Error]:
660
+ if archive.len % block_size != 0:
661
+ return Result[bool, Error].failure(error = error_message("tar archive size must be a multiple of 512 bytes"))
662
+
663
+ fs.create_directories(destination_root).map_error(take_fs_error)?
664
+
665
+ var offset: ptr_uint = 0
666
+ while offset < archive.len:
667
+ if block_is_zero(archive, offset):
668
+ return Result[bool, Error].success(value = true)
669
+
670
+ var entry = parse_entry(archive, offset)?
671
+ defer entry.release()
672
+
673
+ var destination_path = checked_destination_path(destination_root, entry.path.as_str())?
674
+ defer destination_path.release()
675
+
676
+ if entry.kind == EntryKind.directory:
677
+ fs.create_directories(destination_path.as_str()).map_error(take_fs_error)?
678
+
679
+ fs.set_permissions(destination_path.as_str(), entry.mode).map_error(take_fs_error)?
680
+ else:
681
+ let parent = path_ops.dirname(destination_path.as_str())
682
+ fs.create_directories(parent).map_error(take_fs_error)?
683
+
684
+ let file_data = unsafe: span[ubyte](
685
+ data = archive.data + entry.data_offset,
686
+ len = entry.size
687
+ )
688
+ fs.write_bytes(destination_path.as_str(), file_data).map_error(take_fs_error)?
689
+
690
+ fs.set_permissions(destination_path.as_str(), entry.mode).map_error(take_fs_error)?
691
+
692
+ let entry_payload_size = padded_size(entry.size)
693
+ if offset > heap.ptr_uint_max - block_size - entry_payload_size:
694
+ return Result[bool, Error].failure(error = error_message("tar archive offset overflow"))
695
+
696
+ offset += block_size + entry_payload_size
697
+
698
+ return Result[bool, Error].success(value = true)
699
+
700
+
701
+ public function extract_gzip(archive: span[ubyte], destination_root: str) -> Result[bool, Error]:
702
+ var decoded = gzip.decompress_bytes(archive).map_error(take_gzip_error)?
703
+ defer decoded.release()
704
+ return extract(decoded.as_span(), destination_root)