From e16712991d6c46684fa55b2ce6893e550533b73b Mon Sep 17 00:00:00 2001 From: "P. Oscar Boykin" Date: Mon, 16 Dec 2024 11:03:24 -1000 Subject: [PATCH] Make bosatsu.tool package, move some items from MainModule (#1321) --- .../scala/org/bykn/bosatsu/PathModule.scala | 13 ++- .../org/bykn/bosatsu/PathModuleTest.scala | 24 +++--- .../scala/org/bykn/bosatsu/MainModule.scala | 79 +++---------------- .../scala/org/bykn/bosatsu/MemoryMain.scala | 5 +- .../org/bykn/bosatsu/tool/FileKind.scala | 8 ++ .../org/bykn/bosatsu/tool/GraphOutput.scala | 21 +++++ .../scala/org/bykn/bosatsu/tool/Output.scala | 44 +++++++++++ .../org/bykn/bosatsu/{ => tool}/PathGen.scala | 5 +- .../scala/org/bykn/bosatsu/TestUtils.scala | 7 +- .../scala/org/bykn/bosatsu/jsapi/JsApi.scala | 6 +- .../scala/org/bykn/bosatsu/jsui/Store.scala | 9 ++- 11 files changed, 121 insertions(+), 100 deletions(-) create mode 100644 core/src/main/scala/org/bykn/bosatsu/tool/FileKind.scala create mode 100644 core/src/main/scala/org/bykn/bosatsu/tool/GraphOutput.scala create mode 100644 core/src/main/scala/org/bykn/bosatsu/tool/Output.scala rename core/src/main/scala/org/bykn/bosatsu/{ => tool}/PathGen.scala (97%) diff --git a/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala b/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala index eb0a5eafc..e54f39f4d 100644 --- a/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala +++ b/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala @@ -1,12 +1,11 @@ package org.bykn.bosatsu -import cats.effect.{IO, Resource} -import org.typelevel.paiges.{Doc, Document} - -import java.nio.file.{Path => JPath} - import cats.data.NonEmptyList import cats.effect.ExitCode +import cats.effect.{IO, Resource} +import java.nio.file.{Path => JPath} +import org.typelevel.paiges.{Doc, Document} +import org.bykn.bosatsu.tool.{FileKind, GraphOutput, Output} import cats.implicits.catsKernelOrderingForOrder import cats.syntax.all._ @@ -24,7 +23,7 @@ object PathModule extends MainModule[IO, JPath](IOPlatformIO) { self => def withEC[A](fn: Par.EC => IO[A]): IO[A] = parResource.use(fn) - def report(io: IO[Output]): IO[ExitCode] = + def report(io: IO[Output[JPath]]): IO[ExitCode] = io.attempt.flatMap { case Right(out) => reportOutput(out) case Left(err) => reportException(err).as(ExitCode.Error) @@ -54,7 +53,7 @@ object PathModule extends MainModule[IO, JPath](IOPlatformIO) { self => def writePackages[A](packages: List[Package.Typed[A]], path: JPath): IO[Unit] = ProtoConverter.writePackages(packages, path) - def reportOutput(out: Output): IO[ExitCode] = + def reportOutput(out: Output[JPath]): IO[ExitCode] = out match { case Output.TestOutput(resMap, color) => val hasMissing = resMap.exists(_._2.isEmpty) diff --git a/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala b/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala index 42ea770cb..486a098b8 100644 --- a/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala +++ b/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala @@ -1,14 +1,14 @@ package org.bykn.bosatsu -import cats.effect.IO import cats.data.NonEmptyList +import cats.effect.IO import java.nio.file.{Path, Paths} +import org.bykn.bosatsu.tool.Output import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.funsuite.AnyFunSuite import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks.forAll - import scala.jdk.CollectionConverters._ import scala.util.{Failure, Success, Try} -import org.scalatest.funsuite.AnyFunSuite // allow us to unsafeRunSync import cats.effect.unsafe.implicits.global @@ -102,7 +102,7 @@ class PathModuleTest extends AnyFunSuite { } } - def run(args: String*): PathModule.Output = + def run(args: String*): Output[Path] = PathModule.run(args.toList) match { case Left(h) => fail(s"got help: $h on command: ${args.toList}") case Right(io) => @@ -126,7 +126,7 @@ class PathModuleTest extends AnyFunSuite { .toSeq: _* ) out match { - case PathModule.Output.TestOutput(results, _) => + case Output.TestOutput(results, _) => val res = results.collect { case (pn, Some(t)) if pn.asString == "Queue" => t.value } @@ -142,7 +142,7 @@ class PathModuleTest extends AnyFunSuite { .toSeq: _* ) out match { - case PathModule.Output.TestOutput(results, _) => + case Output.TestOutput(results, _) => val res = results.collect { case (pn, Some(t)) if pn.asString == "Bar" => t.value } @@ -160,7 +160,7 @@ class PathModuleTest extends AnyFunSuite { .toSeq: _* ) out match { - case PathModule.Output.TranspileOut(_, _) => + case Output.TranspileOut(_, _) => assert(true) case other => fail(s"expected transpile output: $other") } @@ -174,7 +174,7 @@ class PathModuleTest extends AnyFunSuite { .toSeq: _* ) out match { - case PathModule.Output.JsonOutput(j @ Json.JObject(_), _) => + case Output.JsonOutput(j @ Json.JObject(_), _) => assert( j.toMap == Map( "value" -> Json.JBool(true), @@ -193,7 +193,7 @@ class PathModuleTest extends AnyFunSuite { .toList :+ "[2, 4]" run(cmd: _*) match { - case PathModule.Output.JsonOutput(Json.JNumberStr("8"), _) => succeed + case Output.JsonOutput(Json.JNumberStr("8"), _) => succeed case other => fail(s"expected json object output: $other") } } @@ -205,7 +205,7 @@ class PathModuleTest extends AnyFunSuite { .toList :+ "[[2, 4], [3, 5]]" run(cmd: _*) match { - case PathModule.Output.JsonOutput( + case Output.JsonOutput( Json.JArray(Vector(Json.JNumberStr("8"), Json.JNumberStr("15"))), _ ) => @@ -271,7 +271,7 @@ class PathModuleTest extends AnyFunSuite { .toSeq: _* ) out match { - case PathModule.Output.TestOutput(res, _) => + case Output.TestOutput(res, _) => val noTests = res.collect { case (pn, None) => pn }.toList assert(noTests == Nil) val failures = res.collect { @@ -288,7 +288,7 @@ class PathModuleTest extends AnyFunSuite { .split("\\s+") .toSeq: _* ) match { - case PathModule.Output.JsonOutput(Json.JString("this is Foo"), _) => + case Output.JsonOutput(Json.JString("this is Foo"), _) => succeed case other => fail(s"unexpeced: $other") } diff --git a/core/src/main/scala/org/bykn/bosatsu/MainModule.scala b/core/src/main/scala/org/bykn/bosatsu/MainModule.scala index ea97b20cb..4612affd5 100644 --- a/core/src/main/scala/org/bykn/bosatsu/MainModule.scala +++ b/core/src/main/scala/org/bykn/bosatsu/MainModule.scala @@ -1,12 +1,13 @@ package org.bykn.bosatsu import cats.data.{Chain, Validated, ValidatedNel, NonEmptyList} -import cats.{Eval, Traverse} +import cats.Traverse import com.monovore.decline.{Argument, Command, Help, Opts} import cats.parse.{Parser0 => P0, Parser => P} import org.typelevel.paiges.Doc import scala.util.{Failure, Success, Try} import org.bykn.bosatsu.Parser.argFromParser +import org.bykn.bosatsu.tool.{FileKind, GraphOutput, Output} import codegen.Transpiler @@ -31,67 +32,11 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { // Below here are concrete and should not use override ////////////////////////////// - final def run(args: List[String]): Either[Help, IO[Output]] = + final def run(args: List[String]): Either[Help, IO[Output[Path]]] = MainCommand.command .parse(args.toList) .map(_.run.widen) - sealed abstract class FileKind(val name: String) - object FileKind { - case object Source extends FileKind("source") - case object Iface extends FileKind("interface") - case object Pack extends FileKind("package") - } - - sealed abstract class GraphOutput - object GraphOutput { - case object Dot extends GraphOutput - case object Json extends GraphOutput - - val jsonOrDot: Opts[GraphOutput] = - Opts - .option[String]("graph_format", "format of graph, either json or dot") - .mapValidated { - case "json" => Validated.valid(Json) - case "dot" => Validated.valid(Dot) - case other => - Validated.invalidNel(s"\"$other\" invalid, expected json or dot") - } - .withDefault(Json) - } - - sealed abstract class Output - object Output { - case class TestOutput( - tests: List[(PackageName, Option[Eval[Test]])], - colorize: Colorize - ) extends Output - case class EvaluationResult( - value: Eval[Value], - tpe: rankn.Type, - doc: Eval[Doc] - ) extends Output - case class JsonOutput(json: Json, output: Option[Path]) extends Output - case class CompileOut( - packList: List[Package.Typed[Any]], - ifout: Option[Path], - output: Option[Path] - ) extends Output - case class TranspileOut(outs: List[(NonEmptyList[String], Doc)], base: Path) - extends Output - - case class ShowOutput( - packages: List[Package.Typed[Any]], - ifaces: List[Package.Interface], - output: Option[Path] - ) extends Output - - case class DepsOutput( - depinfo: List[(Path, PackageName, FileKind, List[PackageName])], - output: Option[Path], - style: GraphOutput - ) extends Output - } sealed abstract class MainException extends Exception { def command: MainCommand @@ -158,7 +103,7 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { } sealed abstract class MainCommand(val name: String) { - type Result <: Output + type Result <: Output[Path] def run: IO[Result] } @@ -584,8 +529,8 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { case class Traverse(in: JsonInput) extends JsonMode } - type PathGen = org.bykn.bosatsu.PathGen[IO, Path] - val PathGen = org.bykn.bosatsu.PathGen + type PathGen = org.bykn.bosatsu.tool.PathGen[IO, Path] + val PathGen = org.bykn.bosatsu.tool.PathGen sealed abstract class Inputs object Inputs { @@ -820,7 +765,7 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { outDir: Path ) extends MainCommand("transpile") { - type Result = Output.TranspileOut + type Result = Output.TranspileOut[Path] def run = withEC { implicit ec => @@ -901,7 +846,7 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { errColor: Colorize ) extends MainCommand("json") { - type Result = Output.JsonOutput + type Result = Output.JsonOutput[Path] private def showError[A](prefix: String, str: String, idx: Int): IO[A] = { val errMsg0 = str.substring(idx + 1) @@ -961,7 +906,7 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { io: IO[String], extract: Json => IO[G[Json]], inject: G[Json] => Json - ): IO[Output.JsonOutput] = + ): IO[Output.JsonOutput[Path]] = v2j.valueFnToJsonFn(res.tpe) match { case Left(unsup) => unsupported(unsup) case Right((arity, fnGen)) => @@ -1048,7 +993,7 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { errColor: Colorize ) extends MainCommand("check") { - type Result = Output.CompileOut + type Result = Output.CompileOut[Path] def run = withEC { implicit ec => @@ -1112,7 +1057,7 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { errColor: Colorize ) extends MainCommand("show") { - type Result = Output.ShowOutput + type Result = Output.ShowOutput[Path] def run = withEC { implicit ec => for { @@ -1129,7 +1074,7 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { style: GraphOutput ) extends MainCommand("deps") { - type Result = Output.DepsOutput + type Result = Output.DepsOutput[Path] def srcDeps( paths: List[Path] diff --git a/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala b/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala index 1deb84537..0c0d262a7 100644 --- a/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala +++ b/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala @@ -4,6 +4,7 @@ import cats.MonadError import cats.data.Kleisli import com.monovore.decline.Argument import scala.collection.immutable.SortedMap +import org.bykn.bosatsu.tool.Output import cats.syntax.all._ @@ -30,10 +31,10 @@ class MemoryMain[G[_], K: Ordering]( files: Iterable[(K, String)], packages: Iterable[(K, List[Package.Typed[Unit]])] = Nil, interfaces: Iterable[(K, List[Package.Interface])] = Nil - )(cmd: List[String]): G[Output] = + )(cmd: List[String]): G[Output[K]] = run(cmd) match { case Left(msg) => - moduleIOMonad.raiseError[Output]( + moduleIOMonad.raiseError[Output[K]]( new Exception(s"got the help message for: $cmd: $msg") ) .run(SortedMap.empty[K, MemoryMain.FileContent]) diff --git a/core/src/main/scala/org/bykn/bosatsu/tool/FileKind.scala b/core/src/main/scala/org/bykn/bosatsu/tool/FileKind.scala new file mode 100644 index 000000000..6251d8894 --- /dev/null +++ b/core/src/main/scala/org/bykn/bosatsu/tool/FileKind.scala @@ -0,0 +1,8 @@ +package org.bykn.bosatsu.tool + +sealed abstract class FileKind(val name: String) +object FileKind { + case object Source extends FileKind("source") + case object Iface extends FileKind("interface") + case object Pack extends FileKind("package") +} diff --git a/core/src/main/scala/org/bykn/bosatsu/tool/GraphOutput.scala b/core/src/main/scala/org/bykn/bosatsu/tool/GraphOutput.scala new file mode 100644 index 000000000..c513e7f02 --- /dev/null +++ b/core/src/main/scala/org/bykn/bosatsu/tool/GraphOutput.scala @@ -0,0 +1,21 @@ +package org.bykn.bosatsu.tool + +import cats.data.Validated +import com.monovore.decline.Opts + +sealed abstract class GraphOutput +object GraphOutput { + case object Dot extends GraphOutput + case object Json extends GraphOutput + + val jsonOrDot: Opts[GraphOutput] = + Opts + .option[String]("graph_format", "format of graph, either json or dot") + .mapValidated { + case "json" => Validated.valid(Json) + case "dot" => Validated.valid(Dot) + case other => + Validated.invalidNel(s"\"$other\" invalid, expected json or dot") + } + .withDefault(Json) +} diff --git a/core/src/main/scala/org/bykn/bosatsu/tool/Output.scala b/core/src/main/scala/org/bykn/bosatsu/tool/Output.scala new file mode 100644 index 000000000..6607d7437 --- /dev/null +++ b/core/src/main/scala/org/bykn/bosatsu/tool/Output.scala @@ -0,0 +1,44 @@ +package org.bykn.bosatsu.tool + +import cats.Eval +import cats.data.NonEmptyList +import org.bykn.bosatsu.{Json, Package, PackageName, Test, Value, rankn} +import org.typelevel.paiges.Doc +import org.bykn.bosatsu.LocationMap.Colorize + +sealed abstract class Output[+Path] +object Output { + case class TestOutput( + tests: List[(PackageName, Option[Eval[Test]])], + colorize: Colorize + ) extends Output[Nothing] + + case class EvaluationResult( + value: Eval[Value], + tpe: rankn.Type, + doc: Eval[Doc] + ) extends Output[Nothing] + + case class JsonOutput[Path](json: Json, output: Option[Path]) extends Output[Path] + + case class CompileOut[Path]( + packList: List[Package.Typed[Any]], + ifout: Option[Path], + output: Option[Path] + ) extends Output[Path] + + case class TranspileOut[Path](outs: List[(NonEmptyList[String], Doc)], base: Path) + extends Output[Path] + + case class ShowOutput[Path]( + packages: List[Package.Typed[Any]], + ifaces: List[Package.Interface], + output: Option[Path] + ) extends Output[Path] + + case class DepsOutput[Path]( + depinfo: List[(Path, PackageName, FileKind, List[PackageName])], + output: Option[Path], + style: GraphOutput + ) extends Output[Path] +} \ No newline at end of file diff --git a/core/src/main/scala/org/bykn/bosatsu/PathGen.scala b/core/src/main/scala/org/bykn/bosatsu/tool/PathGen.scala similarity index 97% rename from core/src/main/scala/org/bykn/bosatsu/PathGen.scala rename to core/src/main/scala/org/bykn/bosatsu/tool/PathGen.scala index 7fd0c3127..c0da65803 100644 --- a/core/src/main/scala/org/bykn/bosatsu/PathGen.scala +++ b/core/src/main/scala/org/bykn/bosatsu/tool/PathGen.scala @@ -1,12 +1,13 @@ -package org.bykn.bosatsu +package org.bykn.bosatsu.tool import cats.{Monoid, Monad} -import cats.implicits._ +import cats.syntax.all._ sealed abstract class PathGen[IO[_], Path] { def read(implicit m: Monad[IO]): IO[List[Path]] } + object PathGen { final case class Direct[IO[_], Path](path: Path) extends PathGen[IO, Path] { def read(implicit m: Monad[IO]): IO[List[Path]] = diff --git a/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala b/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala index 1db953856..9a448124d 100644 --- a/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala +++ b/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala @@ -3,6 +3,7 @@ package org.bykn.bosatsu import cats.data.{Ior, Validated, NonEmptyList} import java.nio.file.{Files, Paths} import org.bykn.bosatsu.rankn._ +import org.bykn.bosatsu.tool.Output import org.scalatest.{Assertion, Assertions} import Assertions.{succeed, fail} @@ -168,7 +169,7 @@ object TestUtils { module.runWith(files)( "eval" :: "--main" :: mainPackS :: makeInputArgs(files) ) match { - case Right(module.Output.EvaluationResult(got, _, gotDoc)) => + case Right(Output.EvaluationResult(got, _, gotDoc)) => val gv = got.value assert( gv == expected, @@ -196,7 +197,7 @@ object TestUtils { files ) ) match { - case Right(module.Output.JsonOutput(got, _)) => + case Right(Output.JsonOutput(got, _)) => assert(got == expected, s"$got != $expected") case Right(other) => fail(s"got an unexpected success: $other") @@ -215,7 +216,7 @@ object TestUtils { module.runWith(files)( "test" :: "--test_package" :: mainPackS :: makeInputArgs(files) ) match { - case Right(module.Output.TestOutput(results, _)) => + case Right(Output.TestOutput(results, _)) => results.collect { case (_, Some(t)) => t.value } match { case t :: Nil => assert( diff --git a/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala b/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala index d8833bb60..143a1524e 100644 --- a/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala +++ b/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala @@ -2,7 +2,7 @@ package org.bykn.bosatsu package jsapi import org.typelevel.paiges.Doc - +import org.bykn.bosatsu.tool.Output import scala.scalajs.js import js.| @@ -40,7 +40,7 @@ object JsApi { module.runWith(files)("eval" :: main ::: makeInputArgs(files.keys)) match { case Left(err) => new Error(s"error: ${err.getMessage}") - case Right(module.Output.EvaluationResult(_, tpe, resDoc)) => + case Right(Output.EvaluationResult(_, tpe, resDoc)) => val tDoc = rankn.Type.fullyResolvedDocument.document(tpe) val doc = resDoc.value + (Doc.lineOrEmpty + Doc.text(": ") + tDoc).nested(4) @@ -93,7 +93,7 @@ object JsApi { ) match { case Left(err) => new Error(s"error: ${err.getMessage}") - case Right(module.Output.JsonOutput(json, _)) => + case Right(Output.JsonOutput(json, _)) => new EvalSuccess(jsonToAny(json)) case Right(other) => new Error(s"internal error. got unexpected result: $other") diff --git a/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala b/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala index 54a2d1eab..d1eba3349 100644 --- a/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala +++ b/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala @@ -2,6 +2,7 @@ package org.bykn.bosatsu.jsui import cats.effect.{IO, Resource} import org.bykn.bosatsu.{MemoryMain, Package, Test, rankn} +import org.bykn.bosatsu.tool.Output import org.typelevel.paiges.{Doc, Document} import org.scalajs.dom.window.localStorage @@ -10,7 +11,7 @@ import Action.Cmd object Store { val memoryMain = MemoryMain[Either[Throwable, *], String](_.split("/", -1).toList) - type HandlerFn = memoryMain.Output => String + type HandlerFn = Output[String] => String def cmdHandler(cmd: Cmd): (List[String], HandlerFn) = cmd match { case Cmd.Eval => @@ -27,7 +28,7 @@ object Store { ) val handler: HandlerFn = { - case memoryMain.Output.EvaluationResult(_, tpe, resDoc) => + case Output.EvaluationResult(_, tpe, resDoc) => val tDoc = rankn.Type.fullyResolvedDocument.document(tpe) val doc = resDoc.value + (Doc.lineOrEmpty + Doc.text(": ") + tDoc).nested(4) @@ -49,7 +50,7 @@ object Store { "html" ) val handler: HandlerFn = { - case memoryMain.Output.TestOutput(resMap, color) => + case Output.TestOutput(resMap, color) => val evaluatedTests = resMap.map { case (p, opt) => (p, opt.map(_.value)) } @@ -70,7 +71,7 @@ object Store { "html" ) val handler: HandlerFn = { - case memoryMain.Output.ShowOutput(packs, ifaces, _) => + case Output.ShowOutput(packs, ifaces, _) => val pdocs = packs.map { pack => Document[Package.Typed[Any]].document(pack) }