diff --git a/doc/site/modules/io/index.markdown b/doc/site/modules/io/index.markdown index 1dc81f49..2b5a765c 100644 --- a/doc/site/modules/io/index.markdown +++ b/doc/site/modules/io/index.markdown @@ -4,4 +4,5 @@ Provides access to operating system streams and the file system. * [Directory](directory.html) * [File](file.html) +* [Stat](stat.html) * [Stdin](stdin.html) diff --git a/doc/site/modules/io/stat.markdown b/doc/site/modules/io/stat.markdown new file mode 100644 index 00000000..0b69b219 --- /dev/null +++ b/doc/site/modules/io/stat.markdown @@ -0,0 +1,50 @@ +^title Stat Class + +Contains the data returned by [File.stat()][stat]. + +[stat]: file.html#file.stat(path) + +## Methods + +### **device** + +The ID of the device containing the entry. + +### **inode** + +The [inode][] number of the entry. + +[inode]: https://en.wikipedia.org/wiki/Inode + +### **mode** + +A bit field describing the entry's type and protection flags. + +### **linkCount** + +The number of hard links to the entry. + +### **user** + +Numeric user ID of the file's owner. + +### **group** + +Numeric group ID of the file's owner. + +### **specialDevice** + +The device ID for the entry, if it's a special file. + +### **size** + +The size of the entry in bytes. + +### **blockSize** + +The preferred block size in bytes for interacting with the file. It may vary +from file to file. + +### **blockCount** + +The number of system blocks allocated on disk for the file. diff --git a/src/cli/modules.c b/src/cli/modules.c index cf4e8075..024156e3 100644 --- a/src/cli/modules.c +++ b/src/cli/modules.c @@ -12,6 +12,7 @@ extern void fileAllocate(WrenVM* vm); extern void fileFinalize(void* data); extern void fileOpen(WrenVM* vm); extern void fileSizePath(WrenVM* vm); +extern void fileStatPath(WrenVM* vm); extern void fileClose(WrenVM* vm); extern void fileDescriptor(WrenVM* vm); extern void fileReadBytes(WrenVM* vm); @@ -29,7 +30,7 @@ extern void timerStartTimer(WrenVM* vm); // If you add a new method to the longest class below, make sure to bump this. // Note that it also includes an extra slot for the sentinel value indicating // the end of the list. -#define MAX_METHODS_PER_CLASS 9 +#define MAX_METHODS_PER_CLASS 10 // The maximum number of foreign classes a single built-in module defines. // @@ -97,6 +98,7 @@ static ModuleRegistry modules[] = FINALIZER(fileFinalize) STATIC_METHOD("open_(_,_)", fileOpen) STATIC_METHOD("sizePath_(_,_)", fileSizePath) + STATIC_METHOD("statPath_(_,_)", fileStatPath) METHOD("close_(_)", fileClose) METHOD("descriptor", fileDescriptor) METHOD("readBytes_(_,_,_)", fileReadBytes) diff --git a/src/module/io.c b/src/module/io.c index 6b6c7c98..06371264 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -188,6 +188,62 @@ void fileSizePath(WrenVM* vm) uv_fs_stat(getLoop(), request, path, fileSizeCallback); } +// Called by libuv when the stat call completes. +static void fileStatPathCallback(uv_fs_t* request) +{ + if (handleRequestError(request)) return; + + WrenVM* vm = getVM(); + wrenEnsureSlots(vm, 4); + wrenSetSlotNewList(vm, 2); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_dev); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_ino); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_mode); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_nlink); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_uid); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_gid); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_rdev); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_size); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blksize); + wrenInsertInList(vm, 2, -1, 3); + + wrenSetSlotDouble(vm, 3, (double)request->statbuf.st_blocks); + wrenInsertInList(vm, 2, -1, 3); + + // TODO: Include access, modification, and change times once we figure out + // how we want to represent it. + // time_t st_atime; /* time of last access */ + // time_t st_mtime; /* time of last modification */ + // time_t st_ctime; /* time of last status change */ + + schedulerResume(freeRequest(request), true); + schedulerFinishResume(); +} + +void fileStatPath(WrenVM* vm) +{ + const char* path = wrenGetSlotString(vm, 1); + uv_fs_t* request = createRequest(wrenGetSlotValue(vm, 2)); + uv_fs_stat(getLoop(), request, path, fileStatPathCallback); +} + static void fileCloseCallback(uv_fs_t* request) { if (handleRequestError(request)) return; diff --git a/src/module/io.wren b/src/module/io.wren index ea707624..e501c67a 100644 --- a/src/module/io.wren +++ b/src/module/io.wren @@ -44,6 +44,13 @@ foreign class File { return Scheduler.runNextScheduled_() } + static stat(path) { + if (!(path is String)) Fiber.abort("Path must be a string.") + + statPath_(path, Fiber.current) + return Stat.new_(Scheduler.runNextScheduled_()) + } + construct new_(fd) {} close() { @@ -80,12 +87,30 @@ foreign class File { foreign static open_(path, fiber) foreign static sizePath_(path, fiber) + foreign static statPath_(path, fiber) foreign close_(fiber) foreign readBytes_(count, start, fiber) foreign size_(fiber) } +class Stat { + construct new_(fields) { + _fields = fields + } + + device { _fields[0] } + inode { _fields[1] } + mode { _fields[2] } + linkCount { _fields[3] } + user { _fields[4] } + group { _fields[5] } + specialDevice { _fields[6] } + size { _fields[7] } + blockSize { _fields[8] } + blockCount { _fields[9] } +} + class Stdin { static readLine() { if (__isClosed == true) { diff --git a/src/module/io.wren.inc b/src/module/io.wren.inc index 7ba842d5..6f83dc7b 100644 --- a/src/module/io.wren.inc +++ b/src/module/io.wren.inc @@ -46,6 +46,13 @@ static const char* ioModuleSource = " return Scheduler.runNextScheduled_()\n" " }\n" "\n" +" static stat(path) {\n" +" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" +"\n" +" statPath_(path, Fiber.current)\n" +" return Stat.new_(Scheduler.runNextScheduled_())\n" +" }\n" +"\n" " construct new_(fd) {}\n" "\n" " close() {\n" @@ -82,12 +89,30 @@ static const char* ioModuleSource = "\n" " foreign static open_(path, fiber)\n" " foreign static sizePath_(path, fiber)\n" +" foreign static statPath_(path, fiber)\n" "\n" " foreign close_(fiber)\n" " foreign readBytes_(count, start, fiber)\n" " foreign size_(fiber)\n" "}\n" "\n" +"class Stat {\n" +" construct new_(fields) {\n" +" _fields = fields\n" +" }\n" +"\n" +" device { _fields[0] }\n" +" inode { _fields[1] }\n" +" mode { _fields[2] }\n" +" linkCount { _fields[3] }\n" +" user { _fields[4] }\n" +" group { _fields[5] }\n" +" specialDevice { _fields[6] }\n" +" size { _fields[7] }\n" +" blockSize { _fields[8] }\n" +" blockCount { _fields[9] }\n" +"}\n" +"\n" "class Stdin {\n" " static readLine() {\n" " if (__isClosed == true) {\n" diff --git a/test/io/file/size.wren b/test/io/file/size.wren index 84e72eab..b1cc17fe 100644 --- a/test/io/file/size.wren +++ b/test/io/file/size.wren @@ -1,13 +1,6 @@ import "io" for File import "scheduler" for Scheduler -System.print(File.size("test/io/file/size.wren")) // expect: 270 - -// Runs asynchronously. -Scheduler.add { - System.print("async") -} - -System.print(File.size("test/io/file/size.wren")) -// expect: async -// expect: 270 +var file = File.open("test/io/file/file.txt") +System.print(file.size) // expect: 19 +file.close() diff --git a/test/io/file/size_static.wren b/test/io/file/size_static.wren new file mode 100644 index 00000000..c9b720f0 --- /dev/null +++ b/test/io/file/size_static.wren @@ -0,0 +1,13 @@ +import "io" for File +import "scheduler" for Scheduler + +System.print(File.size("test/io/file/file.txt")) // expect: 19 + +// Runs asynchronously. +Scheduler.add { + System.print("async") +} + +System.print(File.size("test/io/file/file.txt")) +// expect: async +// expect: 19 diff --git a/test/io/file/size_nonexistent.wren b/test/io/file/size_static_nonexistent.wren similarity index 100% rename from test/io/file/size_nonexistent.wren rename to test/io/file/size_static_nonexistent.wren diff --git a/test/io/file/stat_static.wren b/test/io/file/stat_static.wren new file mode 100644 index 00000000..0ca59d3f --- /dev/null +++ b/test/io/file/stat_static.wren @@ -0,0 +1,16 @@ +import "io" for File, Stat +import "scheduler" for Scheduler + +var stat = File.stat("test/io/file/file.txt") + +System.print(stat is Stat) // expect: true +System.print(stat.device is Num) // expect: true +System.print(stat.inode is Num) // expect: true +System.print(stat.mode is Num) // expect: true +System.print(stat.linkCount) // expect: 1 +System.print(stat.user is Num) // expect: true +System.print(stat.group is Num) // expect: true +System.print(stat.specialDevice) // expect: 0 +System.print(stat.size) // expect: 19 +System.print(stat.blockSize is Num) // expect: true +System.print(stat.blockCount is Num) // expect: true diff --git a/test/io/file/stat_static_nonexistent.wren b/test/io/file/stat_static_nonexistent.wren new file mode 100644 index 00000000..8bc17802 --- /dev/null +++ b/test/io/file/stat_static_nonexistent.wren @@ -0,0 +1,3 @@ +import "io" for File + +File.stat("nonexistent") // expect runtime error: no such file or directory