Skip to content

Commit af2fd36

Browse files
Simon Rlihaoyi
andauthored
Add root to Path & root constructor (#196)
Resolve #170 This PR adds a `def root(root: String, fileSystem: FileSystem): Path` in `os` package object. Additionally, it adds `root` and `filesystem` members to `Path`. It addresses two problems: - Specifying custom roots for a path - Using custom filesystems ## Filesystem Filesystem was added as a field of the `os.Path`. It was not added as a Root field for simplicity and consistency with Java's `nio`. Root is a part of the path; filesystem is the context of the whole path. One filesystem can have many roots; one root can have many subdirectories. ## Root as String A string is the simplest type that can represent the path's root. It also corresponds to the mental model of the path - usually, developers perceive it as a String. Therefore - String was chosen as the type of root. Pull request: #196 --------- Co-authored-by: Li Haoyi <[email protected]>
1 parent 6bfcf30 commit af2fd36

File tree

8 files changed

+328
-4
lines changed

8 files changed

+328
-4
lines changed

Readme.adoc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,38 @@ Python, ...) do. Even in cases where it's uncertain, e.g. you're taking user
20252025
input as a String, you have to either handle both possibilities with BasePath or
20262026
explicitly choose to convert relative paths to absolute using some base.
20272027

2028+
==== Roots and filesystems
2029+
2030+
If you are using a system that supports different roots of paths, e.g. Windows,
2031+
you can use the argument of `os.root` to specify which root you want to use.
2032+
If not specified, the default root will be used (usually, C on Windows, / on Unix).
2033+
2034+
[source,scala]
2035+
----
2036+
val root = os.root('C:\') / "Users" / "me"
2037+
assert(root == os.Path("C:\Users\me"))
2038+
----
2039+
2040+
Additionally, custom filesystems can be specified by passing a `FileSystem` to
2041+
`os.root`. This allows you to use OS-Lib with non-standard filesystems, such as
2042+
jar filesystems or in-memory filesystems.
2043+
2044+
[source,scala]
2045+
----
2046+
val uri = new URI("jar", Paths.get("foo.jar").toURI().toString, null);
2047+
val env = new HashMap[String, String]();
2048+
env.put("create", "true");
2049+
val fs = FileSystems.newFileSystem(uri, env);
2050+
val path = os.root("/", fs) / "dir"
2051+
----
2052+
2053+
Note that the jar file system operations suchs as writing to a file are supported
2054+
only on JVM 11+. Depending on the filesystem, some operations may not be supported -
2055+
for example, running an `os.proc` with pwd in a jar file won't work. You may also
2056+
meet limitations imposed by the implementations - in jar file system, the files are
2057+
created only after the file system is closed. Until that, the ones created in your
2058+
program are kept in memory.
2059+
20282060
==== `os.ResourcePath`
20292061

20302062
In addition to manipulating paths on the filesystem, you can also manipulate

build.sc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import $ivy.`com.github.lolgab::mill-mima::0.0.24`
55
// imports
66
import mill._, scalalib._, scalanativelib._, publish._
77
import mill.scalalib.api.ZincWorkerUtil
8-
import com.github.lolgab.mill.mima.Mima
8+
import com.github.lolgab.mill.mima._
99
import de.tobiasroeser.mill.vcs.version.VcsVersion
1010

1111
val communityBuildDottyVersion = sys.props.get("dottyVersion").toList
@@ -53,6 +53,9 @@ trait SafeDeps extends ScalaModule {
5353

5454
trait MiMaChecks extends Mima {
5555
def mimaPreviousVersions = Seq("0.9.0", "0.9.1")
56+
override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = Seq(
57+
ProblemFilter.exclude[ReversedMissingMethodProblem]("os.PathConvertible.isCustomFs")
58+
)
5659
}
5760

5861
trait OsLibModule

os/src-jvm/package.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import scala.language.implicitConversions
2+
import java.nio.file.FileSystem
3+
import java.nio.file.FileSystems
4+
import java.nio.file.Paths
25

36
package object os {
47
type Generator[+T] = geny.Generator[T]
@@ -10,6 +13,12 @@ package object os {
1013
*/
1114
val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot)
1215

16+
def root(root: String, fileSystem: FileSystem = FileSystems.getDefault()): Path = {
17+
val path = Path(fileSystem.getPath(root))
18+
assert(path.root == root, s"$root is not a root path")
19+
path
20+
}
21+
1322
def resource(implicit resRoot: ResourceRoot = Thread.currentThread().getContextClassLoader) = {
1423
os.ResourcePath.resource(resRoot)
1524
}

os/src-native/package.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import java.nio.file.FileSystem
2+
import java.nio.file.FileSystems
13
package object os {
24
type Generator[+T] = geny.Generator[T]
35
val Generator = geny.Generator
@@ -8,6 +10,12 @@ package object os {
810
*/
911
val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot)
1012

13+
def root(root: String, fileSystem: FileSystem = FileSystems.getDefault()): Path = {
14+
val path = Path(fileSystem.getPath(root))
15+
assert(path.root == root, s"$root is not a root path")
16+
path
17+
}
18+
1119
/**
1220
* The user's home directory
1321
*/

os/src/Path.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.nio.file.Paths
55

66
import collection.JavaConverters._
77
import scala.language.implicitConversions
8+
import java.nio.file
89

910
trait PathChunk {
1011
def segments: Seq[String]
@@ -400,11 +401,12 @@ object Path {
400401

401402
def apply[T: PathConvertible](f: T, base: Path): Path = apply(FilePath(f), base)
402403
def apply[T: PathConvertible](f0: T): Path = {
404+
val pathConvertible = implicitly[PathConvertible[T]]
403405
// drive letter prefix is empty unless running in Windows.
404-
val f = if (driveRelative(f0)) {
406+
val f = if (!pathConvertible.isCustomFs(f0) && driveRelative(f0)) {
405407
Paths.get(s"$driveRoot$f0")
406408
} else {
407-
implicitly[PathConvertible[T]].apply(f0)
409+
pathConvertible.apply(f0)
408410
}
409411
if (f.iterator.asScala.count(_.startsWith("..")) > f.getNameCount / 2) {
410412
throw PathError.AbsolutePathOutsideRoot
@@ -484,6 +486,9 @@ class Path private[os] (val wrapped: java.nio.file.Path)
484486
new SeekableSource.ChannelSource(java.nio.file.Files.newByteChannel(wrapped))
485487

486488
require(wrapped.isAbsolute || Path.driveRelative(wrapped), s"$wrapped is not an absolute path")
489+
def root = Option(wrapped.getRoot).map(_.toString).getOrElse("")
490+
def fileSystem = wrapped.getFileSystem()
491+
487492
def segments: Iterator[String] = wrapped.iterator().asScala.map(_.toString)
488493
def getSegment(i: Int): String = wrapped.getName(i).toString
489494
def segmentCount = wrapped.getNameCount
@@ -509,7 +514,11 @@ class Path private[os] (val wrapped: java.nio.file.Path)
509514
def endsWith(target: RelPath) = wrapped.endsWith(target.toString)
510515

511516
def relativeTo(base: Path): RelPath = {
512-
517+
if (fileSystem != base.fileSystem) {
518+
throw new IllegalArgumentException(
519+
s"Paths $wrapped and $base are on different filesystems"
520+
)
521+
}
513522
val nioRel = base.wrapped.relativize(wrapped)
514523
val segments = nioRel.iterator().asScala.map(_.toString).toArray match {
515524
case Array("") => Internals.emptyStringArray
@@ -533,6 +542,7 @@ class Path private[os] (val wrapped: java.nio.file.Path)
533542

534543
sealed trait PathConvertible[T] {
535544
def apply(t: T): java.nio.file.Path
545+
def isCustomFs(t: T): Boolean = false
536546
}
537547

538548
object PathConvertible {
@@ -544,6 +554,8 @@ object PathConvertible {
544554
}
545555
implicit object NioPathConvertible extends PathConvertible[java.nio.file.Path] {
546556
def apply(t: java.nio.file.Path) = t
557+
override def isCustomFs(t: java.nio.file.Path): Boolean =
558+
t.getFileSystem() != java.nio.file.FileSystems.getDefault()
547559
}
548560
implicit object UriPathConvertible extends PathConvertible[URI] {
549561
def apply(uri: URI) = uri.getScheme() match {

0 commit comments

Comments
 (0)