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.
- package/AGENTS.md +5 -3
- package/dist/src/driver/compiler.js +1 -1
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/runtime-objects.d.ts +1 -1
- package/dist/src/driver/runtime-objects.js +1 -0
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/node/fs/index.d.ts +4 -3
- package/dist/src/extensions/node/fs/index.js +12 -3
- package/dist/src/extensions/node/fs/index.js.map +1 -1
- package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
- package/dist/src/extensions/node/fs-promises/index.js +15 -6
- package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
- package/dist/tests/e2e/async-await.test.js +1 -1
- package/dist/tests/e2e/async-fs.test.d.ts +9 -0
- package/dist/tests/e2e/async-fs.test.js +119 -0
- package/dist/tests/e2e/async-fs.test.js.map +1 -0
- package/dist/tests/e2e/fetch.test.js +24 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -1
- package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
- package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
- package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
- package/doc/ai/contributing.md +2 -1
- package/doc/ai/extensions.md +4 -3
- package/doc/ai/language-support.md +2 -1
- package/doc/ai/zh-CN/contributing.md +1 -1
- package/doc/ai/zh-CN/extensions.md +4 -3
- package/doc/ai/zh-CN/language-support.md +2 -1
- package/doc/gui/api.md +8 -0
- package/doc/gui/known-issues.md +10 -9
- package/doc/gui/media.md +32 -3
- package/doc/gui.md +24 -2
- package/doc/implemented/async.md +16 -3
- package/doc/implemented/fetch.md +38 -6
- package/doc/implemented/node/fs-promises.md +19 -8
- package/doc/implemented/node.md +1 -1
- package/doc/implemented.md +1 -1
- package/doc/unimplemented/deviations.md +1 -1
- package/doc/unimplemented/node/async.md +6 -3
- package/doc/unimplemented/node/fs-promises.md +10 -6
- package/doc/unimplemented/node/fs.md +7 -8
- package/doc/unimplemented/node.md +2 -2
- package/doc/unimplemented/toolchain.md +1 -1
- package/doc/zh-CN/gui/api.md +7 -0
- package/doc/zh-CN/gui/known-issues.md +8 -7
- package/doc/zh-CN/gui/media.md +26 -2
- package/doc/zh-CN/gui.md +18 -1
- package/doc/zh-CN/implemented/async.md +13 -2
- package/doc/zh-CN/implemented/fetch.md +23 -4
- package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
- package/doc/zh-CN/implemented/node.md +1 -1
- package/doc/zh-CN/implemented.md +1 -1
- package/doc/zh-CN/unimplemented/deviations.md +1 -1
- package/doc/zh-CN/unimplemented/node/async.md +6 -3
- package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
- package/doc/zh-CN/unimplemented/node/fs.md +2 -2
- package/doc/zh-CN/unimplemented/node.md +2 -2
- package/doc/zh-CN/unimplemented/toolchain.md +1 -1
- package/package.json +1 -1
- package/runtime/ext_gui/inline_svg.cpp +219 -0
- package/runtime/ext_gui/inline_svg.h +41 -0
- package/runtime/ext_gui/layout.cpp +18 -0
- package/runtime/ext_gui/layout.h +7 -0
- package/runtime/ext_gui/layout_flow.cpp +4 -3
- package/runtime/ext_gui/paint.cpp +14 -0
- package/runtime/ext_gui/paint.h +5 -1
- package/runtime/ext_gui/renderer_image.cpp +53 -25
- package/runtime/ext_gui/style_ua.cpp +3 -0
- package/runtime/ext_gui/svg.cpp +122 -39
- package/runtime/ext_gui/svg.h +14 -3
- package/runtime/ext_gui/window.cpp +28 -0
- package/runtime/ext_node/fs/async_api.c +314 -0
- package/runtime/ext_node/fs/async_complete.c +99 -0
- package/runtime/ext_node/fs/async_fh.c +206 -0
- package/runtime/ext_node/fs/async_job.c +81 -0
- package/runtime/ext_node/fs/async_job.h +132 -0
- package/runtime/ext_node/fs/async_worker.c +534 -0
- package/runtime/ext_node/fs/async_worker_dir.c +439 -0
- package/runtime/ext_node/fs/async_worker_dir.h +33 -0
- package/runtime/ext_node/fs/async_worker_fd.c +74 -0
- package/runtime/ext_node/fs/dir.c +60 -15
- package/runtime/ext_node/fs/fs_common.h +9 -0
- package/runtime/ext_node/fs/fs_ops.c +18 -29
- package/runtime/ext_node/fs/parts/builtins.inc +34 -0
- package/runtime/ext_node/fs/parts/platform.inc +66 -0
- package/runtime/ext_node/fs/promises.c +44 -234
- package/runtime/rt_internal.h +2 -0
- package/runtime/xt_async_io.c +333 -0
- package/runtime/xt_async_io.h +47 -0
- package/runtime/xt_fetch/async.inc +295 -0
- package/runtime/xt_fetch/request.inc +6 -183
- package/runtime/xt_fetch/socket.inc +5 -4
- package/runtime/xt_fetch.c +6 -4
- package/runtime/xt_loop.c +44 -3
- package/src/driver/compiler.ts +1 -1
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/node/fs/index.ts +12 -3
- package/src/extensions/node/fs-promises/index.ts +15 -6
package/runtime/ext_gui/svg.cpp
CHANGED
|
@@ -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,
|
|
7
|
-
* emits straight (non-premultiplied) RGBA8,
|
|
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
|
-
/**
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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;
|
package/runtime/ext_gui/svg.h
CHANGED
|
@@ -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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
+
}
|