xbintsc 0.5.20 → 0.5.21

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 (100) hide show
  1. package/AGENTS.md +5 -3
  2. package/dist/src/driver/compiler.js +1 -1
  3. package/dist/src/driver/compiler.js.map +1 -1
  4. package/dist/src/driver/runtime-objects.d.ts +1 -1
  5. package/dist/src/driver/runtime-objects.js +1 -0
  6. package/dist/src/driver/runtime-objects.js.map +1 -1
  7. package/dist/src/driver/version.d.ts +1 -1
  8. package/dist/src/driver/version.js +1 -1
  9. package/dist/src/extensions/node/fs/index.d.ts +4 -3
  10. package/dist/src/extensions/node/fs/index.js +12 -3
  11. package/dist/src/extensions/node/fs/index.js.map +1 -1
  12. package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
  13. package/dist/src/extensions/node/fs-promises/index.js +15 -6
  14. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  15. package/dist/tests/e2e/async-await.test.js +1 -1
  16. package/dist/tests/e2e/async-fs.test.d.ts +9 -0
  17. package/dist/tests/e2e/async-fs.test.js +119 -0
  18. package/dist/tests/e2e/async-fs.test.js.map +1 -0
  19. package/dist/tests/e2e/fetch.test.js +24 -0
  20. package/dist/tests/e2e/fetch.test.js.map +1 -1
  21. package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
  22. package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
  23. package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
  24. package/doc/ai/contributing.md +2 -1
  25. package/doc/ai/extensions.md +4 -3
  26. package/doc/ai/language-support.md +2 -1
  27. package/doc/ai/zh-CN/contributing.md +1 -1
  28. package/doc/ai/zh-CN/extensions.md +4 -3
  29. package/doc/ai/zh-CN/language-support.md +2 -1
  30. package/doc/gui/api.md +8 -0
  31. package/doc/gui/known-issues.md +10 -9
  32. package/doc/gui/media.md +32 -3
  33. package/doc/gui.md +24 -2
  34. package/doc/implemented/async.md +16 -3
  35. package/doc/implemented/fetch.md +38 -6
  36. package/doc/implemented/node/fs-promises.md +19 -8
  37. package/doc/implemented/node.md +1 -1
  38. package/doc/implemented.md +1 -1
  39. package/doc/unimplemented/deviations.md +1 -1
  40. package/doc/unimplemented/node/async.md +6 -3
  41. package/doc/unimplemented/node/fs-promises.md +10 -6
  42. package/doc/unimplemented/node/fs.md +7 -8
  43. package/doc/unimplemented/node.md +2 -2
  44. package/doc/unimplemented/toolchain.md +1 -1
  45. package/doc/zh-CN/gui/api.md +7 -0
  46. package/doc/zh-CN/gui/known-issues.md +8 -7
  47. package/doc/zh-CN/gui/media.md +26 -2
  48. package/doc/zh-CN/gui.md +18 -1
  49. package/doc/zh-CN/implemented/async.md +13 -2
  50. package/doc/zh-CN/implemented/fetch.md +23 -4
  51. package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
  52. package/doc/zh-CN/implemented/node.md +1 -1
  53. package/doc/zh-CN/implemented.md +1 -1
  54. package/doc/zh-CN/unimplemented/deviations.md +1 -1
  55. package/doc/zh-CN/unimplemented/node/async.md +6 -3
  56. package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
  57. package/doc/zh-CN/unimplemented/node/fs.md +2 -2
  58. package/doc/zh-CN/unimplemented/node.md +2 -2
  59. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  60. package/package.json +1 -1
  61. package/runtime/ext_gui/inline_svg.cpp +219 -0
  62. package/runtime/ext_gui/inline_svg.h +41 -0
  63. package/runtime/ext_gui/layout.cpp +18 -0
  64. package/runtime/ext_gui/layout.h +7 -0
  65. package/runtime/ext_gui/layout_flow.cpp +4 -3
  66. package/runtime/ext_gui/paint.cpp +14 -0
  67. package/runtime/ext_gui/paint.h +5 -1
  68. package/runtime/ext_gui/renderer_image.cpp +53 -25
  69. package/runtime/ext_gui/style_ua.cpp +3 -0
  70. package/runtime/ext_gui/svg.cpp +122 -39
  71. package/runtime/ext_gui/svg.h +14 -3
  72. package/runtime/ext_gui/window.cpp +28 -0
  73. package/runtime/ext_node/fs/async_api.c +314 -0
  74. package/runtime/ext_node/fs/async_complete.c +99 -0
  75. package/runtime/ext_node/fs/async_fh.c +206 -0
  76. package/runtime/ext_node/fs/async_job.c +81 -0
  77. package/runtime/ext_node/fs/async_job.h +132 -0
  78. package/runtime/ext_node/fs/async_worker.c +534 -0
  79. package/runtime/ext_node/fs/async_worker_dir.c +439 -0
  80. package/runtime/ext_node/fs/async_worker_dir.h +33 -0
  81. package/runtime/ext_node/fs/async_worker_fd.c +74 -0
  82. package/runtime/ext_node/fs/dir.c +60 -15
  83. package/runtime/ext_node/fs/fs_common.h +9 -0
  84. package/runtime/ext_node/fs/fs_ops.c +18 -29
  85. package/runtime/ext_node/fs/parts/builtins.inc +34 -0
  86. package/runtime/ext_node/fs/parts/platform.inc +66 -0
  87. package/runtime/ext_node/fs/promises.c +44 -234
  88. package/runtime/rt_internal.h +2 -0
  89. package/runtime/xt_async_io.c +333 -0
  90. package/runtime/xt_async_io.h +47 -0
  91. package/runtime/xt_fetch/async.inc +295 -0
  92. package/runtime/xt_fetch/request.inc +6 -183
  93. package/runtime/xt_fetch/socket.inc +5 -4
  94. package/runtime/xt_fetch.c +6 -4
  95. package/runtime/xt_loop.c +44 -3
  96. package/src/driver/compiler.ts +1 -1
  97. package/src/driver/runtime-objects.ts +1 -0
  98. package/src/driver/version.ts +1 -1
  99. package/src/extensions/node/fs/index.ts +12 -3
  100. package/src/extensions/node/fs-promises/index.ts +15 -6
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * The heavy lifting is the vendored NanoSVG library (`nanosvg.h` +
5
5
  * `nanosvgrast.h`, zlib licence, fetched verbatim). This file is only the glue:
