More file system checking functions:

- Stat#isFile
- Stat#isDirectory
- Directory.exists()
- File.exists()
This commit is contained in:
Bob Nystrom 2016-02-21 12:23:33 -08:00
parent c6148f8941
commit 60162575ee
15 changed files with 149 additions and 28 deletions

View File

@ -4,6 +4,11 @@ A directory on the file system.
## Static Methods ## Static Methods
### Directory.**exists**(path)
Whether a directory exists at `path`. This returns `false` for files or other
special file system entities.
### Directory.**list**(path) ### Directory.**list**(path)
Lists the contents of the directory at `path`. Returns a sorted list of path Lists the contents of the directory at `path`. Returns a sorted list of path

View File

@ -25,6 +25,11 @@ automatically closed.
Deletes the file at `path`. Deletes the file at `path`.
### File.**exists**(path)
Whether a regular file exists at `path`. This returns `false` for directories
or other special file system entities.
### File.**open**(path, fn) ### File.**open**(path, fn)
Opens the file at `path` for reading and passes it to `fn`. After the function Opens the file at `path` for reading and passes it to `fn`. After the function

View File

@ -10,45 +10,54 @@ A data structure describing the low-level details of a file system entry.
## Methods ## Methods
### **blockCount**
The number of system blocks allocated on disk for the file.
### **blockSize**
The preferred block size in bytes for interacting with the file. It may vary
from file to file.
### **device** ### **device**
The ID of the device containing the entry. The ID of the device containing the entry.
### **group**
Numeric group ID of the file's owner.
### **inode** ### **inode**
The [inode][] number of the entry. The [inode][] number of the entry.
[inode]: https://en.wikipedia.org/wiki/Inode [inode]: https://en.wikipedia.org/wiki/Inode
### **mode** ### **isDirectory**
A bit field describing the entry's type and protection flags. Whether the file system entity is a directory.
### **isFile**
Whether the file system entity is a regular file, as opposed to a directory or
other special entity.
### **linkCount** ### **linkCount**
The number of hard links to the entry. The number of hard links to the entry.
### **user** ### **mode**
Numeric user ID of the file's owner. A bit field describing the entry's type and protection flags.
### **group**
Numeric group ID of the file's owner.
### **specialDevice**
The device ID for the entry, if it's a special file.
### **size** ### **size**
The size of the entry in bytes. The size of the entry in bytes.
### **blockSize** ### **specialDevice**
The preferred block size in bytes for interacting with the file. It may vary The device ID for the entry, if it's a special file.
from file to file.
### **blockCount** ### **user**
The number of system blocks allocated on disk for the file. Numeric user ID of the file's owner.

View File

@ -32,6 +32,8 @@ extern void statMode(WrenVM* vm);
extern void statSize(WrenVM* vm); extern void statSize(WrenVM* vm);
extern void statSpecialDevice(WrenVM* vm); extern void statSpecialDevice(WrenVM* vm);
extern void statUser(WrenVM* vm); extern void statUser(WrenVM* vm);
extern void statIsDirectory(WrenVM* vm);
extern void statIsFile(WrenVM* vm);
extern void stdinReadStart(WrenVM* vm); extern void stdinReadStart(WrenVM* vm);
extern void stdinReadStop(WrenVM* vm); extern void stdinReadStop(WrenVM* vm);
extern void schedulerCaptureMethods(WrenVM* vm); extern void schedulerCaptureMethods(WrenVM* vm);
@ -45,7 +47,7 @@ extern void timerStartTimer(WrenVM* vm);
// If you add a new method to the longest class below, make sure to bump this. // 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 // Note that it also includes an extra slot for the sentinel value indicating
// the end of the list. // the end of the list.
#define MAX_METHODS_PER_CLASS 12 #define MAX_METHODS_PER_CLASS 14
// The maximum number of foreign classes a single built-in module defines. // The maximum number of foreign classes a single built-in module defines.
// //
@ -133,6 +135,8 @@ static ModuleRegistry modules[] =
METHOD("size", statSize) METHOD("size", statSize)
METHOD("specialDevice", statSpecialDevice) METHOD("specialDevice", statSpecialDevice)
METHOD("user", statUser) METHOD("user", statUser)
METHOD("isDirectory", statIsDirectory)
METHOD("isFile", statIsFile)
END_CLASS END_CLASS
CLASS(Stdin) CLASS(Stdin)
STATIC_METHOD("readStart_()", stdinReadStart) STATIC_METHOD("readStart_()", stdinReadStart)

