diff --git a/os/src-jvm/ResourceApi.scala b/os/src-jvm/ResourceApi.scala new file mode 100644 index 00000000..e6989dfe --- /dev/null +++ b/os/src-jvm/ResourceApi.scala @@ -0,0 +1,7 @@ +package os +trait ResourceApi { + def resource(implicit resRoot: ResourceRoot = Thread.currentThread().getContextClassLoader) = { + os.ResourcePath.resource(resRoot) + } + +} diff --git a/os/src-jvm/package.scala b/os/src-jvm/package.scala deleted file mode 100644 index caa08009..00000000 --- a/os/src-jvm/package.scala +++ /dev/null @@ -1,83 +0,0 @@ -import scala.language.implicitConversions -import java.nio.file.FileSystem -import java.nio.file.FileSystems -import java.nio.file.Paths -import scala.util.DynamicVariable - -package object os { - type Generator[+T] = geny.Generator[T] - val Generator = geny.Generator - implicit def GlobSyntax(s: StringContext): GlobInterpolator = new GlobInterpolator(s) - - /** - * The root of the filesystem - */ - val root: Path = Path(java.nio.file.Paths.get(".").toAbsolutePath.getRoot) - - def root(root: String, fileSystem: FileSystem = FileSystems.getDefault()): Path = { - val path = Path(fileSystem.getPath(root)) - assert(path.root == root || path.root == root.replace('/', '\\'), s"$root is not a root path") - path - } - - def resource(implicit resRoot: ResourceRoot = Thread.currentThread().getContextClassLoader) = { - os.ResourcePath.resource(resRoot) - } - - // See https://github.com/com-lihaoyi/os-lib/pull/239 - // and https://github.com/lightbend/mima/issues/794 - // why the need the inner object to preserve binary compatibility - private object _home { - lazy val value = Path(System.getProperty("user.home")) - } - - /** - * The user's home directory - */ - def home: Path = _home.value - - /** - * The current working directory for this process. - */ - def pwd: Path = dynamicPwdFunction.value() - - private val pwd0 = os.Path(java.nio.file.Paths.get(".").toAbsolutePath) - - /** - * Used to override `pwd` within a certain scope with a generated value - */ - val dynamicPwdFunction: DynamicVariable[() => Path] = new DynamicVariable(() => dynamicPwd.value) - - /** - * Used to override `pwd` within a certain scope with a fixed value - */ - val dynamicPwd: DynamicVariable[Path] = new DynamicVariable(pwd0) - - val up: RelPath = RelPath.up - - val rel: RelPath = RelPath.rel - - val sub: SubPath = SubPath.sub - - @experimental - val checker: DynamicVariable[Checker] = new DynamicVariable[Checker](Checker.Nop) - - /** - * Extractor to let you easily pattern match on [[os.Path]]s. Lets you do - * - * {{{ - * @ val base/segment/filename = pwd - * base: Path = Path(Vector("Users", "haoyi", "Dropbox (Personal)")) - * segment: String = "Workspace" - * filename: String = "Ammonite" - * }}} - * - * To break apart a path and extract various pieces of it. - */ - object / { - def unapply(p: Path): Option[(Path, String)] = { - if (p.segmentCount != 0) Some((p / up, p.last)) - else None - } - } -} diff --git a/os/src-native/ResourceApi.scala b/os/src-native/ResourceApi.scala new file mode 100644 index 00000000..e2e9ffbc --- /dev/null +++ b/os/src-native/ResourceApi.scala @@ -0,0 +1,2 @@ +package os +trait ResourceApi diff --git a/os/src/Path.scala b/os/src/Path.scala index 681bc0eb..c4e3fad9 100644 --- a/os/src/Path.scala +++ b/os/src/Path.scala @@ -7,7 +7,7 @@ import scala.language.implicitConversions import acyclic.skipped import os.PathError.{InvalidSegment, NonCanonicalLiteral} -import scala.util.Try //needed for cross-version defined macros +import scala.util.{DynamicVariable, Try} //needed for cross-version defined macros trait PathChunk { def segments: Seq[String] @@ -438,6 +438,31 @@ object SubPath extends SubPathMacros { } object Path extends PathMacros { + @experimental trait Serializer { + def serializeString(p: os.Path): String + def serializeFile(p: os.Path): java.io.File + def serializePath(p: os.Path): java.nio.file.Path + def deserialize(s: String): java.nio.file.Path + def deserialize(s: java.io.File): java.nio.file.Path + def deserialize(s: java.nio.file.Path): java.nio.file.Path + def deserialize(s: java.net.URI): java.nio.file.Path + } + @experimental val pathSerializer = new DynamicVariable[Serializer](defaultPathSerializer) + @experimental object defaultPathSerializer extends Serializer { + def serializeString(p: os.Path): String = p.wrapped.toString + def serializeFile(p: os.Path): java.io.File = p.wrapped.toFile + def serializePath(p: os.Path): java.nio.file.Path = p.wrapped + def deserialize(s: String) = Paths.get(s) + def deserialize(s: java.io.File) = Paths.get(s.getPath) + def deserialize(s: java.nio.file.Path) = s + def deserialize(s: java.net.URI) = s.getScheme() match { + case "file" => Paths.get(s) + case uriType => + throw new IllegalArgumentException( + s"""os.Path can only be created from a "file" URI scheme, but found "${uriType}"""" + ) + } + } def apply(p: FilePath, base: Path) = p match { case p: RelPath => base / p case p: SubPath => base / p @@ -562,7 +587,7 @@ class Path private[os] (val wrapped: java.nio.file.Path) val resolved = wrapped.resolve(chunk.toString).normalize() new Path(resolved) } - override def toString = wrapped.toString + override def toString = Path.pathSerializer.value.serializeString(this) override def equals(o: Any): Boolean = o match { case p: Path => wrapped.equals(p.wrapped) @@ -593,8 +618,8 @@ class Path private[os] (val wrapped: java.nio.file.Path) new RelPath(segments.drop(nonUpIndex), nonUpIndex) } - def toIO: java.io.File = wrapped.toFile - def toNIO: java.nio.file.Path = wrapped + def toIO: java.io.File = Path.pathSerializer.value.serializeFile(this) + def toNIO: java.nio.file.Path = Path.pathSerializer.value.serializePath(this) def resolveFrom(base: os.Path) = this @@ -608,23 +633,18 @@ sealed trait PathConvertible[T] { object PathConvertible { implicit object StringConvertible extends PathConvertible[String] { - def apply(t: String) = Paths.get(t) + def apply(t: String) = Path.pathSerializer.value.deserialize(t) } implicit object JavaIoFileConvertible extends PathConvertible[java.io.File] { - def apply(t: java.io.File) = Paths.get(t.getPath) + def apply(t: java.io.File) = Path.pathSerializer.value.deserialize(t) } implicit object NioPathConvertible extends PathConvertible[java.nio.file.Path] { - def apply(t: java.nio.file.Path) = t + def apply(t: java.nio.file.Path) = Path.pathSerializer.value.deserialize(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 { - case "file" => Paths.get(uri) - case uriType => - throw new IllegalArgumentException( - s"""os.Path can only be created from a "file" URI scheme, but found "${uriType}"""" - ) - } + def apply(uri: URI) = Path.pathSerializer.value.deserialize(uri) + } } diff --git a/os/src/ProcessOps.scala b/os/src/ProcessOps.scala index 7c4f24c7..e4a30629 100644 --- a/os/src/ProcessOps.scala +++ b/os/src/ProcessOps.scala @@ -607,7 +607,9 @@ case class ProcGroup private[os] (commands: Seq[proc]) { def pipeTo(next: proc) = ProcGroup(commands :+ next) } -private[os] object ProcessOps { +@experimental +object ProcessOps { + val spawnHook = new scala.util.DynamicVariable[os.Path => Unit]({ p => () }) def buildProcess( command: Seq[String], cwd: Path = null, @@ -644,7 +646,9 @@ private[os] object ProcessOps { addToProcessEnv(env) - builder.directory(Option(cwd).getOrElse(os.pwd).toIO) + val dir = Option(cwd).getOrElse(os.pwd) + builder.directory(dir.toIO) + spawnHook.value.apply(dir) builder .command(command: _*) diff --git a/os/src-native/package.scala b/os/src/package.scala similarity index 95% rename from os/src-native/package.scala rename to os/src/package.scala index f0afa876..a854b906 100644 --- a/os/src-native/package.scala +++ b/os/src/package.scala @@ -1,7 +1,10 @@ +import scala.language.implicitConversions import java.nio.file.FileSystem import java.nio.file.FileSystems +import java.nio.file.Paths import scala.util.DynamicVariable -package object os { + +package object os extends ResourceApi { type Generator[+T] = geny.Generator[T] val Generator = geny.Generator implicit def GlobSyntax(s: StringContext): GlobInterpolator = new GlobInterpolator(s)