Skip to content

Commit

Permalink
Start work on refactoring MainModule to decompose (#1316)
Browse files Browse the repository at this point in the history
* Start work on refactoring MainModule to decompose

* use Resource, fix tests

* fix jsui compile
  • Loading branch information
johnynek authored Dec 13, 2024
1 parent 7e7abc5 commit 9a291e4
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 238 deletions.
111 changes: 20 additions & 91 deletions cli/src/main/scala/org/bykn/bosatsu/PathModule.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.bykn.bosatsu

import cats.effect.IO
import com.monovore.decline.Argument
import cats.effect.{IO, Resource}
import org.typelevel.paiges.{Doc, Document}

import java.nio.file.{Path => JPath}
Expand All @@ -12,74 +11,26 @@ import cats.effect.ExitCode
import cats.implicits.catsKernelOrderingForOrder
import cats.syntax.all._

object PathModule extends MainModule[IO] {
object PathModule extends MainModule[IO, JPath](IOPlatformIO) { self =>
type Path = JPath

override def pathArg: Argument[Path] =
Argument.readPath

def readPath(path: Path): IO[String] =
IO.blocking(new String(java.nio.file.Files.readAllBytes(path), "utf-8"))

val resolvePath: Some[(Path, PackageName) => IO[Option[Path]]] =
Some(
{ (root: Path, pack: PackageName) =>
val dir = pack.parts.init.foldLeft(root)(_.resolve(_))
val filePath = dir.resolve(pack.parts.last + ".bosatsu")
IO.blocking {
// this is a side-effect since file is mutable
// and talks to the file system
val file = filePath.toFile
if (file.exists()) Some(filePath)
else None
}
}
)

def readPackages(paths: List[Path]): IO[List[Package.Typed[Unit]]] =
ProtoConverter.readPackages(paths)

def readInterfaces(paths: List[Path]): IO[List[Package.Interface]] =
ProtoConverter.readInterfaces(paths)

def writeInterfaces(
interfaces: List[Package.Interface],
path: Path
): IO[Unit] =
ProtoConverter.writeInterfaces(interfaces, path)

def writePackages[A](packages: List[Package.Typed[A]], path: Path): IO[Unit] =
ProtoConverter.writePackages(packages, path)

def unfoldDir: Option[Path => IO[Option[IO[List[Path]]]]] = Some {
(path: Path) =>
IO.blocking {
val f = path.toFile

if (f.isDirectory()) {
Some(IO.blocking {
f.listFiles.iterator.map(_.toPath).toList
})
} else None
}
}

def hasExtension(str: String): Path => Boolean = { (path: Path) =>
path.toString.endsWith(str)
}

def print(str: => String): IO[Unit] =
def print(str: String): IO[Unit] =
IO.println(str)

def delay[A](a: => A): IO[A] = IO(a)
val parResource: Resource[IO, Par.EC] =
Resource.make(IO(Par.newService()))(es => IO(Par.shutdownService(es)))
.map(Par.ecFromService(_))

def withEC[A](fn: Par.EC => IO[A]): IO[A] =
parResource.use(fn)

def report(io: IO[Output]): IO[ExitCode] =
io.attempt.flatMap {
case Right(out) => reportOutput(out)
case Left(err) => reportException(err).as(ExitCode.Error)
}

private def writeOut(doc: Doc, out: Option[Path]): IO[Unit] =
private def writeOut(doc: Doc, out: Option[JPath]): IO[Unit] =
out match {
case None =>
IO.blocking {
Expand All @@ -94,6 +45,15 @@ object PathModule extends MainModule[IO] {
CodeGenWrite.writeDoc(p, doc)
}

def writeInterfaces(
interfaces: List[Package.Interface],
path: JPath
): IO[Unit] =
ProtoConverter.writeInterfaces(interfaces, path)

def writePackages[A](packages: List[Package.Typed[A]], path: JPath): IO[Unit] =
ProtoConverter.writePackages(packages, path)

def reportOutput(out: Output): IO[ExitCode] =
out match {
case Output.TestOutput(resMap, color) =>
Expand All @@ -120,7 +80,7 @@ object PathModule extends MainModule[IO] {
.as(ExitCode.Success)

case Output.TranspileOut(outs, base) =>
def path(p: List[String]): Path =
def path(p: List[String]): JPath =
p.foldLeft(base)(_.resolve(_))

outs.toList
Expand Down Expand Up @@ -295,35 +255,4 @@ object PathModule extends MainModule[IO] {
IO.blocking(ex.printStackTrace(System.err))
}

def pathPackage(roots: List[Path], packFile: Path): Option[PackageName] = {
import scala.jdk.CollectionConverters._

def getP(p: Path): Option[PackageName] = {
val subPath = p
.relativize(packFile)
.asScala
.map { part =>
part.toString.toLowerCase.capitalize
}
.mkString("/")

val dropExtension = """(.*)\.[^.]*$""".r
val toParse = subPath match {
case dropExtension(prefix) => prefix
case _ => subPath
}
PackageName.parse(toParse)
}

@annotation.tailrec
def loop(roots: List[Path]): Option[PackageName] =
roots match {
case Nil => None
case h :: _ if packFile.startsWith(h) => getP(h)
case _ :: t => loop(t)
}

if (packFile.toString.isEmpty) None
else loop(roots)
}
}
90 changes: 90 additions & 0 deletions cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.bykn.bosatsu

import cats.MonadError
import cats.effect.IO
import com.monovore.decline.Argument
import java.nio.file.{Path => JPath}

object IOPlatformIO extends PlatformIO[IO, JPath] {
type F[A] = IO[A]
type Path = JPath

override def pathArg: Argument[Path] =
Argument.readPath

override def moduleIOMonad: MonadError[IO, Throwable] =
cats.effect.IO.asyncForIO

def readPath(path: Path): IO[String] =
IO.blocking(new String(java.nio.file.Files.readAllBytes(path), "utf-8"))

val resolvePath: Some[(Path, PackageName) => IO[Option[Path]]] =
Some(
{ (root: Path, pack: PackageName) =>
val dir = pack.parts.init.foldLeft(root)(_.resolve(_))
val filePath = dir.resolve(pack.parts.last + ".bosatsu")
IO.blocking {
// this is a side-effect since file is mutable
// and talks to the file system
val file = filePath.toFile
if (file.exists()) Some(filePath)
else None
}
}
)

def readPackages(paths: List[Path]): IO[List[Package.Typed[Unit]]] =
ProtoConverter.readPackages(paths)

def readInterfaces(paths: List[Path]): IO[List[Package.Interface]] =
ProtoConverter.readInterfaces(paths)

def unfoldDir: Option[Path => IO[Option[IO[List[Path]]]]] = Some {
(path: Path) =>
IO.blocking {
val f = path.toFile

if (f.isDirectory()) {
Some(IO.blocking {
f.listFiles.iterator.map(_.toPath).toList
})
} else None
}
}

def hasExtension(str: String): Path => Boolean = { (path: Path) =>
path.toString.endsWith(str)
}

def pathPackage(roots: List[Path], packFile: Path): Option[PackageName] = {
import scala.jdk.CollectionConverters._

def getP(p: Path): Option[PackageName] = {
val subPath = p
.relativize(packFile)
.asScala
.map { part =>
part.toString.toLowerCase.capitalize
}
.mkString("/")

val dropExtension = """(.*)\.[^.]*$""".r
val toParse = subPath match {
case dropExtension(prefix) => prefix
case _ => subPath
}
PackageName.parse(toParse)
}

@annotation.tailrec
def loop(roots: List[Path]): Option[PackageName] =
roots match {
case Nil => None
case h :: _ if packFile.startsWith(h) => getP(h)
case _ :: t => loop(t)
}

if (packFile.toString.isEmpty) None
else loop(roots)
}
}
11 changes: 6 additions & 5 deletions cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.scalatest.funsuite.AnyFunSuite
import cats.effect.unsafe.implicits.global

class PathModuleTest extends AnyFunSuite {
import PathModule.platformIO.pathPackage

implicit val arbPath: Arbitrary[Path] =
Arbitrary {
Expand All @@ -24,7 +25,7 @@ class PathModuleTest extends AnyFunSuite {

test("test some hand written examples") {
def pn(roots: List[String], file: String): Option[PackageName] =
PathModule.pathPackage(roots.map(Paths.get(_)), Paths.get(file))
pathPackage(roots.map(Paths.get(_)), Paths.get(file))

assert(
pn(List("/root0", "/root1"), "/root0/Bar.bosatsu") == Some(
Expand Down Expand Up @@ -56,13 +57,13 @@ class PathModuleTest extends AnyFunSuite {

test("no roots means no Package") {
forAll { (p: Path) =>
assert(PathModule.pathPackage(Nil, p) == None)
assert(pathPackage(Nil, p) == None)
}
}

test("empty path is not okay for a package") {
forAll { (roots: List[Path]) =>
assert(PathModule.pathPackage(roots, Paths.get("")) == None)
assert(pathPackage(roots, Paths.get("")) == None)
}
}

Expand All @@ -74,7 +75,7 @@ class PathModuleTest extends AnyFunSuite {
PackageName.parse(
rest.asScala.map(_.toString.toLowerCase.capitalize).mkString("/")
)
assert(PathModule.pathPackage(root :: otherRoots, path) == pack)
assert(pathPackage(root :: otherRoots, path) == pack)
}

forAll(law(_, _, _))
Expand All @@ -91,7 +92,7 @@ class PathModuleTest extends AnyFunSuite {
test("if none of the roots are prefixes we have none") {
forAll { (r0: Path, roots0: List[Path], file: Path) =>
val roots = (r0 :: roots0).filterNot(_.toString == "")
val pack = PathModule.pathPackage(roots, file)
val pack = pathPackage(roots, file)

val noPrefix = !roots.exists { r =>
file.asScala.toList.startsWith(r.asScala.toList)
Expand Down
Loading

0 comments on commit 9a291e4

Please sign in to comment.