6
- * path detection, a parsed-document cache and the scale-to-fit call. NanoSVG
7
- * emits straight (non-premultiplied) RGBA8, which is exactly the `Image`
8
- * layout the rest of the engine expects.
6
+ * path detection, parsed-document caches (per file and per inline markup) and
7
+ * the scale-to-fit call. NanoSVG emits straight (non-premultiplied) RGBA8,
8
+ * which is exactly the `Image` layout the rest of the engine expects.
9
9
  */
10
10
 
11
11
  #include "svg.h"
@@ -31,6 +31,7 @@
31
31
 
32
32
  #include <algorithm>
33
33
  #include <cctype>
34
+ #include <cstdint>
34
35
  #include <cstdio>
35
36
  #include <fstream>
36
37
  #include <string>
@@ -89,16 +90,13 @@ NSVGrasterizer *&rasterizer() {
89
90
  return value;
90
91
  }
91
92
 
92
- /** Parse `path` once and remember the result (including failures, so a missing
93
- * file is not retried every frame). */
94
- NSVGimage *parseCached(const std::string &path) {
95
- auto &cache = parsedCache();
96
- auto found = cache.find(path);
97
- if (found != cache.end()) return found->second;
98
- NSVGimage *image = nsvgParseFromFile(path.c_str(), "px", 96.0f);
99
- cache.emplace(path, image);
100
- return image;
101
- }
93
+ /** One parsed inline-SVG document in the markup cache. `stamp` is the last use
94
+ * (see `parseMarkup`); `image` is null when the markup failed to parse so a
95
+ * broken document is not retried on every layout. */
96
+ struct MarkupDocument {
97
+ NSVGimage *image = nullptr;
98
+ uint64_t stamp = 0;
99
+ };
102
100
 
103
101
  float intrinsicWidth(const NSVGimage *image) {
104
102
  return image->width > 0.0f ? image->width : kDefaultWidth;
@@ -108,33 +106,53 @@ float intrinsicHeight(const NSVGimage *image) {
108
106
  return image->height > 0.0f ? image->height : kDefaultHeight;
109
107
  }
110
108
 
111
- } // namespace
109
+ /** Inline SVG markup is authored in the document and can change at any time, so
110
+ * unlike the per-path file cache this one is bounded: a script that animates an
111
+ * inline `<svg>` attribute every frame would otherwise add one parse (and its
112
+ * NSVGimage tree) per frame forever. */
113
+ constexpr size_t kMaxMarkupDocuments = 256;
112
114
 
