Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start work on refactoring MainModule to decompose #1316

Merged
merged 3 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading