From 77310e39bf76f4d2652c6adc34ab4942bc6f0cee Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Mon, 7 Aug 2023 13:18:10 +0200 Subject: [PATCH 01/11] Add root to Path & root constructor --- os/src-jvm/package.scala | 6 +++++- os/src-native/package.scala | 6 +++++- os/src/Path.scala | 1 + os/test/src/PathTests.scala | 9 +++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/os/src-jvm/package.scala b/os/src-jvm/package.scala index 1774cb37..96a992a5 100644 --- a/os/src-jvm/package.scala +++ b/os/src-jvm/package.scala @@ -8,7 +8,11 @@ package object os { /** * The root of the filesystem */ - val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) + val root: RootPathSelector = new RootPathSelector(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) + + class RootPathSelector(wrapped: java.nio.file.Path) extends Path(wrapped) with (String => Path) { + override def apply(root: String): Path = Path(root) + } def resource(implicit resRoot: ResourceRoot = Thread.currentThread().getContextClassLoader) = { os.ResourcePath.resource(resRoot) diff --git a/os/src-native/package.scala b/os/src-native/package.scala index 0edf9f2d..289d9684 100644 --- a/os/src-native/package.scala +++ b/os/src-native/package.scala @@ -6,7 +6,11 @@ package object os { /** * The root of the filesystem */ - val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) + val root: RootPathSelector = new RootPathSelector(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) + + class RootPathSelector(wrapped: java.nio.file.Path) extends Path(wrapped) with (String => Path) { + override def apply(root: String): Path = Path(root) + } /** * The user's home directory diff --git a/os/src/Path.scala b/os/src/Path.scala index 9de9d2a4..11c60571 100644 --- a/os/src/Path.scala +++ b/os/src/Path.scala @@ -453,6 +453,7 @@ class Path private[os] (val wrapped: java.nio.file.Path) new SeekableSource.ChannelSource(java.nio.file.Files.newByteChannel(wrapped)) require(wrapped.isAbsolute, s"$wrapped is not an absolute path") + def root = Option(wrapped.getRoot).map(_.toString).getOrElse("") def segments: Iterator[String] = wrapped.iterator().asScala.map(_.toString) def getSegment(i: Int): String = wrapped.getName(i).toString def segmentCount = wrapped.getNameCount diff --git a/os/test/src/PathTests.scala b/os/test/src/PathTests.scala index 646280d5..c90479f2 100644 --- a/os/test/src/PathTests.scala +++ b/os/test/src/PathTests.scala @@ -1,6 +1,7 @@ package test.os import java.nio.file.Paths +import java.io.File import os._ import utest.{assert => _, _} @@ -436,5 +437,13 @@ object PathTests extends TestSuite { assert(result1 == expected) assert(result2 == expected) } + test("custom root") { + assert(os.root == os.root(os.root.root)) + File.listRoots().foreach { root => + val path = os.root(root.toPath().toString) / "test" / "dir" + assert(path.root == root.toString) + assert(path.relativeTo(os.root(root.toPath().toString)) == rel / "test" / "dir") + } + } } } From ec2038470198e06b62039f691efe1d1cd7811709 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Tue, 19 Sep 2023 18:28:55 +0200 Subject: [PATCH 02/11] Keep val and add def for customizable root --- os/src-jvm/package.scala | 10 ++++++---- os/src-native/package.scala | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/os/src-jvm/package.scala b/os/src-jvm/package.scala index 96a992a5..bd5fe4bf 100644 --- a/os/src-jvm/package.scala +++ b/os/src-jvm/package.scala @@ -8,10 +8,12 @@ package object os { /** * The root of the filesystem */ - val root: RootPathSelector = new RootPathSelector(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) - - class RootPathSelector(wrapped: java.nio.file.Path) extends Path(wrapped) with (String => Path) { - override def apply(root: String): Path = Path(root) + val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) + + def root(root: String): Path = { + val path = Path(root) + assert(path.root == root, s"$root is not a root path") + path } def resource(implicit resRoot: ResourceRoot = Thread.currentThread().getContextClassLoader) = { diff --git a/os/src-native/package.scala b/os/src-native/package.scala index 289d9684..c58c1d68 100644 --- a/os/src-native/package.scala +++ b/os/src-native/package.scala @@ -6,10 +6,12 @@ package object os { /** * The root of the filesystem */ - val root: RootPathSelector = new RootPathSelector(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) - - class RootPathSelector(wrapped: java.nio.file.Path) extends Path(wrapped) with (String => Path) { - override def apply(root: String): Path = Path(root) + val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) + + def root(root: String): Path = { + val path = Path(root) + assert(path.root == root, s"$root is not a root path") + path } /** From 00adf743b220f89f6a225b58de6d87d08614fd17 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Tue, 19 Sep 2023 18:30:16 +0200 Subject: [PATCH 03/11] Formatted code --- os/src-jvm/package.scala | 2 +- os/src-native/package.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/os/src-jvm/package.scala b/os/src-jvm/package.scala index bd5fe4bf..d58da592 100644 --- a/os/src-jvm/package.scala +++ b/os/src-jvm/package.scala @@ -9,7 +9,7 @@ package object os { * The root of the filesystem */ val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) - + def root(root: String): Path = { val path = Path(root) assert(path.root == root, s"$root is not a root path") diff --git a/os/src-native/package.scala b/os/src-native/package.scala index c58c1d68..dfd49f6f 100644 --- a/os/src-native/package.scala +++ b/os/src-native/package.scala @@ -7,7 +7,7 @@ package object os { * The root of the filesystem */ val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) - + def root(root: String): Path = { val path = Path(root) assert(path.root == root, s"$root is not a root path") From b46fa04375a4225443ce4977e739cf16f818d5a8 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Tue, 10 Oct 2023 16:35:00 +0200 Subject: [PATCH 04/11] Support custom fs --- os/src-jvm/package.scala | 7 +++++-- os/src-native/package.scala | 6 ++++-- os/src/Path.scala | 2 ++ os/test/src-jvm/PathTestsJvmOnly.scala | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/os/src-jvm/package.scala b/os/src-jvm/package.scala index d58da592..300c1546 100644 --- a/os/src-jvm/package.scala +++ b/os/src-jvm/package.scala @@ -1,4 +1,7 @@ import scala.language.implicitConversions +import java.nio.file.FileSystem +import java.nio.file.FileSystems +import java.nio.file.Paths package object os { type Generator[+T] = geny.Generator[T] @@ -10,8 +13,8 @@ package object os { */ val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) - def root(root: String): Path = { - val path = Path(root) + def root(root: String, fileSystem: FileSystem = FileSystems.getDefault()): Path = { + val path = Path(fileSystem.getPath(root)) assert(path.root == root, s"$root is not a root path") path } diff --git a/os/src-native/package.scala b/os/src-native/package.scala index dfd49f6f..9f6121f3 100644 --- a/os/src-native/package.scala +++ b/os/src-native/package.scala @@ -1,3 +1,5 @@ +import java.nio.file.FileSystem +import java.nio.file.FileSystems package object os { type Generator[+T] = geny.Generator[T] val Generator = geny.Generator @@ -8,8 +10,8 @@ package object os { */ val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) - def root(root: String): Path = { - val path = Path(root) + def root(root: String, fileSystem: FileSystem = FileSystems.getDefault()): Path = { + val path = Path(fileSystem.getPath(root)) assert(path.root == root, s"$root is not a root path") path } diff --git a/os/src/Path.scala b/os/src/Path.scala index 5df2ca07..2894abb0 100644 --- a/os/src/Path.scala +++ b/os/src/Path.scala @@ -485,6 +485,8 @@ class Path private[os] (val wrapped: java.nio.file.Path) require(wrapped.isAbsolute || Path.driveRelative(wrapped), s"$wrapped is not an absolute path") def root = Option(wrapped.getRoot).map(_.toString).getOrElse("") + def fileSystem = wrapped.getFileSystem() + def segments: Iterator[String] = wrapped.iterator().asScala.map(_.toString) def getSegment(i: Int): String = wrapped.getName(i).toString def segmentCount = wrapped.getNameCount diff --git a/os/test/src-jvm/PathTestsJvmOnly.scala b/os/test/src-jvm/PathTestsJvmOnly.scala index 8028e063..87e37319 100644 --- a/os/test/src-jvm/PathTestsJvmOnly.scala +++ b/os/test/src-jvm/PathTestsJvmOnly.scala @@ -4,6 +4,10 @@ import java.nio.file.Paths import os._ import utest._ +import java.util.HashMap +import java.nio.file.FileSystems +import java.net.URI + object PathTestsJvmOnly extends TestSuite { val tests = Tests { test("construction") { @@ -40,6 +44,18 @@ object PathTestsJvmOnly extends TestSuite { names.foreach(p => assert(!exists(twd / p))) } } + test("custom filesystem") { // native doesnt support custom fs yet + val path: java.nio.file.Path = java.nio.file.Paths.get("foo.jar"); + val uri = new URI("jar", path.toUri().toString(), null); + + val env = new HashMap[String, String](); + env.put("create", "true"); + + val fileSystem = FileSystems.newFileSystem(uri, env); + val p = os.root("/", fileSystem) / "test" / "dir" + assert(p.root == "/") + assert(p.fileSystem == fileSystem) + } } } } From 39994de7b468bf21020c97ed000805f832c02bde Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Tue, 10 Oct 2023 16:38:43 +0200 Subject: [PATCH 05/11] reformat --- os/test/src-jvm/PathTestsJvmOnly.scala | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/os/test/src-jvm/PathTestsJvmOnly.scala b/os/test/src-jvm/PathTestsJvmOnly.scala index 87e37319..86a04ed2 100644 --- a/os/test/src-jvm/PathTestsJvmOnly.scala +++ b/os/test/src-jvm/PathTestsJvmOnly.scala @@ -44,18 +44,18 @@ object PathTestsJvmOnly extends TestSuite { names.foreach(p => assert(!exists(twd / p))) } } - test("custom filesystem") { // native doesnt support custom fs yet - val path: java.nio.file.Path = java.nio.file.Paths.get("foo.jar"); - val uri = new URI("jar", path.toUri().toString(), null); + test("custom filesystem") { // native doesnt support custom fs yet + val path: java.nio.file.Path = java.nio.file.Paths.get("foo.jar"); + val uri = new URI("jar", path.toUri().toString(), null); - val env = new HashMap[String, String](); - env.put("create", "true"); + val env = new HashMap[String, String](); + env.put("create", "true"); - val fileSystem = FileSystems.newFileSystem(uri, env); - val p = os.root("/", fileSystem) / "test" / "dir" - assert(p.root == "/") - assert(p.fileSystem == fileSystem) - } + val fileSystem = FileSystems.newFileSystem(uri, env); + val p = os.root("/", fileSystem) / "test" / "dir" + assert(p.root == "/") + assert(p.fileSystem == fileSystem) + } } } } From bcd2be31540592f17dc44964a3c177bd3d4e5f56 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Thu, 12 Oct 2023 15:59:26 +0200 Subject: [PATCH 06/11] Fix for windows --- os/src/Path.scala | 8 ++++-- .../src-jvm/PathTestsCustomFilesystem.scala | 26 +++++++++++++++++++ os/test/src-jvm/PathTestsJvmOnly.scala | 12 --------- 3 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 os/test/src-jvm/PathTestsCustomFilesystem.scala diff --git a/os/src/Path.scala b/os/src/Path.scala index 2894abb0..4edcc252 100644 --- a/os/src/Path.scala +++ b/os/src/Path.scala @@ -5,6 +5,7 @@ import java.nio.file.Paths import collection.JavaConverters._ import scala.language.implicitConversions +import java.nio.file trait PathChunk { def segments: Seq[String] @@ -400,11 +401,12 @@ object Path { def apply[T: PathConvertible](f: T, base: Path): Path = apply(FilePath(f), base) def apply[T: PathConvertible](f0: T): Path = { + val pathConvertible = implicitly[PathConvertible[T]] // drive letter prefix is empty unless running in Windows. - val f = if (driveRelative(f0)) { + val f = if (!pathConvertible.isCustomFs(f0) && driveRelative(f0)) { Paths.get(s"$driveRoot$f0") } else { - implicitly[PathConvertible[T]].apply(f0) + pathConvertible.apply(f0) } if (f.iterator.asScala.count(_.startsWith("..")) > f.getNameCount / 2) { throw PathError.AbsolutePathOutsideRoot @@ -536,6 +538,7 @@ class Path private[os] (val wrapped: java.nio.file.Path) sealed trait PathConvertible[T] { def apply(t: T): java.nio.file.Path + def isCustomFs(t: T): Boolean = false } object PathConvertible { @@ -547,6 +550,7 @@ object PathConvertible { } implicit object NioPathConvertible extends PathConvertible[java.nio.file.Path] { def apply(t: java.nio.file.Path) = t + override def isCustomFs(t: java.nio.file.Path): Boolean = t.getFileSystem() != java.nio.file.FileSystems.getDefault() } implicit object UriPathConvertible extends PathConvertible[URI] { def apply(uri: URI) = uri.getScheme() match { diff --git a/os/test/src-jvm/PathTestsCustomFilesystem.scala b/os/test/src-jvm/PathTestsCustomFilesystem.scala new file mode 100644 index 00000000..b3bcaf7f --- /dev/null +++ b/os/test/src-jvm/PathTestsCustomFilesystem.scala @@ -0,0 +1,26 @@ +package test.os + +import java.nio.file.Paths + +import os._ +import utest._ +import java.util.HashMap +import java.nio.file.FileSystems +import java.net.URI + +object PathTestsCustomFilesystem extends TestSuite { + val tests = Tests { // native doesnt support custom fs yet + test("custom filesystem") { + val path: java.nio.file.Path = java.nio.file.Paths.get("foo.jar"); + val uri = new URI("jar", path.toUri().toString(), null); + + val env = new HashMap[String, String](); + env.put("create", "true"); + + val fileSystem = FileSystems.newFileSystem(uri, env); + val p = os.root("/", fileSystem) / "test" / "dir" + assert(p.root == "/") + assert(p.fileSystem == fileSystem) + } + } +} \ No newline at end of file diff --git a/os/test/src-jvm/PathTestsJvmOnly.scala b/os/test/src-jvm/PathTestsJvmOnly.scala index 86a04ed2..fbb23ad7 100644 --- a/os/test/src-jvm/PathTestsJvmOnly.scala +++ b/os/test/src-jvm/PathTestsJvmOnly.scala @@ -44,18 +44,6 @@ object PathTestsJvmOnly extends TestSuite { names.foreach(p => assert(!exists(twd / p))) } } - test("custom filesystem") { // native doesnt support custom fs yet - val path: java.nio.file.Path = java.nio.file.Paths.get("foo.jar"); - val uri = new URI("jar", path.toUri().toString(), null); - - val env = new HashMap[String, String](); - env.put("create", "true"); - - val fileSystem = FileSystems.newFileSystem(uri, env); - val p = os.root("/", fileSystem) / "test" / "dir" - assert(p.root == "/") - assert(p.fileSystem == fileSystem) - } } } } From 81ef064afed7a17e1f3737deb5fb586a15daa1a2 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Fri, 13 Oct 2023 16:51:23 +0200 Subject: [PATCH 07/11] Tests, Readme, fixes --- Readme.adoc | 32 +++ os/src/Path.scala | 3 +- .../src-jvm/PathTestsCustomFilesystem.scala | 240 ++++++++++++++++-- 3 files changed, 258 insertions(+), 17 deletions(-) diff --git a/Readme.adoc b/Readme.adoc index 5ac96649..381b20a2 100644 --- a/Readme.adoc +++ b/Readme.adoc @@ -2025,6 +2025,38 @@ Python, ...) do. Even in cases where it's uncertain, e.g. you're taking user input as a String, you have to either handle both possibilities with BasePath or explicitly choose to convert relative paths to absolute using some base. +==== Roots and filesystems + +If you are using a system that supports different roots of paths, e.g. Windows, +you can use the argument of `os.root` to specify which root you want to use. +If not specified, the default root will be used (usually, C on Windows, / on Unix). + +[source,scala] +---- +val root = os.root('C:\') / "Users" / "me" +assert(root == os.Path("C:\Users\me")) +---- + +Additionally, custom filesystems can be specified by passing a `FileSystem` to +`os.root`. This allows you to use OS-Lib with non-standard filesystems, such as +jar filesystems or in-memory filesystems. + +[source,scala] +---- +val uri = new URI("jar", Paths.get("foo.jar").toURI().toString, null); +val env = new HashMap[String, String](); +env.put("create", "true"); +val fs = FileSystems.newFileSystem(uri, env); +val path = os.root("/", fs) / "dir" +---- + +Note that the jar file system operations suchs as writing to a file are supported +only on JVM 11+. Depending on the filesystem, some operations may not be supported - +for example, running an `os.proc` with pwd in a jar file won't work. You may also +meet limitations imposed by the implementations - in jar file system, the files are +created only after the file system is closed. Until that, the ones created in your +program are kept in memory. + ==== `os.ResourcePath` In addition to manipulating paths on the filesystem, you can also manipulate diff --git a/os/src/Path.scala b/os/src/Path.scala index 4edcc252..6eba5cd8 100644 --- a/os/src/Path.scala +++ b/os/src/Path.scala @@ -550,7 +550,8 @@ object PathConvertible { } implicit object NioPathConvertible extends PathConvertible[java.nio.file.Path] { def apply(t: java.nio.file.Path) = t - override def isCustomFs(t: java.nio.file.Path): Boolean = t.getFileSystem() != java.nio.file.FileSystems.getDefault() + override def isCustomFs(t: java.nio.file.Path): Boolean = + t.getFileSystem() != java.nio.file.FileSystems.getDefault() } implicit object UriPathConvertible extends PathConvertible[URI] { def apply(uri: URI) = uri.getScheme() match { diff --git a/os/test/src-jvm/PathTestsCustomFilesystem.scala b/os/test/src-jvm/PathTestsCustomFilesystem.scala index b3bcaf7f..a27815b8 100644 --- a/os/test/src-jvm/PathTestsCustomFilesystem.scala +++ b/os/test/src-jvm/PathTestsCustomFilesystem.scala @@ -1,26 +1,234 @@ package test.os -import java.nio.file.Paths - -import os._ import utest._ +import os._ import java.util.HashMap import java.nio.file.FileSystems import java.net.URI +import java.nio.file.FileSystem +import java.nio.file.Paths object PathTestsCustomFilesystem extends TestSuite { - val tests = Tests { // native doesnt support custom fs yet - test("custom filesystem") { - val path: java.nio.file.Path = java.nio.file.Paths.get("foo.jar"); - val uri = new URI("jar", path.toUri().toString(), null); - - val env = new HashMap[String, String](); - env.put("create", "true"); - - val fileSystem = FileSystems.newFileSystem(uri, env); - val p = os.root("/", fileSystem) / "test" / "dir" - assert(p.root == "/") - assert(p.fileSystem == fileSystem) + + def customFsUri(jarName: String = "foo.jar") = { + val path = java.nio.file.Paths.get(jarName); + path.toUri() + } + + def withCustomFs(f: FileSystem => Unit, fsUri: URI = customFsUri()): Unit = { + val uri = new URI("jar", fsUri.toString(), null); + val env = new HashMap[String, String](); + env.put("create", "true"); + val fs = FileSystems.newFileSystem(uri, env); + val p = os.root("/", fs) + try { + os.makeDir(p / "test") + os.makeDir(p / "test" / "dir") + f(fs) + } finally { + cleanUpFs(fs, fsUri) + } + } + + def cleanUpFs(fs: FileSystem, fsUri: URI): Unit = { + fs.close() + os.remove(Path(fsUri)) + } + + val testsCommon = Tests { // native doesnt support custom fs yet + test("customFilesystem") { + test("createPath") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + assert(p.root == "/") + assert(p.fileSystem == fileSystem) + } + } + test("list") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" + os.makeDir(p / "dir2") + os.makeDir(p / "dir3") + assert(os.list(p).size == 3) + } + } + test("removeDir") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" / "dir2" + os.makeDir.all(p) + assert(os.exists(p)) + os.remove.all(p) + assert(!os.exists(p)) + } + } + test("failTemp") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + intercept[UnsupportedOperationException] { + os.temp.dir(dir = p) + } + } + } + test("failProcCall") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + intercept[UnsupportedOperationException] { + os.proc("echo", "hello").call(cwd = p) + } + } + } + test("up") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + assert((p / os.up) == os.root("/", fileSystem) / "test") + } + } + test("withRelPath") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + val rel = os.rel / os.up / "file.txt" + assert((p / rel) == os.root("/", fileSystem) / "test" / "file.txt") + } + } + test("withSubPath") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + val sub = os.sub / "file.txt" + assert((p / sub) == os.root("/", fileSystem) / "test" / "dir" / "file.txt") + } + } + test("differentFsCompare") { + withCustomFs { fs1 => + withCustomFs({ fs2 => + val p1 = os.root("/", fs1) / "test" / "dir" + val p2 = os.root("/", fs2) / "test" / "dir" + assert(p1 != p2) + }, fsUri = customFsUri("bar.jar")) + } + } + test("failRelativeToDifferentFs") { + withCustomFs { fs1 => + withCustomFs({ fs2 => + val p1 = os.root("/", fs1) / "test" / "dir" + val p2 = os.root("/", fs2) / "test" / "dir" + intercept[IllegalArgumentException] { + p1.relativeTo(p2) + } + }, fsUri = customFsUri("bar.jar")) + } + } + test("failSubRelativeToDifferentFs") { + withCustomFs { fs1 => + withCustomFs({ fs2 => + val p1 = os.root("/", fs1) / "test" / "dir" + val p2 = os.root("/", fs2) / "test" / "dir" + intercept[IllegalArgumentException] { + p1.subRelativeTo(p2) + } + }, fsUri = customFsUri("bar.jar")) + } + } + } + } + + val testsJava11 = Tests { + test("customFilesystem") { + test("writeAndRead") { + withCustomFs { fileSystem => + val p = root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello") + assert(os.read(p / "file.txt") == "Hello") + } + } + test("writeOver") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello World") + os.write.over(p / "file.txt", "Hello World2") + assert(os.read(p / "file.txt") == "Hello World2") + } + } + test("move") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello World") + os.move(p / "file.txt", p / "file2.txt") + assert(os.read(p / "file2.txt") == "Hello World") + assert(!os.exists(p / "file.txt")) + } + } + test("copy") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello World") + os.copy(p / "file.txt", p / "file2.txt") + assert(os.read(p / "file2.txt") == "Hello World") + assert(os.exists(p / "file.txt")) + } + } + test("remove") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello World") + assert(os.exists(p / "file.txt")) + os.remove(p / "file.txt") + assert(!os.exists(p / "file.txt")) + } + } + test("removeAll") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello World") + os.write(p / "file2.txt", "Hello World") + os.remove.all(p) + assert(!os.exists(p / "file.txt")) + assert(!os.exists(p / "file2.txt")) + } + } + test("failSymlink") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello World") + intercept[UnsupportedOperationException] { + os.symlink(p / "link", p / "file.txt") + } + } + } + test("walk") { + withCustomFs { fileSystem => + val p = os.root("/", fileSystem) / "test" / "dir" + os.write(p / "file.txt", "Hello World") + os.write(p / "file2.txt", "Hello World") + os.write(p / "file3.txt", "Hello World") + os.makeDir(p / "dir2") + os.write(p / "dir2" / "file.txt", "Hello World") + assert(os.walk(p).map(_.relativeTo(p)).toSet == + Set(RelPath("file.txt"), RelPath("file2.txt"), RelPath("file3.txt"), RelPath("dir2"), RelPath("dir2/file.txt"))) + } + } + } + } + + val testWindows = Tests { + test("cRootPath") { + val p = os.root("C:\\") + val p2 = os.root("C:") + val p3 = os.root("C") + assert(p == p2) + assert(p == p3) + assert(p.toString == "C:\\") } } -} \ No newline at end of file + + private lazy val isWindows: Boolean = { + sys.props("os.name").toLowerCase().contains("windows") + } + + private lazy val isJava11OrAbove: Boolean = { + val version = System.getProperty("java.version") + val major = version.split("\\.")(0).toInt + major >= 11 + } + + override val tests: Tests = testsCommon ++ (if(isJava11OrAbove) testsJava11 else Tests()) ++ (if(isWindows) testWindows else Tests()) +} From 27e6e7472321587c29bf0891bafc54dfd70d1757 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Fri, 13 Oct 2023 17:13:11 +0200 Subject: [PATCH 08/11] More fixes for Windows and Scala 3 --- os/src/Path.scala | 26 ++++++++++++------- .../src-jvm/PathTestsCustomFilesystem.scala | 11 +++----- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/os/src/Path.scala b/os/src/Path.scala index 6eba5cd8..a228927d 100644 --- a/os/src/Path.scala +++ b/os/src/Path.scala @@ -514,18 +514,24 @@ class Path private[os] (val wrapped: java.nio.file.Path) def endsWith(target: RelPath) = wrapped.endsWith(target.toString) def relativeTo(base: Path): RelPath = { + try { + val nioRel = base.wrapped.relativize(wrapped) + val segments = nioRel.iterator().asScala.map(_.toString).toArray match { + case Array("") => Internals.emptyStringArray + case arr => arr + } + val nonUpIndex = segments.indexWhere(_ != "..") match { + case -1 => segments.length + case n => n + } - val nioRel = base.wrapped.relativize(wrapped) - val segments = nioRel.iterator().asScala.map(_.toString).toArray match { - case Array("") => Internals.emptyStringArray - case arr => arr - } - val nonUpIndex = segments.indexWhere(_ != "..") match { - case -1 => segments.length - case n => n + new RelPath(segments.drop(nonUpIndex), nonUpIndex) + } catch { + case _: NegativeArraySizeException => // Workaround for bug in Windows zipfs implementation + throw new IllegalArgumentException( + s"Cannot relativize $wrapped against $base - different file system" + ) } - - new RelPath(segments.drop(nonUpIndex), nonUpIndex) } def toIO: java.io.File = wrapped.toFile diff --git a/os/test/src-jvm/PathTestsCustomFilesystem.scala b/os/test/src-jvm/PathTestsCustomFilesystem.scala index a27815b8..f1862c66 100644 --- a/os/test/src-jvm/PathTestsCustomFilesystem.scala +++ b/os/test/src-jvm/PathTestsCustomFilesystem.scala @@ -211,15 +211,12 @@ object PathTestsCustomFilesystem extends TestSuite { val testWindows = Tests { test("cRootPath") { - val p = os.root("C:\\") - val p2 = os.root("C:") - val p3 = os.root("C") - assert(p == p2) - assert(p == p3) - assert(p.toString == "C:\\") + val p = os.root("C:\\") / "Users" + assert(p.toString == "C:\\Users") } } + private lazy val isWindows: Boolean = { sys.props("os.name").toLowerCase().contains("windows") } @@ -230,5 +227,5 @@ object PathTestsCustomFilesystem extends TestSuite { major >= 11 } - override val tests: Tests = testsCommon ++ (if(isJava11OrAbove) testsJava11 else Tests()) ++ (if(isWindows) testWindows else Tests()) + override val tests: Tests = testsCommon ++ (if(isJava11OrAbove) testsJava11 else Tests{}) ++ (if(isWindows) testWindows else Tests{}) } From 33d1b98b7182af641a3606a304660fd688ffb376 Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Fri, 13 Oct 2023 17:14:46 +0200 Subject: [PATCH 09/11] Formatting --- .../src-jvm/PathTestsCustomFilesystem.scala | 72 +++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/os/test/src-jvm/PathTestsCustomFilesystem.scala b/os/test/src-jvm/PathTestsCustomFilesystem.scala index f1862c66..6ffd21a9 100644 --- a/os/test/src-jvm/PathTestsCustomFilesystem.scala +++ b/os/test/src-jvm/PathTestsCustomFilesystem.scala @@ -99,33 +99,42 @@ object PathTestsCustomFilesystem extends TestSuite { } test("differentFsCompare") { withCustomFs { fs1 => - withCustomFs({ fs2 => - val p1 = os.root("/", fs1) / "test" / "dir" - val p2 = os.root("/", fs2) / "test" / "dir" - assert(p1 != p2) - }, fsUri = customFsUri("bar.jar")) + withCustomFs( + { fs2 => + val p1 = os.root("/", fs1) / "test" / "dir" + val p2 = os.root("/", fs2) / "test" / "dir" + assert(p1 != p2) + }, + fsUri = customFsUri("bar.jar") + ) } } test("failRelativeToDifferentFs") { withCustomFs { fs1 => - withCustomFs({ fs2 => - val p1 = os.root("/", fs1) / "test" / "dir" - val p2 = os.root("/", fs2) / "test" / "dir" - intercept[IllegalArgumentException] { - p1.relativeTo(p2) - } - }, fsUri = customFsUri("bar.jar")) + withCustomFs( + { fs2 => + val p1 = os.root("/", fs1) / "test" / "dir" + val p2 = os.root("/", fs2) / "test" / "dir" + intercept[IllegalArgumentException] { + p1.relativeTo(p2) + } + }, + fsUri = customFsUri("bar.jar") + ) } } test("failSubRelativeToDifferentFs") { withCustomFs { fs1 => - withCustomFs({ fs2 => - val p1 = os.root("/", fs1) / "test" / "dir" - val p2 = os.root("/", fs2) / "test" / "dir" - intercept[IllegalArgumentException] { - p1.subRelativeTo(p2) - } - }, fsUri = customFsUri("bar.jar")) + withCustomFs( + { fs2 => + val p1 = os.root("/", fs1) / "test" / "dir" + val p2 = os.root("/", fs2) / "test" / "dir" + intercept[IllegalArgumentException] { + p1.subRelativeTo(p2) + } + }, + fsUri = customFsUri("bar.jar") + ) } } } @@ -135,14 +144,14 @@ object PathTestsCustomFilesystem extends TestSuite { test("customFilesystem") { test("writeAndRead") { withCustomFs { fileSystem => - val p = root("/", fileSystem) / "test" / "dir" + val p = root("/", fileSystem) / "test" / "dir" os.write(p / "file.txt", "Hello") assert(os.read(p / "file.txt") == "Hello") } } test("writeOver") { withCustomFs { fileSystem => - val p = os.root("/", fileSystem) / "test" / "dir" + val p = os.root("/", fileSystem) / "test" / "dir" os.write(p / "file.txt", "Hello World") os.write.over(p / "file.txt", "Hello World2") assert(os.read(p / "file.txt") == "Hello World2") @@ -150,7 +159,7 @@ object PathTestsCustomFilesystem extends TestSuite { } test("move") { withCustomFs { fileSystem => - val p = os.root("/", fileSystem) / "test" / "dir" + val p = os.root("/", fileSystem) / "test" / "dir" os.write(p / "file.txt", "Hello World") os.move(p / "file.txt", p / "file2.txt") assert(os.read(p / "file2.txt") == "Hello World") @@ -159,7 +168,7 @@ object PathTestsCustomFilesystem extends TestSuite { } test("copy") { withCustomFs { fileSystem => - val p = os.root("/", fileSystem) / "test" / "dir" + val p = os.root("/", fileSystem) / "test" / "dir" os.write(p / "file.txt", "Hello World") os.copy(p / "file.txt", p / "file2.txt") assert(os.read(p / "file2.txt") == "Hello World") @@ -168,7 +177,7 @@ object PathTestsCustomFilesystem extends TestSuite { } test("remove") { withCustomFs { fileSystem => - val p = os.root("/", fileSystem) / "test" / "dir" + val p = os.root("/", fileSystem) / "test" / "dir" os.write(p / "file.txt", "Hello World") assert(os.exists(p / "file.txt")) os.remove(p / "file.txt") @@ -177,7 +186,7 @@ object PathTestsCustomFilesystem extends TestSuite { } test("removeAll") { withCustomFs { fileSystem => - val p = os.root("/", fileSystem) / "test" / "dir" + val p = os.root("/", fileSystem) / "test" / "dir" os.write(p / "file.txt", "Hello World") os.write(p / "file2.txt", "Hello World") os.remove.all(p) @@ -203,7 +212,13 @@ object PathTestsCustomFilesystem extends TestSuite { os.makeDir(p / "dir2") os.write(p / "dir2" / "file.txt", "Hello World") assert(os.walk(p).map(_.relativeTo(p)).toSet == - Set(RelPath("file.txt"), RelPath("file2.txt"), RelPath("file3.txt"), RelPath("dir2"), RelPath("dir2/file.txt"))) + Set( + RelPath("file.txt"), + RelPath("file2.txt"), + RelPath("file3.txt"), + RelPath("dir2"), + RelPath("dir2/file.txt") + )) } } } @@ -216,7 +231,6 @@ object PathTestsCustomFilesystem extends TestSuite { } } - private lazy val isWindows: Boolean = { sys.props("os.name").toLowerCase().contains("windows") } @@ -227,5 +241,7 @@ object PathTestsCustomFilesystem extends TestSuite { major >= 11 } - override val tests: Tests = testsCommon ++ (if(isJava11OrAbove) testsJava11 else Tests{}) ++ (if(isWindows) testWindows else Tests{}) + override val tests: Tests = + testsCommon ++ (if (isJava11OrAbove) testsJava11 else Tests {}) ++ (if (isWindows) testWindows + else Tests {}) } From 121002a4240a48ccadba650168596953f01f673b Mon Sep 17 00:00:00 2001 From: Szymon Rodziewicz Date: Fri, 13 Oct 2023 17:46:45 +0200 Subject: [PATCH 10/11] Add guard condition instead of rethrowing error --- os/src/Path.scala | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/os/src/Path.scala b/os/src/Path.scala index a228927d..d9fb0554 100644 --- a/os/src/Path.scala +++ b/os/src/Path.scala @@ -514,24 +514,22 @@ class Path private[os] (val wrapped: java.nio.file.Path) def endsWith(target: RelPath) = wrapped.endsWith(target.toString) def relativeTo(base: Path): RelPath = { - try { - val nioRel = base.wrapped.relativize(wrapped) - val segments = nioRel.iterator().asScala.map(_.toString).toArray match { - case Array("") => Internals.emptyStringArray - case arr => arr - } - val nonUpIndex = segments.indexWhere(_ != "..") match { - case -1 => segments.length - case n => n - } - - new RelPath(segments.drop(nonUpIndex), nonUpIndex) - } catch { - case _: NegativeArraySizeException => // Workaround for bug in Windows zipfs implementation - throw new IllegalArgumentException( - s"Cannot relativize $wrapped against $base - different file system" - ) + if (fileSystem != base.fileSystem) { + throw new IllegalArgumentException( + s"Paths $wrapped and $base are on different filesystems" + ) + } + val nioRel = base.wrapped.relativize(wrapped) + val segments = nioRel.iterator().asScala.map(_.toString).toArray match { + case Array("") => Internals.emptyStringArray + case arr => arr } + val nonUpIndex = segments.indexWhere(_ != "..") match { + case -1 => segments.length + case n => n + } + + new RelPath(segments.drop(nonUpIndex), nonUpIndex) } def toIO: java.io.File = wrapped.toFile From e61bf06f20c4e53e59294434a33c8cda8d30e758 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 18 Oct 2023 18:25:44 +0800 Subject: [PATCH 11/11] add-exclude --- build.sc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.sc b/build.sc index 693a3280..80437aea 100644 --- a/build.sc +++ b/build.sc @@ -5,7 +5,7 @@ import $ivy.`com.github.lolgab::mill-mima::0.0.24` // imports import mill._, scalalib._, scalanativelib._, publish._ import mill.scalalib.api.ZincWorkerUtil -import com.github.lolgab.mill.mima.Mima +import com.github.lolgab.mill.mima._ import de.tobiasroeser.mill.vcs.version.VcsVersion val communityBuildDottyVersion = sys.props.get("dottyVersion").toList @@ -53,6 +53,9 @@ trait SafeDeps extends ScalaModule { trait MiMaChecks extends Mima { def mimaPreviousVersions = Seq("0.9.0", "0.9.1") + override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = Seq( + ProblemFilter.exclude[ReversedMissingMethodProblem]("os.PathConvertible.isCustomFs") + ) } trait OsLibModule