Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add std.writeFile #932

Merged
merged 1 commit into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/docs/stdlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,18 @@ encoding. Return `null` in case of I/O error.

If `options.binary` is set to `true` a `Uint8Array` is returned instead.

### `writeFile(filename, data)`

Create the file `filename` and write `data` into it.

`data` can be a string, a typed array or an `ArrayBuffer`. `undefined` is
treated as the empty string.

When `data` is a string, the file is opened in text mode; otherwise it is
opened in binary mode. (This distinction is only relevant on Windows.)

Comment on lines +365 to +367
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might add that the string is encoded in UTF-8 on all systems, whereas the data from typed arrays or ArrayBuffer objects is simply written as untranslated binary data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I'm not entirely sure that's correct. It uses fwrite and on Windows that's code page-dependent, AFAIK.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I'm not entirely sure that's correct. It uses fwrite and on Windows that's code page-dependent, AFAIK.

Indeed fwrite in Windows can do this, so if the stream is configured for code page or worse Unicode conversion, fwrite would unlikely produce gibberish output, aka Mojibake.

Here are the relevant paragraphs in the Microsoft help page:

The fwrite function writes up to count items, of size length each, from buffer to the output stream. The file pointer associated with stream (if there's one) is incremented by the number of bytes fwrite writes. If stream is opened in text mode, each line feed is replaced with a carriage return-line feed pair. The replacement has no effect on the return value.

When stream is opened in Unicode translation mode — for example, if stream is opened by calling fopen and using a mode parameter that includes ccs=UNICODE, ccs=UTF-16LE, or ccs=UTF-8, or if the mode is changed to a Unicode translation mode by using _setmode and a mode parameter that includes _O_WTEXT, _O_U16TEXT, or _O_U8TEXTbuffer is interpreted as a pointer to an array of wchar_t that contains UTF-16 data. An attempt to write an odd number of bytes in this mode causes a parameter validation error.

You open the file in default mode (text mode would use "wt") so the stream translation mode will be the default translation mode defined by the global variable _fmode which could be _O_TEXT and trigger LF to CR/LF translation or _O_BINARY which would leave the output unchanged.

Unless I am missing some more subtle side effects, the data written should just undergo line ending translation.

My suggestion is correct for unix-like systems and somewhat helpful, albeit UTF-8 is probably expected on these systems, and a more explicit caveat for Windows could help programmers who set up the standard streams stdout or stderr to perform code page or Unicode translation.

Throws an exception if the file cannot be created or written to.

### `open(filename, flags, errorObj = undefined)`

Open a file (wrapper to the libc `fopen()`). Return the FILE
Expand Down
63 changes: 63 additions & 0 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,68 @@ static JSValue js_std_loadFile(JSContext *ctx, JSValue this_val,
return ret;
}

static JSValue js_std_writeFile(JSContext *ctx, JSValue this_val,
int argc, JSValue *argv)
{
const char *filename;
const char *mode;
const void *buf;
size_t len, n;
JSValue data, val, ret;
bool release, unref;
FILE *fp;

ret = JS_EXCEPTION;
len = 0;
buf = "";
mode = "w";
data = argv[1];
unref = false;
release = false;
filename = JS_ToCString(ctx, argv[0]);
if (!filename)
return JS_EXCEPTION;
if (JS_IsObject(data)) {
val = JS_GetPropertyStr(ctx, data, "buffer");
if (JS_IsException(val))
goto exception;
if (JS_IsArrayBuffer(val)) {
data = val;
unref = true;
} else {
JS_FreeValue(ctx, val);
}
}
if (JS_IsArrayBuffer(data)) {
buf = JS_GetArrayBuffer(ctx, &len, data);
mode = "wb";
} else if (!JS_IsUndefined(data)) {
buf = JS_ToCStringLen(ctx, &len, data);
release = true;
}
if (!buf)
goto exception;
fp = fopen(filename, mode);
if (!fp) {
JS_ThrowPlainError(ctx, "error opening %s for writing", filename);
goto exception;
}
n = fwrite(buf, len, 1, fp);
fclose(fp);
if (n != 1) {
JS_ThrowPlainError(ctx, "error writing to %s", filename);
goto exception;
}
ret = JS_UNDEFINED;
exception:
JS_FreeCString(ctx, filename);
if (release)
JS_FreeCString(ctx, buf);
if (unref)
JS_FreeValue(ctx, data);
return ret;
}

typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx,
const char *module_name);

Expand Down Expand Up @@ -1674,6 +1736,7 @@ static const JSCFunctionListEntry js_std_funcs[] = {
JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ),
#endif
JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ),
JS_CFUNC_DEF("writeFile", 2, js_std_writeFile ),
JS_CFUNC_DEF("strerror", 1, js_std_strerror ),

/* FILE I/O */
Expand Down
13 changes: 7 additions & 6 deletions tests/test_std.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,15 @@ function test_getline()
function test_popen()
{
var str, f, fname = "tmp_file.txt";
var content = "hello world";
var ta, content = "hello world";
var cmd = isWin ? "type" : "cat";

f = std.open(fname, "w");
f.puts(content);
f.close();

/* test loadFile */
ta = new Uint8Array([...content].map(c => c.charCodeAt(0)));
std.writeFile(fname, ta);
assert(std.loadFile(fname), content);
std.writeFile(fname, ta.buffer);
assert(std.loadFile(fname), content);
std.writeFile(fname, content);
assert(std.loadFile(fname), content);

/* execute shell command */
Expand Down