diff --git a/.scalafmt.conf b/.scalafmt.conf index cd041b1..ee905df 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.5.9" +version = "3.8.3" runner.dialect = scala3 fileOverride { "glob:**/scaladex/src/**" { diff --git a/build.sbt b/build.sbt index 2318510..40056ec 100644 --- a/build.sbt +++ b/build.sbt @@ -5,10 +5,11 @@ enablePlugins(NpmPackagePlugin) name := "dexsearch" version := "0.1.5" -scalaVersion := "3.2.1" // or any other Scala version >= 2.11.12 +scalaVersion := "3.3.4" // or any other Scala version >= 2.11.12 libraryDependencies ++= Seq( - "com.monovore" %%% "decline-effect" % "2.4.1" + "com.monovore" %%% "decline-effect" % "2.4.1", + "tech.neander" %%% "cue4s-cats-effect" % "0.0.6" ) // This is an application with a main method @@ -27,7 +28,6 @@ npmPackageBinaryEnable := true npmPackageDependencies ++= { Seq( "node-fetch" -> "^2.6.1", - "prompts" -> "^2.4.2", "clipboardy" -> "^3.0.0" ) } diff --git a/project/build.properties b/project/build.properties index 46e43a9..db1723b 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.2 +sbt.version=1.10.5 diff --git a/project/plugins.sbt b/project/plugins.sbt index 2764f09..26fd547 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,3 @@ // addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta40") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.12.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") addSbtPlugin("io.chrisdavenport" % "sbt-npm-package" % "0.1.2") diff --git a/src/main/scala/dex/main.scala b/src/main/scala/dex/main.scala index d1f97a1..e363ba6 100644 --- a/src/main/scala/dex/main.scala +++ b/src/main/scala/dex/main.scala @@ -3,9 +3,6 @@ package dex import scala.scalajs.js import scala.scalajs.js.annotation.JSImport -// @main -def main() = println(util.inspect(prompts.default)) - import scala.scalajs.js import scala.scalajs.js.JSConverters._ import scala.util.Success @@ -16,6 +13,8 @@ import cats.syntax.all._ import com.monovore.decline.effect.CommandIOApp import cats.effect.IO import cats.effect.ExitCode +import cue4s.catseffect.PromptsIO +import cue4s.CompletionError object Main extends CommandIOApp("dex", "searches scaladex for Scala libraries"): @@ -24,18 +23,31 @@ object Main def main = (BuildTool.opt, LibName.argument).mapN { (maybeBuildTool, maybeLibName) => - for - libName <- maybeLibName.map(IO.pure(_)).getOrElse(promptLibName) - projects <- scaladex.search(libName.value) - project <- promptProject(projects) - modules <- promptModules(project) - details <- scaladex.project(project.organization, project.repository) - version <- promptVersion(details) - buildTool <- maybeBuildTool.map(IO.pure(_)).getOrElse(promptBuildTool) - content = format(buildTool, details.groupId, modules, version) - _ <- copyToClipboard(content) - _ <- IO.println(doneMessage) - yield ExitCode(0) + PromptsIO.make + .use: prompts => + for + libName <- maybeLibName + .map(IO.pure(_)) + .getOrElse(promptLibName(prompts)) + projects <- scaladex.search(libName.value) + project <- promptProject(prompts, projects) + modules <- promptModules(prompts, project) + details <- scaladex.project( + project.organization, + project.repository + ) + version <- promptVersion(prompts, details) + buildTool <- maybeBuildTool + .map(IO.pure(_)) + .getOrElse(promptBuildTool(prompts)) + content = format(buildTool, details.groupId, modules, version) + _ <- copyToClipboard(content) + _ <- IO.println(doneMessage) + yield ExitCode(0) + .recoverWith: + case CompletionError.Interrupted => + IO.println("cancelled").as(ExitCode.Error) + case CompletionError.Error(msg) => IO.println(msg).as(ExitCode.Error) } end main @@ -45,14 +57,14 @@ object Main modules: js.Array[String], version: String ): List[String] = modules.toList.map { case module => - buildTool match - case BuildTool.SBT => s""""$groupId" %% "$module" % "$version"""" - case BuildTool.Mill => s"""ivy"$groupId::$module:$version"""" - case BuildTool.Bleep => s"""$groupId::$module:$version""" - case BuildTool.ScalaCLI => - s"""//> using lib "$groupId::$module:$version"""" - case BuildTool.Ammonite => - s"""import $$ivy.`$groupId::$module:$version`""" - } + buildTool match + case BuildTool.SBT => s""""$groupId" %% "$module" % "$version"""" + case BuildTool.Mill => s"""ivy"$groupId::$module:$version"""" + case BuildTool.Bleep => s"""$groupId::$module:$version""" + case BuildTool.ScalaCLI => + s"""//> using lib "$groupId::$module:$version"""" + case BuildTool.Ammonite => + s"""import $$ivy.`$groupId::$module:$version`""" + } end Main diff --git a/src/main/scala/dex/prompts.scala b/src/main/scala/dex/prompts.scala index ef3ce0d..89b5952 100644 --- a/src/main/scala/dex/prompts.scala +++ b/src/main/scala/dex/prompts.scala @@ -6,104 +6,57 @@ import scala.scalajs.js.Promise import scala.scalajs.js.Thenable import cats.effect.IO import scala.scalajs.js.JSConverters._ - -@js.native -@JSImport("prompts", JSImport.Namespace) -object prompts extends js.Any: - def default[T](x: js.Object): Promise[T] = js.native - -def ioPrompt[T](x: js.Object): IO[T] = - IO.fromPromise(IO(prompts.default(x))) - -def promptLibName: IO[LibName] = - val libNameQuestion = new js.Object { - val `type` = "text" - val name = "libName" - val message = "Which library ?" - } - - trait LibNameAnswer extends js.Object { - val libName: String - } - - ioPrompt[LibNameAnswer](libNameQuestion).map(_.libName).map(LibName(_)) -end promptLibName - -def promptProject(project: js.Array[Project]): IO[Project] = - val projectQuestion = new js.Object { - val `type` = "autocomplete" - val name = "project" - val message = "From which repo ?" - val choices = project.map { p => - new js.Object { - val title = s"${p.repository} from ${p.organization}" - val value = p - } - } - } - - trait ProjectAnswer extends js.Object { - val project: Project - } - - ioPrompt[ProjectAnswer](projectQuestion).map(_.project) +import cue4s.*, catseffect.PromptsIO + +def promptLibName(prompts: PromptsIO): IO[LibName] = + prompts + .text("Which library?") + .map(_.toEither) + .flatMap(IO.fromEither) + .map(LibName(_)) + +def promptProject(prompts: PromptsIO, project: js.Array[Project]): IO[Project] = + val mapping = project.toList + .map: p => + val title = s"${p.repository} from ${p.organization}" + val value = p + + title -> p + .toMap + + if mapping.isEmpty then IO.raiseError(CompletionError.Error("no repos found")) + else + prompts + .singleChoice("From which repo?", mapping.keySet.toList.sorted) + .map(_.toEither) + .flatMap(IO.fromEither) + .map(mapping(_)) end promptProject -def promptModules(project: Project): IO[js.Array[String]] = - val moduleQuestion = new js.Object { - val `type` = "autocompleteMultiselect" - val name = "modules" - val message = "Which modules ?" - val choices = project.artifacts.map { artifact => - new js.Object { - val title = artifact - val value = artifact - } - } - } - - trait ProjectAnswer extends js.Object { - val modules: js.Array[String] - } +def promptModules(prompts: PromptsIO, project: Project): IO[js.Array[String]] = + val choices = project.artifacts.toList - ioPrompt[ProjectAnswer](moduleQuestion).map(_.modules) + prompts + .multiChoiceNoneSelected("Which modules?", choices) + .map(_.toEither) + .flatMap(IO.fromEither) + .map(_.toJSArray) end promptModules -def promptVersion(project: ProjectDetails): IO[String] = - val versionQuestion = new js.Object { - val `type` = "autocomplete" - val name = "version" - val message = "Which version ?" - val choices = project.versions.map { version => - new js.Object { - val title = version - val value = version - } - } - } - - trait VersionAnswer extends js.Object: - val version: String - - ioPrompt[VersionAnswer](versionQuestion).map(_.version) +def promptVersion(prompts: PromptsIO, project: ProjectDetails): IO[String] = + prompts + .singleChoice("Which version?", project.versions.toList) + .map(_.toEither) + .flatMap(IO.fromEither) end promptVersion -def promptBuildTool: IO[BuildTool] = - val versionQuestion = new js.Object { - val `type` = "select" - val name = "buildTool" - val message = "Which build tool ?" - val choices = BuildTool.values.map { bt => - new js.Object { - val title = bt.toString() - val value = bt - } - }.toJSArray - } - - trait VersionAnswer extends js.Object { - val buildTool: BuildTool - } - - ioPrompt[VersionAnswer](versionQuestion).map(_.buildTool) +def promptBuildTool(prompts: PromptsIO): IO[BuildTool] = + prompts + .singleChoice( + "Which build tool?", + BuildTool.values.map(_.toString).toList + ) + .map(_.toEither) + .flatMap(IO.fromEither) + .map(BuildTool.valueOf) end promptBuildTool