View File

@ -442,6 +442,18 @@ void statUser(WrenVM* vm)
wrenSetSlotDouble(vm, 0, (double)stat->st_uid); wrenSetSlotDouble(vm, 0, (double)stat->st_uid);
} }
void statIsDirectory(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotBool(vm, 0, S_ISDIR(stat->st_mode));
}
void statIsFile(WrenVM* vm)
{
uv_stat_t* stat = (uv_stat_t*)wrenGetSlotForeign(vm, 0);
wrenSetSlotBool(vm, 0, S_ISREG(stat->st_mode));
}
static void allocCallback(uv_handle_t* handle, size_t suggestedSize, static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
uv_buf_t* buf) uv_buf_t* buf)
{ {

View File

@ -6,6 +6,18 @@ class Directory {
if (!(path is String)) Fiber.abort("Path must be a string.") if (!(path is String)) Fiber.abort("Path must be a string.")
} }
static exists(path) {
ensurePath_(path)
var stat
Fiber.new {
stat = Stat.path(path)
}.try()
// If we can't stat it, there's nothing there.
if (stat == null) return false
return stat.isDirectory
}
static list(path) { static list(path) {
ensurePath_(path) ensurePath_(path)
list_(path, Fiber.current) list_(path, Fiber.current)
@ -31,11 +43,23 @@ foreign class File {
} }
static delete(path) { static delete(path) {
File.ensurePath_(path) ensurePath_(path)
delete_(path, Fiber.current) delete_(path, Fiber.current)
return Scheduler.runNextScheduled_() return Scheduler.runNextScheduled_()
} }
static exists(path) {
ensurePath_(path)
var stat
Fiber.new {
stat = Stat.path(path)
}.try()
// If we can't stat it, there's nothing there.
if (stat == null) return false
return stat.isFile
}
static open(path) { openWithFlags(path, FileFlags.readOnly) } static open(path) { openWithFlags(path, FileFlags.readOnly) }
static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) } static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }
@ -43,8 +67,8 @@ foreign class File {
// TODO: Add named parameters and then call this "open(_,flags:_)"? // TODO: Add named parameters and then call this "open(_,flags:_)"?
// TODO: Test. // TODO: Test.
static openWithFlags(path, flags) { static openWithFlags(path, flags) {
File.ensurePath_(path) ensurePath_(path)
File.ensureInt_(flags, "Flags") ensureInt_(flags, "Flags")
open_(path, flags, Fiber.current) open_(path, flags, Fiber.current)
var fd = Scheduler.runNextScheduled_() var fd = Scheduler.runNextScheduled_()
return new_(fd) return new_(fd)
@ -68,7 +92,7 @@ foreign class File {
} }
static size(path) { static size(path) {
File.ensurePath_(path) ensurePath_(path)
sizePath_(path, Fiber.current) sizePath_(path, Fiber.current)
return Scheduler.runNextScheduled_() return Scheduler.runNextScheduled_()
} }
@ -175,6 +199,10 @@ foreign class Stat {
foreign size foreign size
foreign specialDevice foreign specialDevice
foreign user foreign user
foreign isFile
foreign isDirectory
// TODO: Other mode checks.
} }
class Stdin { class Stdin {

View File

@ -8,6 +8,18 @@ static const char* ioModuleSource =
" if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n" " if (!(path is String)) Fiber.abort(\"Path must be a string.\")\n"
" }\n" " }\n"
"\n" "\n"
" static exists(path) {\n"
" ensurePath_(path)\n"
" var stat\n"
" Fiber.new {\n"
" stat = Stat.path(path)\n"
" }.try()\n"
"\n"
" // If we can't stat it, there's nothing there.\n"
" if (stat == null) return false\n"
" return stat.isDirectory\n"
" }\n"
"\n"
" static list(path) {\n" " static list(path) {\n"
" ensurePath_(path)\n" " ensurePath_(path)\n"
" list_(path, Fiber.current)\n" " list_(path, Fiber.current)\n"
@ -33,11 +45,23 @@ static const char* ioModuleSource =
" }\n" " }\n"
"\n" "\n"
" static delete(path) {\n" " static delete(path) {\n"
" File.ensurePath_(path)\n" " ensurePath_(path)\n"
" delete_(path, Fiber.current)\n" " delete_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n" " return Scheduler.runNextScheduled_()\n"
" }\n" " }\n"
"\n" "\n"
" static exists(path) {\n"
" ensurePath_(path)\n"
" var stat\n"
" Fiber.new {\n"
" stat = Stat.path(path)\n"
" }.try()\n"
"\n"
" // If we can't stat it, there's nothing there.\n"
" if (stat == null) return false\n"
" return stat.isFile\n"
" }\n"
"\n"
" static open(path) { openWithFlags(path, FileFlags.readOnly) }\n" " static open(path) { openWithFlags(path, FileFlags.readOnly) }\n"
"\n" "\n"
" static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }\n" " static open(path, fn) { openWithFlags(path, FileFlags.readOnly, fn) }\n"
@ -45,8 +69,8 @@ static const char* ioModuleSource =
" // TODO: Add named parameters and then call this \"open(_,flags:_)\"?\n" " // TODO: Add named parameters and then call this \"open(_,flags:_)\"?\n"
" // TODO: Test.\n" " // TODO: Test.\n"
" static openWithFlags(path, flags) {\n" " static openWithFlags(path, flags) {\n"
" File.ensurePath_(path)\n" " ensurePath_(path)\n"
" File.ensureInt_(flags, \"Flags\")\n" " ensureInt_(flags, \"Flags\")\n"
" open_(path, flags, Fiber.current)\n" " open_(path, flags, Fiber.current)\n"
" var fd = Scheduler.runNextScheduled_()\n" " var fd = Scheduler.runNextScheduled_()\n"
" return new_(fd)\n" " return new_(fd)\n"
@ -70,7 +94,7 @@ static const char* ioModuleSource =
" }\n" " }\n"
"\n" "\n"
" static size(path) {\n" " static size(path) {\n"
" File.ensurePath_(path)\n" " ensurePath_(path)\n"
" sizePath_(path, Fiber.current)\n" " sizePath_(path, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\n" " return Scheduler.runNextScheduled_()\n"
" }\n" " }\n"
@ -177,6 +201,10 @@ static const char* ioModuleSource =
" foreign size\n" " foreign size\n"
" foreign specialDevice\n" " foreign specialDevice\n"
" foreign user\n" " foreign user\n"
"\n"
" foreign isFile\n"
" foreign isDirectory\n"
" // TODO: Other mode checks.\n"
"}\n" "}\n"
"\n" "\n"
"class Stdin {\n" "class Stdin {\n"

View File

@ -0,0 +1,9 @@
import "io" for Directory
System.print(Directory.exists("test/io/file")) // expect: true
System.print(Directory.exists("nonexistent")) // expect: false
// Files are not directories.
System.print(Directory.exists("test/io/file/file.txt")) // expect: false
// TODO: Symlinks.

View File

@ -0,0 +1,3 @@
import "io" for Directory
Directory.exists(123) // expect runtime error: Path must be a string.

9
test/io/file/exists.wren Normal file
View File

@ -0,0 +1,9 @@
import "io" for File
System.print(File.exists("test/io/file/file.txt")) // expect: true
System.print(File.exists("nonexistent")) // expect: false
// Directories are not files.
System.print(File.exists("test/io/file")) // expect: false
// TODO: Symlinks.

View File

@ -0,0 +1,3 @@
import "io" for File
File.exists(123) // expect runtime error: Path must be a string.

View File

@ -0,0 +1,4 @@
import "io" for Stat
System.print(Stat.path("test/io/file/file.txt").isDirectory) // expect: false
System.print(Stat.path("test").isDirectory) // expect: true

View File

@ -0,0 +1,4 @@
import "io" for Stat
System.print(Stat.path("test/io/file/file.txt").isFile) // expect: true
System.print(Stat.path("test").isFile) // expect: false

View File

@ -1,5 +1,4 @@
import "io" for Stat import "io" for Stat
import "scheduler" for Scheduler
var stat = Stat.path("test/io/file/file.txt") var stat = Stat.path("test/io/file/file.txt")

View File

@ -1,5 +1,4 @@
import "io" for Stat import "io" for Stat
import "scheduler" for Scheduler
var stat = Stat.path("test/io/directory/dir") var stat = Stat.path("test/io/directory/dir")