Skip to content

Commit

Permalink
Merge pull request #573 from exoego/scala3
Browse files Browse the repository at this point in the history
Implement Scala 3 support
  • Loading branch information
pathikrit authored Jan 27, 2023
2 parents 196d62e + 36af573 commit 0e23954
Show file tree
Hide file tree
Showing 19 changed files with 304 additions and 168 deletions.
7 changes: 6 additions & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
maxColumn = 140
align.preset = more
version=2.7.5
version = 3.6.0
project.layout = StandardConvention
runner.dialect = scala213source3

# Added to minimize changes
docstrings.style = keep
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -50,4 +51,4 @@ cache:
directories:
- $HOME/.cache/coursier
- $HOME/.ivy2/cache
- $HOME/.sbt
- $HOME/.sbt
2 changes: 1 addition & 1 deletion akka/src/test/scala/better/files/FileWatcherSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
48 changes: 29 additions & 19 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -18,15 +18,15 @@ 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
crossVersion := CrossVersion.binary,
scalacOptions := myScalacOptions(scalaVersion.value, scalacOptions.value),
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),
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
Expand All @@ -51,22 +51,32 @@ 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.scalaReflect(scalaVersion.value),
Dependencies.commonsio,
Dependencies.fastjavaio,
Dependencies.shapeless
)
Dependencies.fastjavaio
) ++ (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) =>
Seq(
Dependencies.shapeless,
Dependencies.scalaReflect(scalaVersion.value)
)
case _ => Seq.empty
})
)

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 += 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")

Expand All @@ -80,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"[email protected]:$username/$repo.git",
git.remoteRepo := s"[email protected]:$username/$repo.git",
ghpagesPushSite / envVars += ("SBT_GHPAGES_COMMIT_MESSAGE" -> s"Publishing Scaladoc [CI SKIP]")
)

Expand Down
90 changes: 90 additions & 0 deletions core/src/main/scala-2/better/files/ResourceScalaCompat.scala
Original file line number Diff line number Diff line change
@@ -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))
}
}
116 changes: 116 additions & 0 deletions core/src/main/scala-3/better/files/ResourceScalaCompat.scala
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading

0 comments on commit 0e23954

Please sign in to comment.