Skip to content

Commit

Permalink
Merge pull request #376 from olafurpg/js-node-fs
Browse files Browse the repository at this point in the history
Remove Node.js requirement with Scala.js
  • Loading branch information
gabro authored Jul 2, 2021
2 parents 3e55ef2 + 33ec001 commit be448cb
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 127 deletions.
3 changes: 1 addition & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ val sharedJVMSettings: List[Def.Setting[_]] = List(
) ++ mimaEnable
val sharedJSSettings: List[Def.Setting[_]] = List(
skipIdeaSettings,
crossScalaVersions := allScalaVersions.filterNot(_.startsWith("0.")),
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule))
crossScalaVersions := allScalaVersions.filterNot(_.startsWith("0."))
)
val sharedJSConfigure: Project => Project =
_.disablePlugins(MimaPlugin)
Expand Down
24 changes: 16 additions & 8 deletions munit/js/src/main/scala/java/io/File.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package java.io

import java.net.URI
import java.nio.file.Path

import munit.internal.{JSIO, JSPath, NodeNIOPath}
import munit.internal.JSIO
import munit.internal.NodeNIOPath

// obtained implementation by experimentation on the JDK.
class File(path: String) {
Expand Down Expand Up @@ -55,19 +55,27 @@ class File(path: String) {
object File {
def listRoots(): Array[File] = Array(
new File(
if (JSIO.isNode) JSPath.parse(JSPath.resolve()).root
else "/"
JSIO.path match {
case Some(p) => p.parse(p.resolve()).root.asInstanceOf[String]
case None => "/"
}
// if (JSIO.isNode) JSPath.parse(JSPath.resolve()).root
// else "/"
)
)

def separatorChar: Char =
separator.charAt(0)

def separator: String =
if (JSIO.isNode) JSPath.sep
else "/"
JSIO.path match {
case Some(p) => p.sep.asInstanceOf[String]
case None => "/"
}

def pathSeparator: String =
if (JSIO.isNode) JSPath.delimiter
else ":"
JSIO.path match {
case Some(p) => p.delimeter.asInstanceOf[String]
case None => ":"
}
}
11 changes: 8 additions & 3 deletions munit/js/src/main/scala/java/nio/file/Files.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package java.nio.file

import scala.scalajs.js
import java.{util => ju}
import java.nio.charset.StandardCharsets

import munit.internal.{JSFs, JSIO}
import munit.internal.JSIO

import scala.collection.JavaConverters._

Expand All @@ -13,8 +14,12 @@ object Files {
val text = new String(bytes, StandardCharsets.UTF_8)
text.linesIterator.toSeq.asJava
}
def readAllBytes(path: Path): Array[Byte] = JSIO.inNode {
val jsArray = JSFs.readFileSync(path.toString)
def readAllBytes(path: Path): Array[Byte] = {
val jsArray = JSIO.fs match {
case Some(fs) =>
fs.readFileSync(path.toString).asInstanceOf[js.Array[Int]]
case None => new js.Array[Int](0)
}
val len = jsArray.length
val result = new Array[Byte](len)
var curr = 0
Expand Down
125 changes: 23 additions & 102 deletions munit/js/src/main/scala/munit/internal/JSIO.scala
Original file line number Diff line number Diff line change
@@ -1,117 +1,38 @@
package munit.internal

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import scala.scalajs.js.annotation.JSImport.Namespace

/**
* Facade for the native nodejs process API
*
* The process object is a global that provides information about, and
* control over, the current Node.js process. As a global, it is always
* available to Node.js applications without using require().
*
* @see https://nodejs.org/api/process.html
*/
@js.native
trait JSProcess extends js.Any {
def cwd(): String = js.native
}

/**
* Facade for native nodejs module "fs".
*
* @see https://nodejs.org/api/fs.html
*/
@js.native
@JSImport("fs", Namespace)
object JSFs extends js.Any {

/**
* Returns the file contents as Buffer using blocking apis.
*
* NOTE: The actual return value is a Node.js buffer and not js.Array[Int].
* However, both support .length and angle bracket access (foo[1]).
*/
def readFileSync(path: String): js.Array[Int] = js.native

/** Returns the file contents as String using blocking apis */
def readFileSync(path: String, encoding: String): String = js.native

/** Writes file contents using blocking apis */
def writeFileSync(path: String, buffer: js.Array[Int]): Unit = js.native

/** Returns an array of filenames excluding '.' and '..'. */
def readdirSync(path: String): js.Array[String] = js.native

/** Returns an fs.Stats for path. */
def lstatSync(path: String): JSStats = js.native

/** Returns true if the file exists, false otherwise. */
def existsSync(path: String): Boolean = js.native

/** Synchronously creates a directory. */
def mkdirSync(path: String): Unit = js.native
}

/**
* Facade for nodejs class fs.Stats.
*
* @see https://nodejs.org/api/fs.html#fs_class_fs_stats
*/
@js.native
@JSImport("fs", Namespace)
class JSStats extends js.Any {
def isFile(): Boolean = js.native
def isDirectory(): Boolean = js.native
}

/**
* Facade for native nodejs module "path".
*
* @see https://nodejs.org/api/path.html
*/
@js.native
@JSImport("path", Namespace)
object JSPath extends js.Any {
def sep: String = js.native
def delimiter: String = js.native
def isAbsolute(path: String): Boolean = js.native
def parse(path: String): JSPath.type = js.native
def resolve(paths: String*): String = js.native
def normalize(path: String): String = js.native
def basename(path: String): String = js.native
def dirname(path: String): String = js.native
def root: String = js.native
def relative(from: String, to: String): String = js.native
def join(first: String, more: String*): String = js.native
}
import scala.util.Try

object JSIO {
private[internal] val process: JSProcess =
js.Dynamic.global.process.asInstanceOf[JSProcess]
def isNode: Boolean =
!js.isUndefined(process) && !js.isUndefined(process.cwd())

def inNode[T](f: => T): T =
if (JSIO.isNode) f
else {
throw new IllegalStateException(
"This operation is not supported in this environment."
)
}
private def require(module: String): Option[js.Dynamic] = {
Try(js.Dynamic.global.require(module)).toOption
}
val process: Option[js.Dynamic] = require("process")
val path: Option[js.Dynamic] = require("path")
val fs: Option[js.Dynamic] = require("fs")

def cwd(): String =
if (isNode) process.cwd()
else "/"
process match {
case Some(p) => p.cwd().asInstanceOf[String]
case None => "/"
}

def exists(path: String): Boolean =
if (isNode) JSFs.existsSync(path)
else false
fs match {
case Some(f) => f.existsSync(path).asInstanceOf[Boolean]
case None => false
}

def isFile(path: String): Boolean =
exists(path) && JSFs.lstatSync(path).isFile()
exists(path) && (fs match {
case Some(f) => f.lstatSync(path).isFile().asInstanceOf[Boolean]
case None => false
})

def isDirectory(path: String): Boolean =
exists(path) && JSFs.lstatSync(path).isDirectory()
exists(path) && (fs match {
case Some(f) => f.lstatSync(path).isDirectory().asInstanceOf[Boolean]
case None => false
})
}
75 changes: 63 additions & 12 deletions munit/js/src/main/scala/munit/internal/NodeNIOPath.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ case class NodeNIOPath(filename: String) extends Path {
)
override def toFile: File =
new File(filename)
override def isAbsolute: Boolean =
if (JSIO.isNode) JSPath.isAbsolute(filename)
else filename.startsWith(File.separator)
override def isAbsolute: Boolean = JSIO.path match {
case Some(path) => path.isAbsolute(filename).asInstanceOf[Boolean]
case None => filename.startsWith(File.separator)
}
override def getName(index: Int): Path =
NodeNIOPath(
filename
Expand All @@ -40,12 +41,29 @@ case class NodeNIOPath(filename: String) extends Path {
.getOrElse(throw new IllegalArgumentException)
)
override def getParent: Path =
NodeNIOPath(JSPath.dirname(filename))
JSIO.path match {
case Some(path) =>
NodeNIOPath(path.dirname(filename).asInstanceOf[String])
case None =>
throw new UnsupportedOperationException(
"Path.getParent() is only supported in Node.js"
)
}

override def toAbsolutePath: Path =
if (isAbsolute) this
else NodeNIOPath.workingDirectory.resolve(this)
override def relativize(other: Path): Path =
NodeNIOPath(JSPath.relative(filename, other.toString))
JSIO.path match {
case Some(path) =>
NodeNIOPath(
path.relative(filename, other.toString()).asInstanceOf[String]
)
case None =>
throw new UnsupportedOperationException(
"Path.relativize() is only supported in Node.js"
)
}
override def getNameCount: Int = {
val strippeddrive =
if ((filename.length > 1) && (filename(1) == ':')) filename.substring(2)
Expand All @@ -57,13 +75,26 @@ case class NodeNIOPath(filename: String) extends Path {
}
override def toUri: URI = toFile.toURI
override def getFileName: Path =
NodeNIOPath(JSPath.basename(filename))
JSIO.path match {
case Some(path) =>
NodeNIOPath(path.basename(filename).asInstanceOf[String])
case None =>
throw new UnsupportedOperationException(
"Path.getFileName() is only supported in Node.js"
)
}
override def getRoot: Path =
if (!isAbsolute) null
else NodeNIOPath(File.separator)
override def normalize(): Path =
if (JSIO.isNode) NodeNIOPath(JSPath.normalize(filename))
else this
JSIO.path match {
case Some(path) =>
NodeNIOPath(path.normalize(filename).asInstanceOf[String])
case None =>
throw new UnsupportedOperationException(
"Path.normalize() is only supported in Node.js"
)
}
override def endsWith(other: Path): Boolean =
endsWith(other.toString)
override def endsWith(other: String): Boolean =
Expand All @@ -76,13 +107,33 @@ case class NodeNIOPath(filename: String) extends Path {
override def resolveSibling(other: Path): Path =
resolveSibling(other.toString)
override def resolveSibling(other: String): Path =
adjustResolvedPath(
NodeNIOPath(JSPath.resolve(JSPath.dirname(filename), other))
)
JSIO.path match {
case Some(path) =>
adjustResolvedPath(
NodeNIOPath(
path
.resolve(path.dirname(filename).asInstanceOf[String], other)
.asInstanceOf[String]
)
)
case None =>
throw new UnsupportedOperationException(
"Path.normalize() is only supported in Node.js"
)
}
override def resolve(other: Path): Path =
resolve(other.toString)
override def resolve(other: String): Path =
adjustResolvedPath(NodeNIOPath(JSPath.resolve(filename, other)))
JSIO.path match {
case Some(path) =>
adjustResolvedPath(
NodeNIOPath(path.resolve(filename, other).asInstanceOf[String])
)
case None =>
throw new UnsupportedOperationException(
"Path.normalize() is only supported in Node.js"
)
}
override def startsWith(other: Path): Boolean =
startsWith(other.toString)
override def startsWith(other: String): Boolean =
Expand Down

0 comments on commit be448cb

Please sign in to comment.