From 934841f401b5eb68ab3065a8053ea23d77e2d212 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Thu, 20 Oct 2022 14:48:48 +0900 Subject: [PATCH 01/12] Bump dependencies for Scala 3 --- .travis.yml | 3 ++- build.sbt | 20 +++++++++++++++----- project/Dependencies.scala | 8 ++++---- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index bbc25bcd..39f5c690 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ scala: - 2.11.12 - 2.12.13 - 2.13.5 + - 3.2.0 before_install: - git fetch --tags # needed to fetch tags @@ -50,4 +51,4 @@ cache: directories: - $HOME/.cache/coursier - $HOME/.ivy2/cache - - $HOME/.sbt \ No newline at end of file + - $HOME/.sbt diff --git a/build.sbt b/build.sbt index a2e115c7..1b7f27d2 100644 --- a/build.sbt +++ b/build.sbt @@ -20,7 +20,7 @@ inThisBuild( lazy val commonSettings = Seq( organization := s"com.github.$username", scalaVersion := crossScalaVersions.value.find(_.startsWith("2.12")).get, - crossScalaVersions := Seq("2.11.12", "2.12.13", "2.13.5"), // when you change this line, also change .travis.yml + crossScalaVersions := Seq("2.11.12", "2.12.13", "2.13.5", "3.2.0"), // when you change this line, also change .travis.yml crossVersion := CrossVersion.binary, scalacOptions := myScalacOptions(scalaVersion.value, scalacOptions.value), Compile / doc / scalacOptions += "-groups", @@ -54,11 +54,16 @@ lazy val core = (project in file("core")) name := repo, description := "Simple, safe and intuitive I/O in Scala", libraryDependencies ++= Seq( - Dependencies.scalaReflect(scalaVersion.value), Dependencies.commonsio, Dependencies.fastjavaio, - Dependencies.shapeless - ) + Dependencies.shapeless.cross(CrossVersion.for3Use2_13) + ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, _)) => + Seq( + Dependencies.scalaReflect(scalaVersion.value) + ) + case _ => Seq.empty + }) ) lazy val akka = (project in file("akka")) @@ -66,7 +71,12 @@ lazy val akka = (project in file("akka")) .settings( name := s"$repo-akka", description := "Reactive file watcher using Akka actors", - libraryDependencies += Dependencies.akka + libraryDependencies += (CrossVersion.partialVersion(scalaVersion.value) match { + // scala-steward:off + case Some((2, 11)) => "com.typesafe.akka" %% "akka-actor" % "2.5.32" + // scala-steward:on + case _ => Dependencies.akka + }) ) .dependsOn(core % "test->test;compile->compile") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 4dcbaafe..0110080f 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -2,10 +2,10 @@ import sbt._ object Dependencies { def scalaReflect(version: String) = "org.scala-lang" % "scala-reflect" % version % Provided - val akka = "com.typesafe.akka" %% "akka-actor" % "2.5.32" // Used in Akka file watcher - val scalatest = "org.scalatest" %% "scalatest" % "3.2.8" % Test - val shapeless = "com.chuusai" %% "shapeless" % "2.3.4" % Test // For shapeless based Reader/Scanner in tests - val commonsio = "commons-io" % "commons-io" % "2.8.0" % Test // Benchmarks + val akka = "com.typesafe.akka" %% "akka-actor" % "2.6.20" // Used in Akka file watcher + val scalatest = "org.scalatest" %% "scalatest" % "3.2.14" % Test + val shapeless = "com.chuusai" %% "shapeless" % "2.3.4" % Test // For shapeless based Reader/Scanner in tests + val commonsio = "commons-io" % "commons-io" % "2.8.0" % Test // Benchmarks val fastjavaio = "fastjavaio" % "fastjavaio" % "1.0" % Test from "https://github.com/williamfiset/FastJavaIO/releases/download/v1.0/fastjavaio.jar" //Benchmarks } From be32aeeed19cd57ab2e4dca5c7d5cc0824da81ff Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Thu, 20 Oct 2022 14:56:35 +0900 Subject: [PATCH 02/12] Implement macros in Resource for Scala 3 --- .../better/files/Resource.scala | 0 .../main/scala-3/better/files/Resource.scala | 239 ++++++++++++++++++ 2 files changed, 239 insertions(+) rename core/src/main/{scala => scala-2}/better/files/Resource.scala (100%) create mode 100644 core/src/main/scala-3/better/files/Resource.scala diff --git a/core/src/main/scala/better/files/Resource.scala b/core/src/main/scala-2/better/files/Resource.scala similarity index 100% rename from core/src/main/scala/better/files/Resource.scala rename to core/src/main/scala-2/better/files/Resource.scala diff --git a/core/src/main/scala-3/better/files/Resource.scala b/core/src/main/scala-3/better/files/Resource.scala new file mode 100644 index 00000000..507e033f --- /dev/null +++ b/core/src/main/scala-3/better/files/Resource.scala @@ -0,0 +1,239 @@ +package better.files + +import java.io.{IOException, InputStream} +import java.net.URL +import java.nio.charset.Charset + +import scala.quoted.{Quotes, Expr} +import scala.quoted.* + +/** Finds and loads [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) class resources]] or + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) class loader resources]]. + * + * The default implementation of this trait is the [[Resource]] object, which looks up resources using the + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#currentThread() current thread]]'s + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#getContextClassLoader() context class loader]]. The Resource object + * also offers several other Resource implementations, through its methods `at`, `from`, and `my`. `at` searches from a + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]], `from` searches from a + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html ClassLoader]], and `my` searches from the class, trait, or + * object surrounding the call. + * + * @example + * {{{ // Look up the config.properties file for this class or object. Resource.my.asStream("config.properties") + * + * // Find logging.properties (in the root package) somewhere on the classpath. Resource.url("logging.properties") }}} + * + * @see + * [[Resource]] + * @see + * [[https://stackoverflow.com/questions/676250/different-ways-of-loading-a-file-as-an-inputstream Different ways of loading a file as an InputStream]] + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] + */ +trait Resource { + + /** Look up a resource by name, and open an [[https://docs.oracle.com/javase/10/docs/api/java/io/InputStream.html InputStream]] for + * reading it. + * + * @param name + * Name of the resource to search for. + * @return + * InputStream for reading the found resource, if a resource was found. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String) Class#getResourceAsStream]] + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResourceAsStream(java.lang.String) ClassLoader#getResourceAsStream]] + */ + @throws[IOException] + def asStream(name: String): Option[InputStream] = + url(name).map(_.openStream()) + + /** Same as asStream but throws a NoSuchElementException if resource is not found + */ + def getAsStream(name: String): InputStream = + asStream(name).getOrElse(Resource.notFound(name)) + + def asString( + name: String, + bufferSize: Int = DefaultBufferSize + )(implicit + charset: Charset = DefaultCharset + ): Option[String] = + asStream(name).map(_.asString(bufferSize = bufferSize)(charset)) + + def getAsString( + name: String, + bufferSize: Int = DefaultBufferSize + )(implicit + charset: Charset = DefaultCharset + ): String = + asString(name, bufferSize)(charset).getOrElse(Resource.notFound(name)) + + /** Look up a resource by name, and get its [[https://docs.oracle.com/javase/10/docs/api/java/net/URL.html URL]]. + * + * @param name + * Name of the resource to search for. + * @return + * URL of the requested resource. If the resource could not be found or is not accessible, returns None. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] + */ + def url(name: String): Option[URL] + + /** Get URL of given resource A default argument of empty string is provided to conveniently get the root resource URL using + * {{Resource.getUrl()}} + * + * @param name + * @return + */ + def getUrl(name: String = ""): URL = + url(name).getOrElse(Resource.notFound(name)) +} + +/** Implementations of [[Resource]]. + * + * This object itself is a Resource uses the + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#currentThread() current thread]]'s + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#getContextClassLoader() context class loader]]. It also creates + * Resources with different lookup behavior, using the methods `at`, `from`, and `my`. `at` searches rom a + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]], `from` searches from a different + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html ClassLoader]], and `my` searches from the class, trait, or + * object surrounding the call. + * + * @see + * [[Resource]] + * @see + * [[https://stackoverflow.com/questions/676250/different-ways-of-loading-a-file-as-an-inputstream Different ways of loading a file as an InputStream]] + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] + */ +object Resource extends Resource { + + @throws[NoSuchElementException] + def notFound(name: String): Nothing = + throw new NoSuchElementException(s"Could not find resource=${name}") + + override def url(name: String): Option[URL] = + from(Thread.currentThread.getContextClassLoader).url(name) + + /** Look up class resource files. + * + * This Resource looks up resources relative to the JVM class file for `T`, using + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For example, if + * `com.example.ExampleClass` is given for `T`, then resource files will be searched for in the `com/example` folder containing + * `ExampleClass.class`. + * + * If you want to look up resource files relative to the call site instead (that is, you want a class to look up one of its own + * resources), use the `my` method instead. + * + * @example + * {{{Resource.at[YourClass].url("config.properties")}}} + * @tparam T + * The class, trait, or object to look up from. Objects must be written with a `.type` suffix, such as `Resource.at[SomeObject.type]`. + * @return + * A Resource for `T`. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + inline def at[T]: Resource = ${ atStaticImpl[T] } + + private def atStaticImpl[T: Type](using qc: Quotes): Expr[Resource] = { + import qc.reflect.* + val tpe = TypeRepr.of[T] + val typeSymbolStr = tpe.typeSymbol.toString + if (typeSymbolStr.startsWith("class ") || typeSymbolStr.startsWith("module class ")) { + val baseClass = tpe.baseClasses.head + return '{ + new Resource { + override def url(name: String) = Option(Class.forName(${ Expr(baseClass.fullName) }).getResource(name)) + } + } + } else { + report.errorAndAbort(s"${tpe.show} is not a concrete type") + } + } + + /** Look up class resource files. + * + * This Resource looks up resources from the given Class, using + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For example, if + * `classOf[com.example.ExampleClass]` is given for `clazz`, then resource files will be searched for in the `com/example` folder + * containing `ExampleClass.class`. + * + * If you want to look up resource files relative to the call site instead (that is, you want your class to look up one of its own + * resources), use the `my` method instead. + * + * @example + * {{{Resource.at(Class.forName("your.AppClass")).url("config.properties")}}} + * + * In this example, a file named `config.properties` is expected to appear alongside the file `AppClass.class` in the package `your`. + * @param clazz + * The class to look up from. + * @return + * A Resource for `clazz`. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + def at(clazz: Class[_]): Resource = new Resource { + override def url(name: String) = Option(clazz.getResource(name)) + } + + /** Look up own resource files. + * + * This Resource looks up resources from the [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]] surrounding the + * call, using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For + * example, if `my` is called from `com.example.ExampleClass`, then resource files will be searched for in the `com/example` folder + * containing `ExampleClass.class`. + * + * @example + * {{{Resource.my.url("config.properties")}}} + * @return + * A Resource for the call site. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + inline def my: Resource = ${ myImpl } + + private def myImpl(using qc: Quotes): Expr[Resource] = { + import qc.reflect.* + var callee = Symbol.spliceOwner + while (callee != null && callee != Symbol.noSymbol) { + callee = callee.owner + if (callee.isClassDef) { + return '{ + new Resource { + override def url(name: String) = Option(Class.forName(${ Expr(callee.fullName) }).getResource(name)) + } + } + } + } + report.errorAndAbort("this location doesn't correspond to a Java class file") + } + + /** Look up resource files using the specified ClassLoader. + * + * This Resource looks up resources from a specific ClassLoader. Like [[Resource the default Resource]], resource names are relative to + * the root package. + * + * @example + * {{{Resource.from(appClassLoader).url("com/example/config.properties")}}} + * @param cl + * ClassLoader to look up resources from. + * @return + * A Resource that uses the supplied ClassLoader. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] + */ + def from(cl: ClassLoader): Resource = + new Resource { + override def url(name: String): Option[URL] = + Option(cl.getResource(name)) + } + +} From 7f7d6b3efd0bf12828af83e2480d1194cf1c2aec Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Thu, 20 Oct 2022 15:17:34 +0900 Subject: [PATCH 03/12] Add `this.` so can Scala3 understand `extension` is a method here --- core/src/main/scala/better/files/File.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/better/files/File.scala b/core/src/main/scala/better/files/File.scala index 441cf16d..8222ef1d 100644 --- a/core/src/main/scala/better/files/File.scala +++ b/core/src/main/scala/better/files/File.scala @@ -73,7 +73,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g /** @return extension (including the dot) of this file if it is a regular file and has an extension, else None */ def extension: Option[String] = - extension() + this.extension() /** @param includeDot whether the dot should be included in the extension or not * @param includeAll whether all extension tokens should be included, or just the last one e.g. for bundle.tar.gz should it be .tar.gz or .gz From 85543c23a8a977311552fe74ff329e0aa4239855 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Mon, 24 Oct 2022 07:00:42 +0900 Subject: [PATCH 04/12] Symbol literal is no more --- core/src/test/scala/better/files/FileSpec.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/test/scala/better/files/FileSpec.scala b/core/src/test/scala/better/files/FileSpec.scala index 8b82ccb2..a5bd2c46 100644 --- a/core/src/test/scala/better/files/FileSpec.scala +++ b/core/src/test/scala/better/files/FileSpec.scala @@ -60,7 +60,7 @@ class FileSpec extends CommonSpec { t3 = testRoot / "a" / "a1" / "t3.scala.txt" fb = testRoot / "b" b1 = testRoot / "b" / "b1" - b2 = testRoot / 'b / "b2.txt" + b2 = testRoot / Symbol("b") / "b2.txt" Seq(a1, a2, fb) foreach mkdirs Seq(t1, t2) foreach touch } @@ -513,8 +513,8 @@ class FileSpec extends CommonSpec { it should "zip/unzip multiple files" in { File.usingTemporaryDirectory() { dir => - val f1 = (dir / 'f1).touch().appendLines("Line 1", "Line 2") - val f2 = (dir / 'f2).touch().appendLines("Line 3", "Line 4") + val f1 = (dir / Symbol("f1")).touch().appendLines("Line 1", "Line 2") + val f2 = (dir / Symbol("f2")).touch().appendLines("Line 3", "Line 4") val zipFile = (dir / "f.zip").zipIn(Iterator(f1, f2)) val lines = zipFile.newZipInputStream.foldMap(_.lines.toSeq).flatten lines.toSeq shouldEqual Seq("Line 1", "Line 2", "Line 3", "Line 4") @@ -523,8 +523,8 @@ class FileSpec extends CommonSpec { it should "exclude destination zip when it's under directory to be zipped" in { File.usingTemporaryDirectory() { dir => - (dir / 'f1).touch().appendLines("Line 1", "Line 2") - (dir / 'f2).touch().appendLines("Line 3", "Line 4") + (dir / Symbol("f1")).touch().appendLines("Line 1", "Line 2") + (dir / Symbol("f2")).touch().appendLines("Line 3", "Line 4") val zipFile = (dir / "f.zip") val zipped = dir.zipTo(zipFile.path) zipped.unzipTo().listRecursively.toList.map(_.name).forall(!_.contains("zip")) shouldBe true From 1e1393567c7add9ee9ea0d34c534cffbeec605d5 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Mon, 24 Oct 2022 07:01:24 +0900 Subject: [PATCH 05/12] Scala3 complains File==String is always false --- core/src/test/scala/better/files/FileSpec.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/test/scala/better/files/FileSpec.scala b/core/src/test/scala/better/files/FileSpec.scala index a5bd2c46..9fe68092 100644 --- a/core/src/test/scala/better/files/FileSpec.scala +++ b/core/src/test/scala/better/files/FileSpec.scala @@ -214,9 +214,7 @@ class FileSpec extends CommonSpec { (t1 < "hello world").changeExtensionTo(".txt").name shouldBe "t1.txt" //t1.contentType shouldBe Some("text/plain") ("src" / "test").toString should include("better-files") - (t1 == t1.toString) shouldBe false (t1.contentAsString == t1.toString) shouldBe false - (t1 == t1.contentAsString) shouldBe false t1.root shouldEqual fa.root file"/tmp/foo.scala.html".extension shouldBe Some(".html") file"/tmp/foo.scala.html".nameWithoutExtension shouldBe "foo" From a08728783fce56cec0844f93fb452d8054d42f2f Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Mon, 24 Oct 2022 07:01:45 +0900 Subject: [PATCH 06/12] Scala3 complains implicit val should have explicit type --- .../test/scala/better/files/ResourceSpec.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/test/scala/better/files/ResourceSpec.scala b/core/src/test/scala/better/files/ResourceSpec.scala index 15426a3a..ebe879e6 100644 --- a/core/src/test/scala/better/files/ResourceSpec.scala +++ b/core/src/test/scala/better/files/ResourceSpec.scala @@ -1,17 +1,18 @@ package better.files import java.net.{URL, URLClassLoader} +import java.nio.charset.Charset import better.files.test_pkg.ResourceSpecHelper final class ResourceSpec extends CommonSpec { - implicit val charset = java.nio.charset.StandardCharsets.US_ASCII - val testFileText = "This is the test-file.txt file." - val altTestFileText = "This is the another-test-file.txt file." - val testFile = "better/files/test-file.txt" - val testFileRel = "test-file.txt" - val testFileAltRel = "another-test-file.txt" - val testFileFromCL = "files/test-file.txt" + implicit val charset: Charset = java.nio.charset.StandardCharsets.US_ASCII + val testFileText = "This is the test-file.txt file." + val altTestFileText = "This is the another-test-file.txt file." + val testFile = "better/files/test-file.txt" + val testFileRel = "test-file.txt" + val testFileAltRel = "another-test-file.txt" + val testFileFromCL = "files/test-file.txt" "Resource" can "look up from the context class loader" in { assert(Resource.asStream(testFile).get.asString() startsWith testFileText) From fac1efa82910b96f4e3283c4fadb7215001af763 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Mon, 24 Oct 2022 07:23:40 +0900 Subject: [PATCH 07/12] Run Shapeless2-based spec only for Scala 2: Scala 3 requires Shapeless3 --- build.sbt | 4 ++-- .../better/files/ShapelessScannerSpec.scala | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename core/src/test/{scala => scala-2}/better/files/ShapelessScannerSpec.scala (100%) diff --git a/build.sbt b/build.sbt index 1b7f27d2..0a73088b 100644 --- a/build.sbt +++ b/build.sbt @@ -55,11 +55,11 @@ lazy val core = (project in file("core")) description := "Simple, safe and intuitive I/O in Scala", libraryDependencies ++= Seq( Dependencies.commonsio, - Dependencies.fastjavaio, - Dependencies.shapeless.cross(CrossVersion.for3Use2_13) + Dependencies.fastjavaio ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, _)) => Seq( + Dependencies.shapeless, Dependencies.scalaReflect(scalaVersion.value) ) case _ => Seq.empty diff --git a/core/src/test/scala/better/files/ShapelessScannerSpec.scala b/core/src/test/scala-2/better/files/ShapelessScannerSpec.scala similarity index 100% rename from core/src/test/scala/better/files/ShapelessScannerSpec.scala rename to core/src/test/scala-2/better/files/ShapelessScannerSpec.scala From 201fce5aadcd693a4e4c2bc319644d6723dc4c3f Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Mon, 24 Oct 2022 07:24:02 +0900 Subject: [PATCH 08/12] Fix akka error --- akka/src/test/scala/better/files/FileWatcherSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka/src/test/scala/better/files/FileWatcherSpec.scala b/akka/src/test/scala/better/files/FileWatcherSpec.scala index 85885729..1959003b 100644 --- a/akka/src/test/scala/better/files/FileWatcherSpec.scala +++ b/akka/src/test/scala/better/files/FileWatcherSpec.scala @@ -26,7 +26,7 @@ class FileWatcherSpec extends CommonSpec { import java.nio.file.{StandardWatchEventKinds => Events} import FileWatcher._ - import akka.actor.{ActorRef, ActorSystem} + import akka.actor._ implicit val system = ActorSystem() val watcher: ActorRef = dir.newWatcher() From 1ccf8f904af78ca06c0c4c5fa1c8d1fc9c5e7211 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Mon, 24 Oct 2022 22:35:37 +0900 Subject: [PATCH 09/12] Added scalafmt config to support Scala 2/3 cross build --- .scalafmt.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 32e687bc..c149312f 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,3 +1,5 @@ maxColumn = 140 align.preset = more -version=2.7.5 +version = 3.6.0 +project.layout = StandardConvention +runner.dialect = scala213source3 From 993f28405511e03bd09a0c58c5ebba00cc7581e3 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Mon, 24 Oct 2022 23:25:46 +0900 Subject: [PATCH 10/12] Import DSL just before usage so that own ===/!== have take a priority --- core/src/test/scala/better/files/FileSpec.scala | 4 +++- core/src/test/scala/better/files/GlobSpec.scala | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/test/scala/better/files/FileSpec.scala b/core/src/test/scala/better/files/FileSpec.scala index 9fe68092..aaf34017 100644 --- a/core/src/test/scala/better/files/FileSpec.scala +++ b/core/src/test/scala/better/files/FileSpec.scala @@ -278,6 +278,7 @@ class FileSpec extends CommonSpec { // } it should "support equality" in { + import better.files.Dsl._ fa shouldEqual (testRoot / "a") fa shouldNot equal(testRoot / "b") val c1 = fa.md5 @@ -433,7 +434,7 @@ class FileSpec extends CommonSpec { (fb / "t3").exists shouldBe true (fb / "t5" / "t3").exists shouldBe true (fb / "t5" / "t5.txt").contentAsString shouldEqual "Scala Awesome" - assert((fb / "t3") === (fb / "t5" / "t3")) + assert((fb / "t3") isSameContentAs (fb / "t5" / "t3")) } it should "move" in { @@ -489,6 +490,7 @@ class FileSpec extends CommonSpec { zipFile.name should endWith(".zip") def test(output: File) = { + import better.files.Dsl._ (output / "a" / "a1" / "t1.txt").contentAsString shouldEqual "hello world" output === testRoot shouldBe true (output / "a" / "a1" / "t1.txt").overwrite("hello") diff --git a/core/src/test/scala/better/files/GlobSpec.scala b/core/src/test/scala/better/files/GlobSpec.scala index 2dd1284f..502b1d47 100644 --- a/core/src/test/scala/better/files/GlobSpec.scala +++ b/core/src/test/scala/better/files/GlobSpec.scala @@ -128,6 +128,7 @@ class GlobSpec extends CommonSpec with BeforeAndAfterAll { withClue("Result: " + debugPaths(paths) + "Reference: " + debugPaths(refs)) { assert(paths.length === refPaths.length) assert(paths.nonEmpty) + import better.files.Dsl._ paths.sortBy(_.path).zip(refs).foreach({ case (path, refPath) => assert(path === refPath) }) } } From 931ea2a2b0bc76eae6cc4f59752f13b82805a4f0 Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Tue, 25 Oct 2022 07:04:47 +0900 Subject: [PATCH 11/12] Reformat with new scalafmt --- .scalafmt.conf | 3 +++ build.sbt | 26 +++++++++---------- core/src/main/scala/better/files/File.scala | 24 ++++++++--------- .../main/scala/better/files/FileMonitor.scala | 8 +++--- .../main/scala/better/files/Implicits.scala | 6 ++--- .../src/main/scala/better/files/Scanner.scala | 2 +- .../test/scala/better/files/DisposeSpec.scala | 4 +-- .../test/scala/better/files/FileSpec.scala | 20 +++++++------- .../files/benchmarks/ScannerBenchmark.scala | 4 +-- .../better/files/benchmarks/Scanners.scala | 2 +- project/Dependencies.scala | 8 +++--- 11 files changed, 55 insertions(+), 52 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index c149312f..053bb317 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -3,3 +3,6 @@ align.preset = more version = 3.6.0 project.layout = StandardConvention runner.dialect = scala213source3 + +# Added to minimize changes +docstrings.style = keep diff --git a/build.sbt b/build.sbt index 0a73088b..b6fc00f4 100644 --- a/build.sbt +++ b/build.sbt @@ -4,8 +4,8 @@ val repo = "better-files" inThisBuild( List( organization.withRank(KeyRanks.Invisible) := "better.files", - homepage := Some(url(s"https://github.com/$username/$repo")), - licenses := List("MIT" -> url(s"https://github.com/$username/$repo/blob/master/LICENSE")), + homepage := Some(url(s"https://github.com/$username/$repo")), + licenses := List("MIT" -> url(s"https://github.com/$username/$repo/blob/master/LICENSE")), developers := List( Developer( id = username, @@ -18,15 +18,15 @@ inThisBuild( ) lazy val commonSettings = Seq( - organization := s"com.github.$username", - scalaVersion := crossScalaVersions.value.find(_.startsWith("2.12")).get, + organization := s"com.github.$username", + scalaVersion := crossScalaVersions.value.find(_.startsWith("2.12")).get, crossScalaVersions := Seq("2.11.12", "2.12.13", "2.13.5", "3.2.0"), // when you change this line, also change .travis.yml - crossVersion := CrossVersion.binary, - scalacOptions := myScalacOptions(scalaVersion.value, scalacOptions.value), + crossVersion := CrossVersion.binary, + scalacOptions := myScalacOptions(scalaVersion.value, scalacOptions.value), Compile / doc / scalacOptions += "-groups", libraryDependencies += Dependencies.scalatest, Compile / compile := (Compile / compile).dependsOn(formatAll).value, - Test / test := (Test / test).dependsOn(checkFormat).value, + Test / test := (Test / test).dependsOn(checkFormat).value, formatAll := { (Compile / scalafmt).value (Test / scalafmt).value @@ -51,7 +51,7 @@ def myScalacOptions(scalaVersion: String, suggestedOptions: Seq[String]): Seq[St lazy val core = (project in file("core")) .settings(commonSettings: _*) .settings( - name := repo, + name := repo, description := "Simple, safe and intuitive I/O in Scala", libraryDependencies ++= Seq( Dependencies.commonsio, @@ -69,7 +69,7 @@ lazy val core = (project in file("core")) lazy val akka = (project in file("akka")) .settings(commonSettings: _*) .settings( - name := s"$repo-akka", + name := s"$repo-akka", description := "Reactive file watcher using Akka actors", libraryDependencies += (CrossVersion.partialVersion(scalaVersion.value) match { // scala-steward:off @@ -90,12 +90,12 @@ lazy val root = (project in file(".")) .aggregate(core, akka) lazy val docSettings = Seq( - autoAPIMappings := true, + autoAPIMappings := true, ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(core, akka), - siteSourceDirectory := baseDirectory.value / "site", - ScalaUnidoc / siteSubdirName := "latest/api", + siteSourceDirectory := baseDirectory.value / "site", + ScalaUnidoc / siteSubdirName := "latest/api", addMappingsToSiteDir(ScalaUnidoc / packageDoc / mappings, ScalaUnidoc / siteSubdirName), - git.remoteRepo := s"git@github.com:$username/$repo.git", + git.remoteRepo := s"git@github.com:$username/$repo.git", ghpagesPushSite / envVars += ("SBT_GHPAGES_COMMIT_MESSAGE" -> s"Publishing Scaladoc [CI SKIP]") ) diff --git a/core/src/main/scala/better/files/File.scala b/core/src/main/scala/better/files/File.scala index 8222ef1d..91a4ddfc 100644 --- a/core/src/main/scala/better/files/File.scala +++ b/core/src/main/scala/better/files/File.scala @@ -20,7 +20,7 @@ import scala.util.matching.Regex /** Scala wrapper around java.nio.files.Path */ @SerialVersionUID(3435L) class File private (val path: Path)(implicit val fileSystem: FileSystem = path.getFileSystem) extends Serializable { - //TODO: LinkOption? + // TODO: LinkOption? def pathAsString: String = path.toString @@ -231,7 +231,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g contains(child) def bytes: Iterator[Byte] = - newInputStream.buffered.bytes //TODO: Dispose here? + newInputStream.buffered.bytes // TODO: Dispose here? def loadBytes: Array[Byte] = Files.readAllBytes(path) @@ -270,7 +270,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g } def chars(implicit charset: Charset = DefaultCharset): Iterator[Char] = - newBufferedReader(charset).chars //TODO: Dispose here? + newBufferedReader(charset).chars // TODO: Dispose here? /** Load all lines from this file * Note: Large files may cause an OutOfMemory in which case, use the streaming version @see lineIterator @@ -392,7 +392,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g new RandomAccessFile(toJava, mode.value) def randomAccess(mode: File.RandomAccessMode = File.RandomAccessMode.read): Dispose[RandomAccessFile] = - newRandomAccess(mode).autoClosed //TODO: Mode enum? + newRandomAccess(mode).autoClosed // TODO: Mode enum? def newBufferedReader(implicit charset: Charset = DefaultCharset): BufferedReader = Files.newBufferedReader(path, charset) @@ -740,7 +740,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g )(implicit visitOptions: File.VisitOptions = File.VisitOptions.default ): Iterator[File] = - Files.walk(path, maxDepth, visitOptions: _*) //TODO: that ignores I/O errors? + Files.walk(path, maxDepth, visitOptions: _*) // TODO: that ignores I/O errors? def pathMatcher(syntax: File.PathMatcherSyntax, includePath: Boolean)(pattern: String): PathMatcher = syntax(this, pattern, includePath) @@ -754,7 +754,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g * @param maxDepth Recurse up to maxDepth * @return Set of files that matched */ - //TODO: Consider removing `syntax` as implicit. You often want to control this on a per method call basis + // TODO: Consider removing `syntax` as implicit. You often want to control this on a per method call basis def glob( pattern: String, includePath: Boolean = true, @@ -964,7 +964,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g if (isDirectory(linkOption)) list.toList.foreach(_.delete(swallowIOExceptions, linkOption)) Files.delete(path) } catch { - case _: IOException if swallowIOExceptions => //e.printStackTrace() //swallow + case _: IOException if swallowIOExceptions => // e.printStackTrace() //swallow } this } @@ -1004,7 +1004,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g )(implicit copyOptions: File.CopyOptions = File.CopyOptions(overwrite) ): destination.type = { - if (isDirectory) { //TODO: maxDepth? + if (isDirectory) { // TODO: maxDepth? Files.walkFileTree( path, new SimpleFileVisitor[Path] { @@ -1104,7 +1104,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g */ def isEmpty(implicit linkOptions: File.LinkOptions = File.LinkOptions.default): Boolean = { if (isDirectory(linkOptions)) { - Files.list(path).autoClosed(_.count()) == 0 //Do not use children.isEmpty as it may leave stream open + Files.list(path).autoClosed(_.count()) == 0 // Do not use children.isEmpty as it may leave stream open } else if (isRegularFile(linkOptions)) { toJava.length() == 0 } else { @@ -1142,7 +1142,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g if (isDirectory(linkOption)) list.toList.foreach(_.deleteOnExit(swallowIOExceptions, linkOption)) toJava.deleteOnExit() } catch { - case _: IOException if swallowIOExceptions => //e.printStackTrace() //swallow + case _: IOException if swallowIOExceptions => // e.printStackTrace() //swallow } this } @@ -1288,7 +1288,7 @@ class File private (val path: Path)(implicit val fileSystem: FileSystem = path.g def toTemporary: Dispose[File] = new Dispose(this)(Disposable.fileDisposer) - //TODO: add features from https://github.com/sbt/io + // TODO: add features from https://github.com/sbt/io } object File { @@ -1413,7 +1413,7 @@ object File { object CopyOptions { def apply(overwrite: Boolean): CopyOptions = (if (overwrite) Seq(StandardCopyOption.REPLACE_EXISTING) else default) ++ LinkOptions.default - val default: CopyOptions = Seq.empty //Seq(StandardCopyOption.COPY_ATTRIBUTES) + val default: CopyOptions = Seq.empty // Seq(StandardCopyOption.COPY_ATTRIBUTES) val atomically: CopyOptions = Seq(StandardCopyOption.ATOMIC_MOVE) } diff --git a/core/src/main/scala/better/files/FileMonitor.scala b/core/src/main/scala/better/files/FileMonitor.scala index 45ca5ce6..724044f8 100644 --- a/core/src/main/scala/better/files/FileMonitor.scala +++ b/core/src/main/scala/better/files/FileMonitor.scala @@ -65,9 +65,9 @@ abstract class FileMonitor(val root: File, maxDepth: Int) extends File.Monitor { override def close() = service.close() // Although this class is abstract, we provide noop implementations so user can choose to implement a subset of these - override def onCreate(file: File, count: Int) = {} - override def onModify(file: File, count: Int) = {} - override def onDelete(file: File, count: Int) = {} + override def onCreate(file: File, count: Int) = {} + override def onModify(file: File, count: Int) = {} + override def onDelete(file: File, count: Int) = {} override def onUnknownEvent(event: WatchEvent[_]) = {} - override def onException(exception: Throwable) = {} + override def onException(exception: Throwable) = {} } diff --git a/core/src/main/scala/better/files/Implicits.scala b/core/src/main/scala/better/files/Implicits.scala index df762393..237a9307 100644 --- a/core/src/main/scala/better/files/Implicits.scala +++ b/core/src/main/scala/better/files/Implicits.scala @@ -19,7 +19,7 @@ import java.net.URI */ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits with Scanner.Source.Implicits { - //TODO: Rename all Ops to Extensions + // TODO: Rename all Ops to Extensions implicit class StringInterpolations(sc: StringContext) { def file(args: Any*): File = @@ -393,7 +393,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi * @return the extracted file */ def extractTo(rootDir: File, inputStream: => InputStream): File = { - val entryName = entry.getName.replace("\\", "/") //see https://github.com/pathikrit/better-files/issues/262 + val entryName = entry.getName.replace("\\", "/") // see https://github.com/pathikrit/better-files/issues/262 val child = rootDir.createChild(entryName, asDirectory = entry.isDirectory, createParents = true) if (!entry.isDirectory) child.outputStream.foreach(inputStream.pipeTo(_)) child @@ -442,7 +442,7 @@ trait Implicits extends Dispose.FlatMap.Implicits with Scanner.Read.Implicits wi implicit def tokenizerToIterator(s: StringTokenizer): Iterator[String] = Iterator.continually(s.nextToken()).withHasNext(s.hasMoreTokens) - //implicit def posixPermissionToFileAttribute(perm: PosixFilePermission) = + // implicit def posixPermissionToFileAttribute(perm: PosixFilePermission) = // PosixFilePermissions.asFileAttribute(Set(perm)) private[files] implicit def pathStreamToFiles(files: JStream[Path]): Iterator[File] = diff --git a/core/src/main/scala/better/files/Scanner.scala b/core/src/main/scala/better/files/Scanner.scala index d4e12070..783d51e1 100644 --- a/core/src/main/scala/better/files/Scanner.scala +++ b/core/src/main/scala/better/files/Scanner.scala @@ -71,7 +71,7 @@ object Scanner { trait Implicits { implicit val stringRead: Read[String] = Read(identity) implicit val booleanRead: Read[Boolean] = Read(_.toBoolean) - implicit val byteRead: Read[Byte] = Read(_.toByte) //TODO: https://issues.scala-lang.org/browse/SI-9706 + implicit val byteRead: Read[Byte] = Read(_.toByte) // TODO: https://issues.scala-lang.org/browse/SI-9706 implicit val shortRead: Read[Short] = Read(_.toShort) implicit val intRead: Read[Int] = Read(_.toInt) implicit val longRead: Read[Long] = Read(_.toLong) diff --git a/core/src/test/scala/better/files/DisposeSpec.scala b/core/src/test/scala/better/files/DisposeSpec.scala index 001f0ba9..58ec9843 100644 --- a/core/src/test/scala/better/files/DisposeSpec.scala +++ b/core/src/test/scala/better/files/DisposeSpec.scala @@ -261,8 +261,8 @@ class DisposeSpec extends CommonSpec { for { pw <- f.printWriter() // TODO: Following couple of lines fails here https://travis-ci.com/github/pathikrit/better-files/jobs/500762452 - //_ :: rows = data - //row <- rows + // _ :: rows = data + // row <- rows row <- data.tail } pw.println(row) diff --git a/core/src/test/scala/better/files/FileSpec.scala b/core/src/test/scala/better/files/FileSpec.scala index aaf34017..7ce9635f 100644 --- a/core/src/test/scala/better/files/FileSpec.scala +++ b/core/src/test/scala/better/files/FileSpec.scala @@ -29,7 +29,7 @@ class FileSpec extends CommonSpec { } } - var testRoot: File = _ //TODO: Get rid of mutable test vars + var testRoot: File = _ // TODO: Get rid of mutable test vars var fa: File = _ var a1: File = _ var a2: File = _ @@ -70,10 +70,10 @@ class FileSpec extends CommonSpec { } override def withFixture(test: NoArgTest) = { - //val before = File.numberOfOpenFileDescriptors() + // val before = File.numberOfOpenFileDescriptors() val result = super.withFixture(test) - //val after = File.numberOfOpenFileDescriptors() - //assert(before == after, s"Resource leakage detected in $test") + // val after = File.numberOfOpenFileDescriptors() + // assert(before == after, s"Resource leakage detected in $test") result } @@ -85,7 +85,7 @@ class FileSpec extends CommonSpec { val f2: File = "/User/johndoe/Documents".toFile // convert a string path to a file val f3: File = new JFile("/User/johndoe/Documents").toScala // convert a Java file to Scala val f4: File = root / "User" / "johndoe" / "Documents" // using root helper to start from root - //val f5: File = `~` / "Documents" // also equivalent to `home / "Documents"` + // val f5: File = `~` / "Documents" // also equivalent to `home / "Documents"` val f6: File = "/User" / "johndoe" / "Documents" // using file separator DSL val f7: File = home / "Documents" / "presentations" / `..` // Use `..` to navigate up to parent val f8: File = root / "User" / "johndoe" / "Documents" / `.` @@ -212,7 +212,7 @@ class FileSpec extends CommonSpec { t1.nameWithoutExtension shouldBe "t1" t1.changeExtensionTo(".md").name shouldBe "t1.md" (t1 < "hello world").changeExtensionTo(".txt").name shouldBe "t1.txt" - //t1.contentType shouldBe Some("text/plain") + // t1.contentType shouldBe Some("text/plain") ("src" / "test").toString should include("better-files") (t1.contentAsString == t1.toString) shouldBe false t1.root shouldEqual fa.root @@ -325,10 +325,10 @@ class FileSpec extends CommonSpec { fa.ownerName should not be empty fa.groupName should not be empty a[java.nio.file.attribute.UserPrincipalNotFoundException] should be thrownBy chown("hitler", fa) - //a[java.nio.file.FileSystemException] should be thrownBy chown("root", fa) + // a[java.nio.file.FileSystemException] should be thrownBy chown("root", fa) a[java.nio.file.attribute.UserPrincipalNotFoundException] should be thrownBy chgrp("cool", fa) - //a[java.nio.file.FileSystemException] should be thrownBy chown("admin", fa) - //fa.chown("nobody").chgrp("nobody") + // a[java.nio.file.FileSystemException] should be thrownBy chown("admin", fa) + // fa.chown("nobody").chgrp("nobody") stat(t1) shouldBe a[java.nio.file.attribute.PosixFileAttributes] } @@ -592,7 +592,7 @@ class FileSpec extends CommonSpec { class Person(val name: String, val age: Int) extends Serializable val p1 = new Person("Chris", 34) - File.temporaryFile() foreach { f => //serialization round-trip test + File.temporaryFile() foreach { f => // serialization round-trip test assert(f.isEmpty) f.writeSerialized(p1) assert(f.nonEmpty) diff --git a/core/src/test/scala/better/files/benchmarks/ScannerBenchmark.scala b/core/src/test/scala/better/files/benchmarks/ScannerBenchmark.scala index 1c4ceeb6..25c68d08 100644 --- a/core/src/test/scala/better/files/benchmarks/ScannerBenchmark.scala +++ b/core/src/test/scala/better/files/benchmarks/ScannerBenchmark.scala @@ -36,7 +36,7 @@ class ScannerBenchmark extends Benchmark { repeat(n) { assert(scanner.hasNext) val ints = List.fill(2 * n + 1)(scanner.nextInt()) - val line = "" //scanner.nextLine() + val line = "" // scanner.nextLine() val words = IndexedSeq.fill(2 * n)(scanner.next()) (line, ints, words) } @@ -61,7 +61,7 @@ class ScannerBenchmark extends Benchmark { val l = scanner.nextLine() assert(l == "Hello World", l) assert(scanner.nextInt() == 19) - //assert(!scanner.hasNext) + // assert(!scanner.hasNext) } info("Running benchmark ...") diff --git a/core/src/test/scala/better/files/benchmarks/Scanners.scala b/core/src/test/scala/better/files/benchmarks/Scanners.scala index d9cba41a..0bdb05ed 100644 --- a/core/src/test/scala/better/files/benchmarks/Scanners.scala +++ b/core/src/test/scala/better/files/benchmarks/Scanners.scala @@ -123,7 +123,7 @@ class FastJavaIOScanner(reader: BufferedReader) extends AbstractScanner(reader) private[this] val fastReader = new fastjavaio.InputReader(is) - override def hasNext = true //TODO: https://github.com/williamfiset/FastJavaIO/issues/3 + override def hasNext = true // TODO: https://github.com/williamfiset/FastJavaIO/issues/3 override def next() = fastReader.readStr() override def nextInt() = fastReader.readInt() override def nextLine() = fastReader.readLine() diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 0110080f..08163d43 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,11 +1,11 @@ import sbt._ object Dependencies { - def scalaReflect(version: String) = "org.scala-lang" % "scala-reflect" % version % Provided + def scalaReflect(version: String) = "org.scala-lang" % "scala-reflect" % version % Provided val akka = "com.typesafe.akka" %% "akka-actor" % "2.6.20" // Used in Akka file watcher val scalatest = "org.scalatest" %% "scalatest" % "3.2.14" % Test - val shapeless = "com.chuusai" %% "shapeless" % "2.3.4" % Test // For shapeless based Reader/Scanner in tests - val commonsio = "commons-io" % "commons-io" % "2.8.0" % Test // Benchmarks + val shapeless = "com.chuusai" %% "shapeless" % "2.3.4" % Test // For shapeless based Reader/Scanner in tests + val commonsio = "commons-io" % "commons-io" % "2.8.0" % Test // Benchmarks val fastjavaio = - "fastjavaio" % "fastjavaio" % "1.0" % Test from "https://github.com/williamfiset/FastJavaIO/releases/download/v1.0/fastjavaio.jar" //Benchmarks + "fastjavaio" % "fastjavaio" % "1.0" % Test from "https://github.com/williamfiset/FastJavaIO/releases/download/v1.0/fastjavaio.jar" // Benchmarks } From 36af5739c5820c723e9b8f6d6663b573cdf7133d Mon Sep 17 00:00:00 2001 From: TATSUNO Yasuhiro Date: Wed, 26 Oct 2022 07:14:17 +0900 Subject: [PATCH 12/12] Extract ResourceScalaCompat so the other parts can be shared by Scala 2 and 3 --- .../better/files/ResourceScalaCompat.scala | 90 +++++++ .../main/scala-3/better/files/Resource.scala | 239 ------------------ .../better/files/ResourceScalaCompat.scala | 116 +++++++++ .../better/files/Resource.scala | 90 +------ 4 files changed, 207 insertions(+), 328 deletions(-) create mode 100644 core/src/main/scala-2/better/files/ResourceScalaCompat.scala delete mode 100644 core/src/main/scala-3/better/files/Resource.scala create mode 100644 core/src/main/scala-3/better/files/ResourceScalaCompat.scala rename core/src/main/{scala-2 => scala}/better/files/Resource.scala (59%) diff --git a/core/src/main/scala-2/better/files/ResourceScalaCompat.scala b/core/src/main/scala-2/better/files/ResourceScalaCompat.scala new file mode 100644 index 00000000..3848d17f --- /dev/null +++ b/core/src/main/scala-2/better/files/ResourceScalaCompat.scala @@ -0,0 +1,90 @@ +package better.files + +import scala.language.experimental.macros +import scala.reflect.macros.{ReificationException, blackbox} + +private[files] trait ResourceScalaCompat { + + /** Look up class resource files. + * + * This Resource looks up resources relative to the JVM class file for `T`, + * using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. + * For example, if `com.example.ExampleClass` is given for `T`, then resource files will be searched for in the `com/example` folder containing `ExampleClass.class`. + * + * If you want to look up resource files relative to the call site instead (that is, you want a class to look up one of its own resources), use the `my` method instead. + * + * @example {{{ Resource.at[YourClass].url("config.properties") }}} + * @tparam T The class, trait, or object to look up from. Objects must be written with a `.type` suffix, such as `Resource.at[SomeObject.type]`. + * @return A Resource for `T`. + * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + def at[T]: Resource = macro Macros.atStaticImpl[T] + + /** Look up class resource files. + * + * This Resource looks up resources from the given Class, + * using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. + * For example, if `classOf[com.example.ExampleClass]` is given for `clazz`, then resource files will be searched for + * in the `com/example` folder containing `ExampleClass.class`. + * + * If you want to look up resource files relative to the call site instead (that is, you want your class to look up one of its own resources), + * use the `my` method instead. + * + * @example {{{ Resource.at(Class.forName("your.AppClass")).url("config.properties") }}} + * + * In this example, a file named `config.properties` is expected to appear alongside the file `AppClass.class` in the package `your`. + * @param clazz The class to look up from. + * @return A Resource for `clazz`. + * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + def at(clazz: Class[_]): Resource = macro Macros.atDynamicImpl + + /** Look up own resource files. + * + * This Resource looks up resources from the [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]] surrounding the call, + * using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. + * For example, if `my` is called from `com.example.ExampleClass`, + * then resource files will be searched for in the `com/example` folder containing `ExampleClass.class`. + * + * @example {{{ Resource.my.url("config.properties") }}} + * @return A Resource for the call site. + * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + def my: Resource = macro Macros.myImpl +} + +/** Implementations of the `Resource.at` macros. This is needed because `Class#getResource` is caller-sensitive; + * calls to it must appear in user code, ''not'' in better-files. + */ +private[files] final class Macros(val c: blackbox.Context) { + + import c.universe._ + import c.Expr + + def atStaticImpl[T](implicit T: WeakTypeTag[T]): Expr[Resource] = { + val rtc = Expr[Class[_]] { + try { + c.reifyRuntimeClass(T.tpe, concrete = true) + } catch { + case _: ReificationException => c.abort(c.enclosingPosition, s"${T.tpe} is not a concrete type") + } + } + atDynamicImpl(rtc) + } + + def atDynamicImpl(clazz: Expr[Class[_]]): Expr[Resource] = + reify { + new Resource { + override def url(name: String) = Option(clazz.splice.getResource(name)) + } + } + + def myImpl: Expr[Resource] = { + val rtc = c.reifyEnclosingRuntimeClass + if (rtc.isEmpty) { + // The documentation for reifyEnclosingRuntimeClass claims that this is somehow possible!? + c.abort(c.enclosingPosition, "this location doesn't correspond to a Java class file") + } + atDynamicImpl(Expr[Class[_]](rtc)) + } +} diff --git a/core/src/main/scala-3/better/files/Resource.scala b/core/src/main/scala-3/better/files/Resource.scala deleted file mode 100644 index 507e033f..00000000 --- a/core/src/main/scala-3/better/files/Resource.scala +++ /dev/null @@ -1,239 +0,0 @@ -package better.files - -import java.io.{IOException, InputStream} -import java.net.URL -import java.nio.charset.Charset - -import scala.quoted.{Quotes, Expr} -import scala.quoted.* - -/** Finds and loads [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) class resources]] or - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) class loader resources]]. - * - * The default implementation of this trait is the [[Resource]] object, which looks up resources using the - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#currentThread() current thread]]'s - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#getContextClassLoader() context class loader]]. The Resource object - * also offers several other Resource implementations, through its methods `at`, `from`, and `my`. `at` searches from a - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]], `from` searches from a - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html ClassLoader]], and `my` searches from the class, trait, or - * object surrounding the call. - * - * @example - * {{{ // Look up the config.properties file for this class or object. Resource.my.asStream("config.properties") - * - * // Find logging.properties (in the root package) somewhere on the classpath. Resource.url("logging.properties") }}} - * - * @see - * [[Resource]] - * @see - * [[https://stackoverflow.com/questions/676250/different-ways-of-loading-a-file-as-an-inputstream Different ways of loading a file as an InputStream]] - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] - */ -trait Resource { - - /** Look up a resource by name, and open an [[https://docs.oracle.com/javase/10/docs/api/java/io/InputStream.html InputStream]] for - * reading it. - * - * @param name - * Name of the resource to search for. - * @return - * InputStream for reading the found resource, if a resource was found. - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String) Class#getResourceAsStream]] - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResourceAsStream(java.lang.String) ClassLoader#getResourceAsStream]] - */ - @throws[IOException] - def asStream(name: String): Option[InputStream] = - url(name).map(_.openStream()) - - /** Same as asStream but throws a NoSuchElementException if resource is not found - */ - def getAsStream(name: String): InputStream = - asStream(name).getOrElse(Resource.notFound(name)) - - def asString( - name: String, - bufferSize: Int = DefaultBufferSize - )(implicit - charset: Charset = DefaultCharset - ): Option[String] = - asStream(name).map(_.asString(bufferSize = bufferSize)(charset)) - - def getAsString( - name: String, - bufferSize: Int = DefaultBufferSize - )(implicit - charset: Charset = DefaultCharset - ): String = - asString(name, bufferSize)(charset).getOrElse(Resource.notFound(name)) - - /** Look up a resource by name, and get its [[https://docs.oracle.com/javase/10/docs/api/java/net/URL.html URL]]. - * - * @param name - * Name of the resource to search for. - * @return - * URL of the requested resource. If the resource could not be found or is not accessible, returns None. - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] - */ - def url(name: String): Option[URL] - - /** Get URL of given resource A default argument of empty string is provided to conveniently get the root resource URL using - * {{Resource.getUrl()}} - * - * @param name - * @return - */ - def getUrl(name: String = ""): URL = - url(name).getOrElse(Resource.notFound(name)) -} - -/** Implementations of [[Resource]]. - * - * This object itself is a Resource uses the - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#currentThread() current thread]]'s - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#getContextClassLoader() context class loader]]. It also creates - * Resources with different lookup behavior, using the methods `at`, `from`, and `my`. `at` searches rom a - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]], `from` searches from a different - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html ClassLoader]], and `my` searches from the class, trait, or - * object surrounding the call. - * - * @see - * [[Resource]] - * @see - * [[https://stackoverflow.com/questions/676250/different-ways-of-loading-a-file-as-an-inputstream Different ways of loading a file as an InputStream]] - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] - */ -object Resource extends Resource { - - @throws[NoSuchElementException] - def notFound(name: String): Nothing = - throw new NoSuchElementException(s"Could not find resource=${name}") - - override def url(name: String): Option[URL] = - from(Thread.currentThread.getContextClassLoader).url(name) - - /** Look up class resource files. - * - * This Resource looks up resources relative to the JVM class file for `T`, using - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For example, if - * `com.example.ExampleClass` is given for `T`, then resource files will be searched for in the `com/example` folder containing - * `ExampleClass.class`. - * - * If you want to look up resource files relative to the call site instead (that is, you want a class to look up one of its own - * resources), use the `my` method instead. - * - * @example - * {{{Resource.at[YourClass].url("config.properties")}}} - * @tparam T - * The class, trait, or object to look up from. Objects must be written with a `.type` suffix, such as `Resource.at[SomeObject.type]`. - * @return - * A Resource for `T`. - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - */ - inline def at[T]: Resource = ${ atStaticImpl[T] } - - private def atStaticImpl[T: Type](using qc: Quotes): Expr[Resource] = { - import qc.reflect.* - val tpe = TypeRepr.of[T] - val typeSymbolStr = tpe.typeSymbol.toString - if (typeSymbolStr.startsWith("class ") || typeSymbolStr.startsWith("module class ")) { - val baseClass = tpe.baseClasses.head - return '{ - new Resource { - override def url(name: String) = Option(Class.forName(${ Expr(baseClass.fullName) }).getResource(name)) - } - } - } else { - report.errorAndAbort(s"${tpe.show} is not a concrete type") - } - } - - /** Look up class resource files. - * - * This Resource looks up resources from the given Class, using - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For example, if - * `classOf[com.example.ExampleClass]` is given for `clazz`, then resource files will be searched for in the `com/example` folder - * containing `ExampleClass.class`. - * - * If you want to look up resource files relative to the call site instead (that is, you want your class to look up one of its own - * resources), use the `my` method instead. - * - * @example - * {{{Resource.at(Class.forName("your.AppClass")).url("config.properties")}}} - * - * In this example, a file named `config.properties` is expected to appear alongside the file `AppClass.class` in the package `your`. - * @param clazz - * The class to look up from. - * @return - * A Resource for `clazz`. - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - */ - def at(clazz: Class[_]): Resource = new Resource { - override def url(name: String) = Option(clazz.getResource(name)) - } - - /** Look up own resource files. - * - * This Resource looks up resources from the [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]] surrounding the - * call, using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For - * example, if `my` is called from `com.example.ExampleClass`, then resource files will be searched for in the `com/example` folder - * containing `ExampleClass.class`. - * - * @example - * {{{Resource.my.url("config.properties")}}} - * @return - * A Resource for the call site. - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - */ - inline def my: Resource = ${ myImpl } - - private def myImpl(using qc: Quotes): Expr[Resource] = { - import qc.reflect.* - var callee = Symbol.spliceOwner - while (callee != null && callee != Symbol.noSymbol) { - callee = callee.owner - if (callee.isClassDef) { - return '{ - new Resource { - override def url(name: String) = Option(Class.forName(${ Expr(callee.fullName) }).getResource(name)) - } - } - } - } - report.errorAndAbort("this location doesn't correspond to a Java class file") - } - - /** Look up resource files using the specified ClassLoader. - * - * This Resource looks up resources from a specific ClassLoader. Like [[Resource the default Resource]], resource names are relative to - * the root package. - * - * @example - * {{{Resource.from(appClassLoader).url("com/example/config.properties")}}} - * @param cl - * ClassLoader to look up resources from. - * @return - * A Resource that uses the supplied ClassLoader. - * @see - * [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] - */ - def from(cl: ClassLoader): Resource = - new Resource { - override def url(name: String): Option[URL] = - Option(cl.getResource(name)) - } - -} diff --git a/core/src/main/scala-3/better/files/ResourceScalaCompat.scala b/core/src/main/scala-3/better/files/ResourceScalaCompat.scala new file mode 100644 index 00000000..7d5539b6 --- /dev/null +++ b/core/src/main/scala-3/better/files/ResourceScalaCompat.scala @@ -0,0 +1,116 @@ +package better.files + +import scala.quoted.{Quotes, Expr} +import scala.quoted.* + +private[files] trait ResourceScalaCompat { + + /** Look up class resource files. + * + * This Resource looks up resources relative to the JVM class file for `T`, using + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For example, if + * `com.example.ExampleClass` is given for `T`, then resource files will be searched for in the `com/example` folder containing + * `ExampleClass.class`. + * + * If you want to look up resource files relative to the call site instead (that is, you want a class to look up one of its own + * resources), use the `my` method instead. + * + * @example + * {{{Resource.at[YourClass].url("config.properties")}}} + * @tparam T + * The class, trait, or object to look up from. Objects must be written with a `.type` suffix, such as `Resource.at[SomeObject.type]`. + * @return + * A Resource for `T`. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + inline def at[T]: Resource = ${ Macros.atStaticImpl[T] } + + /** Look up class resource files. + * + * This Resource looks up resources from the given Class, using + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For example, if + * `classOf[com.example.ExampleClass]` is given for `clazz`, then resource files will be searched for in the `com/example` folder + * containing `ExampleClass.class`. + * + * If you want to look up resource files relative to the call site instead (that is, you want your class to look up one of its own + * resources), use the `my` method instead. + * + * @example + * {{{Resource.at(Class.forName("your.AppClass")).url("config.properties")}}} + * + * In this example, a file named `config.properties` is expected to appear alongside the file `AppClass.class` in the package `your`. + * @param clazz + * The class to look up from. + * @return + * A Resource for `clazz`. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + def at(clazz: Class[_]): Resource = new Resource { + override def url(name: String) = Option(clazz.getResource(name)) + } + + /** Look up own resource files. + * + * This Resource looks up resources from the [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]] surrounding the + * call, using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. For + * example, if `my` is called from `com.example.ExampleClass`, then resource files will be searched for in the `com/example` folder + * containing `ExampleClass.class`. + * + * @example + * {{{Resource.my.url("config.properties")}}} + * @return + * A Resource for the call site. + * @see + * [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] + */ + inline def my: Resource = ${ Macros.myImpl } +} + +private[files] object Macros { + + def atStaticImpl[T: Type](using qc: Quotes): Expr[Resource] = { + import qc.reflect.* + val tpe = TypeRepr.of[T] + val typeSymbolStr = tpe.typeSymbol.toString + if (typeSymbolStr.startsWith("class ") || typeSymbolStr.startsWith("module class ")) { + val baseClass = tpe.baseClasses.head + return '{ + new Resource { + override def url(name: String) = Option( + Class + .forName(${ + Expr(baseClass.fullName) + }) + .getResource(name) + ) + } + } + } else { + report.errorAndAbort(s"${tpe.show} is not a concrete type") + } + } + + def myImpl(using qc: Quotes): Expr[Resource] = { + import qc.reflect.* + var callee = Symbol.spliceOwner + while (callee != null && callee != Symbol.noSymbol) { + callee = callee.owner + if (callee.isClassDef) { + return '{ + new Resource { + override def url(name: String) = Option( + Class + .forName(${ + Expr(callee.fullName) + }) + .getResource(name) + ) + } + } + } + } + report.errorAndAbort("this location doesn't correspond to a Java class file") + } +} diff --git a/core/src/main/scala-2/better/files/Resource.scala b/core/src/main/scala/better/files/Resource.scala similarity index 59% rename from core/src/main/scala-2/better/files/Resource.scala rename to core/src/main/scala/better/files/Resource.scala index 97b062b7..35a32e1d 100644 --- a/core/src/main/scala-2/better/files/Resource.scala +++ b/core/src/main/scala/better/files/Resource.scala @@ -4,9 +4,6 @@ import java.io.{IOException, InputStream} import java.net.URL import java.nio.charset.Charset -import scala.language.experimental.macros -import scala.reflect.macros.{ReificationException, blackbox} - /** Finds and loads [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) class resources]] * or [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) class loader resources]]. * @@ -97,7 +94,7 @@ trait Resource { * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String) ClassLoader#getResource]] */ -object Resource extends Resource { +object Resource extends Resource with ResourceScalaCompat { @throws[NoSuchElementException] def notFound(name: String): Nothing = @@ -106,56 +103,6 @@ object Resource extends Resource { override def url(name: String): Option[URL] = from(Thread.currentThread.getContextClassLoader).url(name) - /** Look up class resource files. - * - * This Resource looks up resources relative to the JVM class file for `T`, - * using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. - * For example, if `com.example.ExampleClass` is given for `T`, then resource files will be searched for in the `com/example` folder containing `ExampleClass.class`. - * - * If you want to look up resource files relative to the call site instead (that is, you want a class to look up one of its own resources), use the `my` method instead. - * - * @example {{{ Resource.at[YourClass].url("config.properties") }}} - * @tparam T The class, trait, or object to look up from. Objects must be written with a `.type` suffix, such as `Resource.at[SomeObject.type]`. - * @return A Resource for `T`. - * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - */ - def at[T]: Resource = - macro Macros.atStaticImpl[T] - - /** Look up class resource files. - * - * This Resource looks up resources from the given Class, - * using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. - * For example, if `classOf[com.example.ExampleClass]` is given for `clazz`, then resource files will be searched for - * in the `com/example` folder containing `ExampleClass.class`. - * - * If you want to look up resource files relative to the call site instead (that is, you want your class to look up one of its own resources), - * use the `my` method instead. - * - * @example {{{ Resource.at(Class.forName("your.AppClass")).url("config.properties") }}} - * - * In this example, a file named `config.properties` is expected to appear alongside the file `AppClass.class` in the package `your`. - * @param clazz The class to look up from. - * @return A Resource for `clazz`. - * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - */ - def at(clazz: Class[_]): Resource = - macro Macros.atDynamicImpl - - /** Look up own resource files. - * - * This Resource looks up resources from the [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html Class]] surrounding the call, - * using [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]]. - * For example, if `my` is called from `com.example.ExampleClass`, - * then resource files will be searched for in the `com/example` folder containing `ExampleClass.class`. - * - * @example {{{ Resource.my.url("config.properties") }}} - * @return A Resource for the call site. - * @see [[https://docs.oracle.com/javase/10/docs/api/java/lang/Class.html#getResource(java.lang.String) Class#getResource]] - */ - def my: Resource = - macro Macros.myImpl - /** Look up resource files using the specified ClassLoader. * * This Resource looks up resources from a specific ClassLoader. Like [[Resource the default Resource]], resource names are relative to the root package. @@ -170,39 +117,4 @@ object Resource extends Resource { override def url(name: String): Option[URL] = Option(cl.getResource(name)) } - - /** Implementations of the `Resource.at` macros. This is needed because `Class#getResource` is caller-sensitive; - * calls to it must appear in user code, ''not'' in better-files. - */ - private[Resource] final class Macros(val c: blackbox.Context) { - import c.universe._ - import c.Expr - - def atStaticImpl[T](implicit T: WeakTypeTag[T]): Expr[Resource] = { - val rtc = Expr[Class[_]] { - try { - c.reifyRuntimeClass(T.tpe, concrete = true) - } catch { - case _: ReificationException => c.abort(c.enclosingPosition, s"${T.tpe} is not a concrete type") - } - } - atDynamicImpl(rtc) - } - - def atDynamicImpl(clazz: Expr[Class[_]]): Expr[Resource] = - reify { - new Resource { - override def url(name: String) = Option(clazz.splice.getResource(name)) - } - } - - def myImpl: Expr[Resource] = { - val rtc = c.reifyEnclosingRuntimeClass - if (rtc.isEmpty) { - // The documentation for reifyEnclosingRuntimeClass claims that this is somehow possible!? - c.abort(c.enclosingPosition, "this location doesn't correspond to a Java class file") - } - atDynamicImpl(Expr[Class[_]](rtc)) - } - } }