diff --git a/doc/site/modules/io/file.markdown b/doc/site/modules/io/file.markdown index 48933b1d..595eee1c 100644 --- a/doc/site/modules/io/file.markdown +++ b/doc/site/modules/io/file.markdown @@ -51,6 +51,15 @@ No encoding or decoding is done. If the file is UTF-8, then the resulting string will be a UTF-8 string. Otherwise, it will be a string of bytes in whatever encoding the file uses. +### File.**realPath**(path) + +Resolves `path`, traversing symlinks and removining any unneeded `./` and `../` +components. Returns the canonical absolute path to the file. + + :::wren + var path = "/some/./symlink/a/../b/file.txt" + System.print(File.realPath(path)) //> /real/path/a/file.txt + ### File.**size**(path) Returns the size in bytes of the contents of the file at `path`. diff --git a/src/cli/modules.c b/src/cli/modules.c index b46e3dcc..6aa28fd4 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -17,6 +17,7 @@ extern void fileSizePath(WrenVM* vm); extern void fileClose(WrenVM* vm); extern void fileDescriptor(WrenVM* vm); extern void fileReadBytes(WrenVM* vm); +extern void fileRealPath(WrenVM* vm); extern void fileSize(WrenVM* vm); extern void fileStat(WrenVM* vm); extern void fileWriteBytes(WrenVM* vm); @@ -115,6 +116,7 @@ static ModuleRegistry modules[] = FINALIZER(fileFinalize) STATIC_METHOD("delete_(_,_)", fileDelete) STATIC_METHOD("open_(_,_,_)", fileOpen) + STATIC_METHOD("realPath_(_,_)", fileRealPath) STATIC_METHOD("sizePath_(_,_)", fileSizePath) METHOD("close_(_)", fileClose) METHOD("descriptor", fileDescriptor) diff --git a/src/module/io.c b/src/module/io.c index f5182ab4..fd517c61 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -302,6 +302,23 @@ void fileReadBytes(WrenVM* vm) fileReadBytesCallback); } +static void realPathCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + wrenEnsureSlots(getVM(), 3); + wrenSetSlotString(getVM(), 2, (char*)request->ptr); + schedulerResume(freeRequest(request), true); + schedulerFinishResume(); +} + +void fileRealPath(WrenVM* vm) +{ + const char* path = wrenGetSlotString(vm, 1); + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2)); + uv_fs_realpath(getLoop(), request, path, realPathCallback); +} + // Called by libuv when the stat call completes. static void statCallback(uv_fs_t* request) { diff --git a/src/module/io.wren b/src/module/io.wren index d04f1130..1a9766c6 100644 --- a/src/module/io.wren +++ b/src/module/io.wren @@ -91,6 +91,14 @@ foreign class File { return File.open(path) {|file| file.readBytes(file.size) } } + // TODO: This works for directories too, so putting it on File is kind of + // lame. Consider reorganizing these classes some. + static realPath(path) { + ensurePath_(path) + realPath_(path, Fiber.current) + return Scheduler.runNextScheduled_() + } + static size(path) { ensurePath_(path) sizePath_(path, Fiber.current) @@ -158,6 +166,7 @@ foreign class File { foreign static delete_(path, fiber) foreign static open_(path, flags, fiber) + foreign static realPath_(path, fiber) foreign static sizePath_(path, fiber) foreign close_(fiber) diff --git a/src/module/io.wren.inc b/src/module/io.wren.inc index 3b780e40..b6cfccb4 100644 --- a/src/module/io.wren.inc +++ b/src/module/io.wren.inc @@ -93,6 +93,14 @@ static const char* ioModuleSource = " return File.open(path) {|file| file.readBytes(file.size) }\n" " }\n" "\n" +" // TODO: This works for directories too, so putting it on File is kind of\n" +" // lame. Consider reorganizing these classes some.\n" +" static realPath(path) {\n" +" ensurePath_(path)\n" +" realPath_(path, Fiber.current)\n" +" return Scheduler.runNextScheduled_()\n" +" }\n" +"\n" " static size(path) {\n" " ensurePath_(path)\n" " sizePath_(path, Fiber.current)\n" @@ -160,6 +168,7 @@ static const char* ioModuleSource = "\n" " foreign static delete_(path, fiber)\n" " foreign static open_(path, flags, fiber)\n" +" foreign static realPath_(path, fiber)\n" " foreign static sizePath_(path, fiber)\n" "\n" " foreign close_(fiber)\n" diff --git a/test/io/file/real_path_nonexistent.wren b/test/io/file/real_path_nonexistent.wren new file mode 100644 index 00000000..500447ee --- /dev/null +++ b/test/io/file/real_path_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.realPath("nonexistent") // expect runtime error: no such file or directory diff --git a/test/io/file/real_path_wrong_arg_type.wren b/test/io/file/real_path_wrong_arg_type.wren new file mode 100644 index 00000000..23fea60d --- /dev/null +++ b/test/io/file/real_path_wrong_arg_type.wren @@ -0,0 +1,6 @@ +import "io" for File + +File.realPath(123) // expect runtime error: Path must be a string. + +// TODO: Write success case tests too when we have an API to create symlinks +// from Wren.