diff --git a/app/current/src/main/scala/io/scalajs/nodejs/fs/Fs.scala b/app/current/src/main/scala/io/scalajs/nodejs/fs/Fs.scala index a0486981e..721bdd6d7 100644 --- a/app/current/src/main/scala/io/scalajs/nodejs/fs/Fs.scala +++ b/app/current/src/main/scala/io/scalajs/nodejs/fs/Fs.scala @@ -1,7 +1,7 @@ package io.scalajs.nodejs package fs -import com.thoughtworks.enableIf +import com.thoughtworks.{enableIf, enableMembersIf} import io.scalajs.nodejs.buffer.Buffer import io.scalajs.nodejs.events.IEventEmitter @@ -559,6 +559,15 @@ trait Fs extends IEventEmitter with FSConstants { @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) def openSync(path: Path): FileDescriptor = js.native + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + def opendir(path: Path, options: OpendirOptions, callback: FsCallback1[Fs.Dir]): Unit = js.native + + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + def opendir(path: Path, callback: FsCallback1[Fs.Dir]): Unit = js.native + + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + def opendirSync(path: Path, options: OpendirOptions = js.native): Fs.Dir = js.native + /** * Read data from the file specified by fd. * @param fd is the file descriptor @@ -1138,7 +1147,9 @@ object Fs extends Fs { def mkdtemp(prefix: String, encoding: String = js.native): js.Promise[String] = js.native def open(path: Path, flags: Flags, mode: FileMode = js.native): js.Promise[FileHandle] = js.native @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) - def open(path: Path): js.Promise[FileHandle] = js.native + def open(path: Path): js.Promise[FileHandle] = js.native + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + def opendir(path: Path, options: OpendirOptions = js.native): js.Promise[Dir] = js.native def readdir(path: Path, options: ReaddirOptions): js.Promise[js.Array[String] | js.Array[Dirent]] = js.native def readdir(path: Path, options: String | FileEncodingOptions = js.native): js.Promise[js.Array[String]] = js.native def readlink(path: Path): js.Promise[String] = js.native @@ -1206,6 +1217,20 @@ object Fs extends Fs { def isSymbolicLink(): Boolean = js.native val name: String | Buffer = js.native } + + @enableMembersIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + @js.native + trait Dir extends js.Object { + def close(): js.Promise[Unit] = js.native + def close(callback: js.Function1[js.Error, Any]): Unit = js.native + def closeSync(): Unit = js.native + def path: String = js.native + def read(): js.Promise[Dirent] = js.native + def read(callback: js.Function2[js.Error, Dirent, Any]): Unit = js.native + def readSync(): Dirent = js.native + + // TODO: Implement AsyncIterable[Dirent] + } } @js.native @@ -1231,6 +1256,9 @@ class ReaddirOptions(var encoding: js.UndefOr[String] = js.undefined, var withFileTypes: js.UndefOr[Boolean] = js.undefined) extends js.Object +class OpendirOptions(var encoding: js.UndefOr[String] = js.undefined, var bufferSize: js.UndefOr[Double] = js.undefined) + extends js.Object + class ReadFileOptions(var flag: js.UndefOr[String] = js.undefined) extends js.Object class FileInputOptions(var flags: js.UndefOr[String] = js.undefined, diff --git a/app/current/src/main/scala/io/scalajs/nodejs/fs/package.scala b/app/current/src/main/scala/io/scalajs/nodejs/fs/package.scala index b10ddf852..88eee68e9 100644 --- a/app/current/src/main/scala/io/scalajs/nodejs/fs/package.scala +++ b/app/current/src/main/scala/io/scalajs/nodejs/fs/package.scala @@ -32,6 +32,7 @@ package object fs { /** * File System Extensions + * * @param instance the given [[Fs file system]] instance */ implicit final class FsExtensions(private val instance: Fs) extends AnyVal { @@ -194,6 +195,18 @@ package object fs { promiseWithError1[FileIOError, FileDescriptor](instance.open(path, _)) } + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + @inline + def opendirFuture(path: Path, options: OpendirOptions): Future[Fs.Dir] = { + promiseWithError1[FileIOError, Fs.Dir](instance.opendir(path, options, _)) + } + + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + @inline + def opendirFuture(path: Path): Future[Fs.Dir] = { + promiseWithError1[FileIOError, Fs.Dir](instance.opendir(path, _)) + } + @inline def readFuture(fd: FileDescriptor, buffer: Buffer, @@ -447,4 +460,27 @@ package object fs { ) } } + + /** + * Dir Extensions + * + * @param instance the given [[Fs.Dir]] instance + */ + implicit final class FsDirExtensions(private val instance: Fs.Dir) extends AnyVal { + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + @inline + def readFuture(): Future[Option[Fs.Dirent]] = { + promiseWithError1[js.Error, Option[Fs.Dirent]](f => { + instance.read((err, dir) => { + f(err, Option(dir)) + }) + }) + } + + @enableIf(io.scalajs.nodejs.internal.CompilerSwitches.gteNodeJs12) + @inline + def closeFuture(): Future[Unit] = { + promiseWithError0[js.Error](instance.close _) + } + } } diff --git a/app/current/src/test/scala/io/scalajs/nodejs/fs/FsAsyncTest.scala b/app/current/src/test/scala/io/scalajs/nodejs/fs/FsAsyncTest.scala index 103851a3b..1dcc9383c 100644 --- a/app/current/src/test/scala/io/scalajs/nodejs/fs/FsAsyncTest.scala +++ b/app/current/src/test/scala/io/scalajs/nodejs/fs/FsAsyncTest.scala @@ -5,6 +5,8 @@ import org.scalatest.BeforeAndAfterEach import scala.concurrent.ExecutionContext import org.scalatest.funspec.AsyncFunSpec +import scala.scalajs.js.JavaScriptException + class FsAsyncTest extends AsyncFunSpec with BeforeAndAfterEach { override implicit val executionContext = ExecutionContext.Implicits.global @@ -54,5 +56,24 @@ class FsAsyncTest extends AsyncFunSpec with BeforeAndAfterEach { assert(!dirExistsAfterRmdir) } } + + it("support opendir") { + for { + dir <- Fs.opendirFuture("core/src") + maybeFirstEntry <- dir.readFuture() + maybeSecondEntry <- dir.readFuture() + _ <- dir.closeFuture() + } yield { + assert(dir.path === "core/src") + assert(maybeFirstEntry.map(_.name) === Some("main")) + assert(maybeSecondEntry === None) + + val ex = intercept[JavaScriptException] { + assert(dir.readSync() === null) + } + assert(ex.getMessage().contains("ERR_DIR_CLOSED")) + } + } + } } diff --git a/app/current/src/test/scala/io/scalajs/nodejs/fs/FsClassesTest.scala b/app/current/src/test/scala/io/scalajs/nodejs/fs/FsClassesTest.scala index c85d4843d..26bfce242 100644 --- a/app/current/src/test/scala/io/scalajs/nodejs/fs/FsClassesTest.scala +++ b/app/current/src/test/scala/io/scalajs/nodejs/fs/FsClassesTest.scala @@ -1,7 +1,10 @@ package io.scalajs.nodejs.fs +import io.scalajs.nodejs.fs import org.scalatest.funspec.AnyFunSpec +import scala.scalajs.js.JavaScriptException + /** * File System (Fs) Tests * @@ -12,4 +15,22 @@ class FsClassesTest extends AnyFunSpec { assert(new ReadStream("package.json").pending) } } + + describe("opendir") { + it("returns Dir") { + val dir = fs.Fs.opendirSync("core/src") + assert(dir.path === "core/src") + val firstEntry = dir.readSync() + assert(firstEntry.name === "main") + assert(firstEntry.isDirectory()) + + assert(dir.readSync() === null) + + dir.closeSync() + val ex = intercept[JavaScriptException] { + assert(dir.readSync() === null) + } + assert(ex.getMessage().contains("ERR_DIR_CLOSED")) + } + } }