113
- bool xt_svg_is_svg(const std::string &path) {
114
- auto &cache = detectCache();
115
- auto found = cache.find(path);
116
- if (found != cache.end()) return found->second;
117
- bool result = hasSvgExtension(path) || prefixLooksLikeSvg(path);
118
- cache.emplace(path, result);
119
- return result;
115
+ std::unordered_map<std::string, MarkupDocument> &markupCache() {
116
+ static std::unordered_map<std::string, MarkupDocument> cache;
117
+ return cache;
120
118
  }
121
119
 
122
- bool xt_svg_size(const std::string &path, int *width, int *height) {
123
- NSVGimage *image = parseCached(path);
124
- if (image == nullptr) return false;
125
- float w = intrinsicWidth(image);
126
- float h = intrinsicHeight(image);
127
- if (w <= 0.0f || h <= 0.0f) return false;
128
- if (width != nullptr) *width = (int)(w + 0.5f);
129
- if (height != nullptr) *height = (int)(h + 0.5f);
130
- return true;
120
+ uint64_t nextStamp() {
121
+ static uint64_t counter = 0;
122
+ return ++counter;
131
123
  }
132
124
 
133
- bool xt_svg_render(const std::string &path, int width, int height, Image *out) {
134
- if (out == nullptr) return false;
135
- NSVGimage *image = parseCached(path);
136
- if (image == nullptr) return false;
125
+ /** Parse an inline SVG document once and remember the result. Evicts the
126
+ * least-recently-used entry when the table is full; nothing outside this module
127
+ * keeps an `NSVGimage*`, so a parse can always be dropped safely. */
128
+ NSVGimage *parseMarkup(const std::string &markup) {
129
+ auto &cache = markupCache();
130
+ uint64_t stamp = nextStamp();
131
+ auto found = cache.find(markup);
132
+ if (found != cache.end()) {
133
+ found->second.stamp = stamp;
134
+ return found->second.image;
135
+ }
136
+ if (cache.size() >= kMaxMarkupDocuments) {
137
+ auto oldest = cache.begin();
138
+ for (auto it = cache.begin(); it != cache.end(); ++it) {
139
+ if (it->second.stamp < oldest->second.stamp) oldest = it;
140
+ }
141
+ if (oldest->second.image != nullptr) nsvgDelete(oldest->second.image);
142
+ cache.erase(oldest);
143
+ }
144
+ /* NanoSVG's XML parser NUL-terminates tags in place, so it needs its own
145
+ * mutable copy of the document. */
146
+ std::string document = markup;
147
+ NSVGimage *image = nsvgParse(document.data(), "px", 96.0f);
148
+ cache.emplace(markup, MarkupDocument{image, stamp});
149
+ return image;
150
+ }
137
151
 
152
+ /** Rasterise a parsed document at `width` x `height` device pixels, at the
153
+ * larger axis scale so neither axis is under-sampled. Returns false when the
154
+ * size is unusable or the rasteriser cannot be created. */
155
+ bool rasterize(NSVGimage *image, int width, int height, Image *out) {
138
156
  float source_width = intrinsicWidth(image);
139
157
  float source_height = intrinsicHeight(image);
140
158
  int target_width = width > 0 ? width : (int)(source_width + 0.5f);
@@ -143,11 +161,6 @@ bool xt_svg_render(const std::string &path, int width, int height, Image *out) {
143
161
  target_width = std::min(target_width, kMaxDimension);
144
162
  target_height = std::min(target_height, kMaxDimension);
145
163
 
146
- /* Rasterise at the source aspect ratio, at the larger of the two axis scales
147
- * (so neither axis is under-sampled), then let the quad stretch the texture
148
- * to the box — exactly how a bitmap would behave under CSS `object-fit:
149
- * fill`. The common case (layout preserved the aspect ratio) makes this equal
150
- * to the requested size. */
151
164
  float scale = std::max((float)target_width / source_width, (float)target_height / source_height);
152
165
  scale = std::min(scale, (float)kMaxDimension / source_width);
153
166
  scale = std::min(scale, (float)kMaxDimension / source_height);
@@ -167,12 +180,82 @@ bool xt_svg_render(const std::string &path, int width, int height, Image *out) {
167
180
  return true;
168
181
  }
169
182
 
183
+ /** Parse `path` once and remember the result (including failures, so a missing
184
+ * file is not retried every frame). */
185
+ NSVGimage *parseCached(const std::string &path) {
186
+ auto &cache = parsedCache();
187
+ auto found = cache.find(path);
188
+ if (found != cache.end()) return found->second;
189
+ NSVGimage *image = nsvgParseFromFile(path.c_str(), "px", 96.0f);
190
+ cache.emplace(path, image);
191
+ return image;
192
+ }
193
+
194
+ } // namespace
195
+
196
+ bool xt_svg_is_svg(const std::string &path) {
197
+ auto &cache = detectCache();
198
+ auto found = cache.find(path);
199
+ if (found != cache.end()) return found->second;
200
+ bool result = hasSvgExtension(path) || prefixLooksLikeSvg(path);
201
+ cache.emplace(path, result);
202
+ return result;
203
+ }
204
+
205
+ bool xt_svg_size(const std::string &path, int *width, int *height) {
206
+ NSVGimage *image = parseCached(path);
207
+ if (image == nullptr) return false;
208
+ float w = intrinsicWidth(image);
209
+ float h = intrinsicHeight(image);
210
+ if (w <= 0.0f || h <= 0.0f) return false;
211
+ if (width != nullptr) *width = (int)(w + 0.5f);
212
+ if (height != nullptr) *height = (int)(h + 0.5f);
213
+ return true;
214
+ }
215
+
216
+ bool xt_svg_render(const std::string &path, int width, int height, Image *out) {
217
+ if (out == nullptr) return false;
218
+ NSVGimage *image = parseCached(path);
219
+ if (image == nullptr) return false;
220
+ return rasterize(image, width, height, out);
221
+ }
222
+
223
+ bool xt_svg_markup_size(const std::string &markup, int *width, int *height) {
224
+ NSVGimage *image = parseMarkup(markup);
225
+ if (image == nullptr) return false;
226
+ float w = intrinsicWidth(image);
227
+ float h = intrinsicHeight(image);
228
+ if (w <= 0.0f || h <= 0.0f) return false;
229
+ if (width != nullptr) *width = (int)(w + 0.5f);
230
+ if (height != nullptr) *height = (int)(h + 0.5f);
231
+ return true;
232
+ }
233
+
234
+ bool xt_svg_markup_render(const std::string &markup, int width, int height, Image *out) {
235
+ if (out == nullptr) return false;
236
+ NSVGimage *image = parseMarkup(markup);
237
+ if (image == nullptr) return false;
238
+ return rasterize(image, width, height, out);
239
+ }
240
+
241
+ int xt_svg_markup_shapes(const std::string &markup) {
242
+ NSVGimage *image = parseMarkup(markup);
243
+ if (image == nullptr) return 0;
244
+ int count = 0;
245
+ for (const NSVGshape *shape = image->shapes; shape != nullptr; shape = shape->next) count++;
246
+ return count;
247
+ }
248
+
170
249
  void xt_svg_shutdown() {
171
250
  for (auto &entry : parsedCache()) {
172
251
  if (entry.second != nullptr) nsvgDelete(entry.second);
173
252
  }
174
253
  parsedCache().clear();
175
254
  detectCache().clear();
255
+ for (auto &entry : markupCache()) {
256
+ if (entry.second.image != nullptr) nsvgDelete(entry.second.image);
257
+ }
258
+ markupCache().clear();
176
259
  if (rasterizer() != nullptr) {
177
260
  nsvgDeleteRasterizer(rasterizer());
178
261
  rasterizer() = nullptr;
@@ -9,9 +9,10 @@
9
9
  * (`nanosvg.h` + `nanosvgrast.h`) — the same class of low-level, third-party
10
10
  * image library `stb_image` is (see doc/gui.md).
11
11
  *
12
- * Only *files* are handled: `<img src="logo.svg">`. Inline `<svg>` trees in a
13
- * document are still ordinary unknown elements to the HTML parser and generate
14
- * no boxes.
12
+ * Both SVG *files* (`<img src="logo.svg">`) and inline `<svg>` trees go through
13
+ * this module: an inline element's DOM subtree is serialized into a document by
14
+ * `inline_svg.{h,cpp}` and parsed with the markup entry points below, so both
15
+ * kinds produce the same RGBA8 `Image` at the drawn device size.
15
16
  */
16
17
  #ifndef XT_GUI_SVG_H
17
18
  #define XT_GUI_SVG_H
@@ -30,6 +31,16 @@ bool xt_svg_is_svg(const std::string &path);
30
31
  * false when the file is missing or cannot be parsed. Cached per path. */
31
32
  bool xt_svg_size(const std::string &path, int *width, int *height);
32
33
 
34
+ /** The same two operations for an SVG *document string* rather than a file:
35
+ * the markup of an inline `<svg>` subtree (see `inline_svg.h`). The parse is
36
+ * cached by content (least-recently-used, so an animated document cannot grow
37
+ * the cache without bound); each rasterisation is fresh. */
38
+ bool xt_svg_markup_size(const std::string &markup, int *width, int *height);
39
+ bool xt_svg_markup_render(const std::string &markup, int width, int height, Image *out);
40
+
41
+ /** Number of shapes NanoSVG parsed out of `markup` (diagnostics/tests). */
42
+ int xt_svg_markup_shapes(const std::string &markup);
43
+
33
44
  /** Rasterise `path` to RGBA8. `width`/`height` are the requested pixel size;
34
45
  * when either is `<= 0` the intrinsic SVG size is used. The viewBox is scaled
35
46
  * uniformly at the larger of the two axis scales (preserving the SVG's aspect
@@ -12,6 +12,7 @@
12
12
 
13
13
  #include <algorithm>
14
14
 
15
+ #include "inline_svg.h"
15
16
  #include "paint.h"
16
17
  #include "text.h"
17
18
 
@@ -216,6 +217,32 @@ static xt_value win_paint_count(xt_value self, xt_value env, int32_t argc, xt_va
216
217
  return xt_number((double)list.rects.size());
217
218
  }
218
219
 
220
+ /** Diagnostic/test hook: the serialized SVG document of the first `<svg>`
221
+ * element matching `selector`, plus the intrinsic size and shape count the
222
+ * NanoSVG parse yields. Returns `undefined` when there is no such element. */
223
+ static xt_value win_inline_svg(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
224
+ (void)env;
225
+ XtGuiWindow *win = xt_gui_window_from_this(self);
226
+ if (win == nullptr || win->document == nullptr || argc < 1) return XT_UNDEFINED;
227
+ xt_gui_flush_dom(win);
228
+ xt_value selectorValue = xt_to_string(xt_arg(argc, argv, 0));
229
+ const char *selector = xt_string_data(selectorValue);
230
+ if (selector == nullptr) return XT_UNDEFINED;
231
+ const xtgui::Node *node = win->document->querySelector(selector);
232
+ if (!xtgui::xt_inline_svg_is_root(node)) return XT_UNDEFINED;
233
+ std::string markup = xtgui::xt_inline_svg_serialize(node);
234
+ int width = 0;
235
+ int height = 0;
236
+ xtgui::xt_svg_markup_size(markup, &width, &height);
237
+ xt_value object = xt_object_new();
238
+ xt_object_set(object, xt_string_from_cstr("markup"), xt_string_from_cstr(markup.c_str()));
239
+ xt_object_set(object, xt_string_from_cstr("width"), xt_number(width));
240
+ xt_object_set(object, xt_string_from_cstr("height"), xt_number(height));
241
+ xt_object_set(object, xt_string_from_cstr("shapes"),
242
+ xt_number(xtgui::xt_svg_markup_shapes(markup)));
243
+ return object;
244
+ }
245
+
219
246
  /** Diagnostic/test hook: advance the CSS transition clock by `ms`. */
220
247
  static xt_value win_advance(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
221
248
  (void)env;
@@ -527,6 +554,7 @@ xt_value xt_gui_window_proto(void) {
527
554
  define_method(proto, "setBackground", (void *)win_set_background);
528
555
  define_method(proto, "paintList", (void *)win_paint_list);
529
556
  define_method(proto, "paintCount", (void *)win_paint_count);
557
+ define_method(proto, "inlineSvg", (void *)win_inline_svg);
530
558
  define_method(proto, "advance", (void *)win_advance);
531
559
  define_method(proto, "measureText", (void *)win_measure_text);
532
560
  define_method(proto, "fontMetrics", (void *)win_font_metrics);
@@ -0,0 +1,314 @@
1
+ /*
2
+ * Main-thread argument marshalling for asynchronous `fs/promises`.
3
+ *
4
+ * Each export copies whatever it needs from the JavaScript values (paths,
5
+ * encodings, buffers, options) into a job, then submits it. The blocking half
6
+ * runs on a worker; the promise is settled later on the reactor thread. This is
7
+ * also where the non-I/O argument parsing matches the synchronous `fs` module.
8
+ */
9
+
10
+ #include "async_job.h"
11
+
12
+ #include <errno.h>
13
+
14
+ static int xt_fs_option_bool(xt_value options, const char *key, int fallback) {
15
+ if (!XT_IS_OBJECT(options)) return fallback;
16
+ xt_value value = xt_object_get_cstr(options, key);
17
+ if (XT_IS_UNDEFINED(value) || XT_IS_NULL(value)) return fallback;
18
+ return xt_truthy(value);
19
+ }
20
+
21
+ static int xt_fs_option_recursive(xt_value options) {
22
+ if (!xt_truthy(options)) return 0;
23
+ if (XT_IS_OBJECT(options)) return xt_truthy(xt_object_get_cstr(options, "recursive"));
24
+ return 1;
25
+ }
26
+
27
+ /* -- file contents --------------------------------------------------------- */
28
+
29
+ xt_value xt_node_p_read_file(int32_t argc, xt_value *argv) {
30
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
31
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_READ_FILE);
32
+ job->path = xt_fs_take_string(argv[0]);
33
+ job->text = xt_fs_copy_cstr(argc > 1 ? xt_fs_encoding(argv[1]) : NULL);
34
+ return xt_fs_job_submit(job);
35
+ }
36
+
37
+ static xt_value xt_fs_write(int32_t argc, xt_value *argv, int append) {
38
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
39
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_WRITE_FILE);
40
+ job->path = xt_fs_take_string(argv[0]);
41
+ job->flags = append;
42
+ if (xt_node_is_buffer(argv[1])) {
43
+ job->data = xt_node_buffer_bytes(argv[1], &job->data_length);
44
+ } else {
45
+ xt_value text = xt_to_string(argv[1]);
46
+ const char *data = xt_string_data(text);
47
+ int32_t length = xt_string_length_value(text);
48
+ if (data && length >= 0) {
49
+ const char *encoding = argc > 2 ? xt_fs_encoding(argv[2]) : NULL;
50
+ job->data = xt_fs_decode_text(data, (size_t)length, encoding, &job->data_length);
51
+ }
52
+ }
53
+ return xt_fs_job_submit(job);
54
+ }
55
+
56
+ xt_value xt_node_p_write_file(int32_t argc, xt_value *argv) { return xt_fs_write(argc, argv, 0); }
57
+
58
+ xt_value xt_node_p_append_file(int32_t argc, xt_value *argv) { return xt_fs_write(argc, argv, 1); }
59
+
60
+ /* -- directories ----------------------------------------------------------- */
61
+
62
+ xt_value xt_node_p_mkdir(int32_t argc, xt_value *argv) {
63
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
64
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_MKDIR);
65
+ job->path = xt_fs_take_string(argv[0]);
66
+ job->mode = 0777;
67
+ xt_value options = argc > 1 ? argv[1] : XT_UNDEFINED;
68
+ if (XT_IS_OBJECT(options) && !XT_IS_UNDEFINED(xt_object_get_cstr(options, "mode"))) {
69
+ job->mode = (int)xt_to_number(xt_object_get_cstr(options, "mode"));
70
+ }
71
+ job->recursive = argc > 1 && xt_fs_option_recursive(options);
72
+ return xt_fs_job_submit(job);
73
+ }
74
+
75
+ xt_value xt_node_p_readdir(int32_t argc, xt_value *argv) {
76
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
77
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_READDIR);
78
+ job->path = xt_fs_take_string(argv[0]);
79
+ xt_value options = argc > 1 ? argv[1] : XT_UNDEFINED;
80
+ job->with_types = xt_fs_option_bool(options, "withFileTypes", 0);
81
+ job->recursive = xt_fs_option_bool(options, "recursive", 0);
82
+ return xt_fs_job_submit(job);
83
+ }
84
+
85
+ xt_value xt_node_p_rm(int32_t argc, xt_value *argv) {
86
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
87
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_RM);
88
+ job->path = xt_fs_take_string(argv[0]);
89
+ job->recursive = argc > 1 && xt_fs_option_recursive(argv[1]);
90
+ job->force = xt_fs_option_bool(argc > 1 ? argv[1] : XT_UNDEFINED, "force", 0);
91
+ return xt_fs_job_submit(job);
92
+ }
93
+
94
+ xt_value xt_node_p_unlink(int32_t argc, xt_value *argv) {
95
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
96
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_UNLINK);
97
+ job->path = xt_fs_take_string(argv[0]);
98
+ return xt_fs_job_submit(job);
99
+ }
100
+
101
+ xt_value xt_node_p_rmdir(int32_t argc, xt_value *argv) {
102
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
103
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_RMDIR);
104
+ job->path = xt_fs_take_string(argv[0]);
105
+ return xt_fs_job_submit(job);
106
+ }
107
+
108
+ xt_value xt_node_p_rename(int32_t argc, xt_value *argv) {
109
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
110
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_RENAME);
111
+ job->path = xt_fs_take_string(argv[0]);
112
+ job->path2 = xt_fs_take_string(argv[1]);
113
+ return xt_fs_job_submit(job);
114
+ }
115
+
116
+ xt_value xt_node_p_copy_file(int32_t argc, xt_value *argv) {
117
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
118
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_COPY_FILE);
119
+ job->path = xt_fs_take_string(argv[0]);
120
+ job->path2 = xt_fs_take_string(argv[1]);
121
+ job->flags = argc > 2 ? (int)xt_to_number(argv[2]) : 0;
122
+ return xt_fs_job_submit(job);
123
+ }
124
+
125
+ xt_value xt_node_p_cp(int32_t argc, xt_value *argv) {
126
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
127
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_CP);
128
+ job->path = xt_fs_take_string(argv[0]);
129
+ job->path2 = xt_fs_take_string(argv[1]);
130
+ xt_value options = argc > 2 ? argv[2] : XT_UNDEFINED;
131
+ job->recursive = xt_fs_option_bool(options, "recursive", 0);
132
+ job->force = xt_fs_option_bool(options, "force", 1);
133
+ job->error_on_exist = xt_fs_option_bool(options, "errorOnExist", 0);
134
+ job->dereference = xt_fs_option_bool(options, "dereference", 0);
135
+ job->preserve = xt_fs_option_bool(options, "preserveTimestamps", 0);
136
+ return xt_fs_job_submit(job);
137
+ }
138
+
139
+ xt_value xt_node_p_realpath(int32_t argc, xt_value *argv) {
140
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
141
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_REALPATH);
142
+ job->path = xt_fs_take_string(argv[0]);
143
+ return xt_fs_job_submit(job);
144
+ }
145
+
146
+ /* -- metadata -------------------------------------------------------------- */
147
+
148
+ static xt_value xt_fs_stat_job(xt_fs_job_kind kind, int32_t argc, xt_value *argv) {
149
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
150
+ xt_fs_job *job = xt_fs_job_new(kind);
151
+ job->path = xt_fs_take_string(argv[0]);
152
+ return xt_fs_job_submit(job);
153
+ }
154
+
155
+ xt_value xt_node_p_stat(int32_t argc, xt_value *argv) {
156
+ return xt_fs_stat_job(XT_FS_JOB_STAT, argc, argv);
157
+ }
158
+
159
+ xt_value xt_node_p_lstat(int32_t argc, xt_value *argv) {
160
+ return xt_fs_stat_job(XT_FS_JOB_LSTAT, argc, argv);
161
+ }
162
+
163
+ xt_value xt_node_p_statfs(int32_t argc, xt_value *argv) {
164
+ return xt_fs_stat_job(XT_FS_JOB_STATFS, argc, argv);
165
+ }
166
+
167
+ xt_value xt_node_p_access(int32_t argc, xt_value *argv) {
168
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
169
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_ACCESS);
170
+ job->path = xt_fs_take_string(argv[0]);
171
+ job->mode = argc > 1 ? (int)xt_to_number(argv[1]) : F_OK;
172
+ return xt_fs_job_submit(job);
173
+ }
174
+
175
+ xt_value xt_node_p_open(int32_t argc, xt_value *argv) {
176
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
177
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_OPEN);
178
+ job->path = xt_fs_take_string(argv[0]);
179
+ job->flags = argc > 1 ? xt_fs_parse_flags(argv[1]) : (XT_FS_RDONLY | XT_FS_BINARY_FLAG);
180
+ job->mode = argc > 2 ? (int)xt_to_number(argv[2]) : 0666;
181
+ return xt_fs_job_submit(job);
182
+ }
183
+
184
+ xt_value xt_node_p_chmod(int32_t argc, xt_value *argv) {
185
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
186
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_CHMOD);
187
+ job->path = xt_fs_take_string(argv[0]);
188
+ job->mode = (int)xt_to_number(argv[1]);
189
+ return xt_fs_job_submit(job);
190
+ }
191
+
192
+ xt_value xt_node_p_lchmod(int32_t argc, xt_value *argv) {
193
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
194
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_LCHMOD);
195
+ job->path = xt_fs_take_string(argv[0]);
196
+ job->mode = (int)xt_to_number(argv[1]);
197
+ return xt_fs_job_submit(job);
198
+ }
199
+
200
+ static xt_value xt_fs_chown_job(xt_fs_job_kind kind, int32_t argc, xt_value *argv) {
201
+ if (argc < 3) return xt_promise_resolve(XT_UNDEFINED);
202
+ xt_fs_job *job = xt_fs_job_new(kind);
203
+ job->path = xt_fs_take_string(argv[0]);
204
+ job->uid = (int)xt_to_number(argv[1]);
205
+ job->gid = (int)xt_to_number(argv[2]);
206
+ return xt_fs_job_submit(job);
207
+ }
208
+
209
+ xt_value xt_node_p_chown(int32_t argc, xt_value *argv) {
210
+ return xt_fs_chown_job(XT_FS_JOB_CHOWN, argc, argv);
211
+ }
212
+
213
+ xt_value xt_node_p_lchown(int32_t argc, xt_value *argv) {
214
+ return xt_fs_chown_job(XT_FS_JOB_LCHOWN, argc, argv);
215
+ }
216
+
217
+ xt_value xt_node_p_truncate(int32_t argc, xt_value *argv) {
218
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
219
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_TRUNCATE);
220
+ job->path = xt_fs_take_string(argv[0]);
221
+ job->length = argc > 1 ? (long long)xt_to_number(argv[1]) : 0;
222
+ return xt_fs_job_submit(job);
223
+ }
224
+
225
+ static xt_value xt_fs_utimes_job(xt_fs_job_kind kind, int32_t argc, xt_value *argv) {
226
+ if (argc < 3) return xt_promise_resolve(XT_UNDEFINED);
227
+ xt_fs_job *job = xt_fs_job_new(kind);
228
+ job->path = xt_fs_take_string(argv[0]);
229
+ job->atime = xt_fs_time_seconds(argv[1]);
230
+ job->mtime = xt_fs_time_seconds(argv[2]);
231
+ return xt_fs_job_submit(job);
232
+ }
233
+
234
+ xt_value xt_node_p_utimes(int32_t argc, xt_value *argv) {
235
+ return xt_fs_utimes_job(XT_FS_JOB_UTIMES, argc, argv);
236
+ }
237
+
238
+ xt_value xt_node_p_lutimes(int32_t argc, xt_value *argv) {
239
+ return xt_fs_utimes_job(XT_FS_JOB_LUTIMES, argc, argv);
240
+ }
241
+
242
+ /* -- links and temporary directories --------------------------------------- */
243
+
244
+ xt_value xt_node_p_link(int32_t argc, xt_value *argv) {
245
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
246
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_LINK);
247
+ job->path = xt_fs_take_string(argv[0]);
248
+ job->path2 = xt_fs_take_string(argv[1]);
249
+ return xt_fs_job_submit(job);
250
+ }
251
+
252
+ xt_value xt_node_p_symlink(int32_t argc, xt_value *argv) {
253
+ if (argc < 2) return xt_promise_resolve(XT_UNDEFINED);
254
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_SYMLINK);
255
+ job->path = xt_fs_take_string(argv[0]);
256
+ job->path2 = xt_fs_take_string(argv[1]);
257
+ job->text = argc > 2 ? xt_fs_take_string(argv[2]) : NULL;
258
+ return xt_fs_job_submit(job);
259
+ }
260
+
261
+ xt_value xt_node_p_readlink(int32_t argc, xt_value *argv) {
262
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
263
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_READLINK);
264
+ job->path = xt_fs_take_string(argv[0]);
265
+ return xt_fs_job_submit(job);
266
+ }
267
+
268
+ xt_value xt_node_p_mkdtemp(int32_t argc, xt_value *argv) {
269
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
270
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_MKDTEMP);
271
+ job->path = xt_fs_take_string(argv[0]);
272
+ return xt_fs_job_submit(job);
273
+ }
274
+
275
+ /* -- opendir / glob / watch ------------------------------------------------ */
276
+
277
+ xt_value xt_node_p_opendir(int32_t argc, xt_value *argv) {
278
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
279
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_OPENDIR);
280
+ job->path = xt_fs_take_string(argv[0]);
281
+ return xt_fs_job_submit(job);
282
+ }
283
+
284
+ xt_value xt_node_p_glob(int32_t argc, xt_value *argv) {
285
+ if (argc < 1) return xt_promise_resolve(XT_UNDEFINED);
286
+ xt_fs_job *job = xt_fs_job_new(XT_FS_JOB_GLOB);
287
+ xt_value pattern = argv[0];
288
+ if (XT_IS_ARRAY(pattern)) {
289
+ int32_t count = (int32_t)xt_to_number(xt_array_length(pattern));
290
+ if (count > 0) {
291
+ job->patterns = (char **)calloc((size_t)count, sizeof(char *));
292
+ if (!job->patterns) abort();
293
+ for (int32_t i = 0; i < count; i++) {
294
+ xt_value item = xt_array_get(pattern, xt_number((double)i));
295
+ if (XT_IS_STRING(item)) {
296
+ job->patterns[job->pattern_count++] = xt_fs_take_string(item);
297
+ }
298
+ }
299
+ }
300
+ } else {
301
+ job->text = xt_fs_take_string(pattern);
302
+ }
303
+ xt_value options = argc > 1 ? argv[1] : XT_UNDEFINED;
304
+ job->with_types = xt_fs_option_bool(options, "withFileTypes", 0);
305
+ xt_value cwd = XT_IS_OBJECT(options) ? xt_object_get_cstr(options, "cwd") : XT_UNDEFINED;
306
+ if (XT_IS_STRING(cwd)) job->path2 = xt_fs_take_string(cwd);
307
+ return xt_fs_job_submit(job);
308
+ }
309
+
310
+ /* There is no real file-watching backend yet; keep the synchronous inert
311
+ * watcher behind an already-settled promise. */
312
+ xt_value xt_node_p_watch(int32_t argc, xt_value *argv) {
313
+ return xt_fs_promisify(xt_node_watch, argc, argv);
314
+ }