From 76fae9bbaec043610e252b4adebb66d9870ad054 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Fri, 29 Nov 2024 09:02:15 +0000 Subject: [PATCH 1/4] WIP: cue4s port --- .scalafmt.conf | 2 +- build.sbt | 6 +- project/build.properties | 2 +- project/plugins.sbt | 2 +- src/main/scala/dex/main.scala | 48 +++++------ src/main/scala/dex/prompts.scala | 139 ++++++++++--------------------- 6 files changed, 76 insertions(+), 123 deletions(-) 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..6eaca68 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" % "dev" ) // 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..5e0b30a 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,7 @@ import cats.syntax.all._ import com.monovore.decline.effect.CommandIOApp import cats.effect.IO import cats.effect.ExitCode +import cue4s.catseffect.PromptsIO object Main extends CommandIOApp("dex", "searches scaladex for Scala libraries"): @@ -24,18 +22,20 @@ 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().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) } end main @@ -45,14 +45,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..3149171 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 + .io(Prompt.Input("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 + + prompts + .io(Prompt.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 + .io(Prompt.MultipleChoice.withNoneSelected("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 + .io(Prompt.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 + .io( + Prompt.SingleChoice( + "Which build tool?", + BuildTool.values.map(_.toString).toList + ) + ) + .map(_.toEither) + .flatMap(IO.fromEither) + .map(BuildTool.valueOf) end promptBuildTool From cdd32c448d836a05cb6217c8358df7a8c4847bcc Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Fri, 29 Nov 2024 10:03:34 +0000 Subject: [PATCH 2/4] Error handling --- src/main/scala/dex/main.scala | 22 +++++++++++++++++----- src/main/scala/dex/prompts.scala | 12 +++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/scala/dex/main.scala b/src/main/scala/dex/main.scala index 5e0b30a..9c143a2 100644 --- a/src/main/scala/dex/main.scala +++ b/src/main/scala/dex/main.scala @@ -14,6 +14,7 @@ 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"): @@ -22,20 +23,31 @@ object Main def main = (BuildTool.opt, LibName.argument).mapN { (maybeBuildTool, maybeLibName) => - PromptsIO().use: - prompts => + PromptsIO() + .use: prompts => for - libName <- maybeLibName.map(IO.pure(_)).getOrElse(promptLibName(prompts)) + 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) + details <- scaladex.project( + project.organization, + project.repository + ) version <- promptVersion(prompts, details) - buildTool <- maybeBuildTool.map(IO.pure(_)).getOrElse(promptBuildTool(prompts)) + 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 diff --git a/src/main/scala/dex/prompts.scala b/src/main/scala/dex/prompts.scala index 3149171..d237c6e 100644 --- a/src/main/scala/dex/prompts.scala +++ b/src/main/scala/dex/prompts.scala @@ -24,11 +24,13 @@ def promptProject(prompts: PromptsIO, project: js.Array[Project]): IO[Project] = title -> p .toMap - prompts - .io(Prompt.SingleChoice("From which repo?", mapping.keySet.toList.sorted)) - .map(_.toEither) - .flatMap(IO.fromEither) - .map(mapping(_)) + if mapping.isEmpty then IO.raiseError(CompletionError.Error("no repos found")) + else + prompts + .io(Prompt.SingleChoice("From which repo?", mapping.keySet.toList.sorted)) + .map(_.toEither) + .flatMap(IO.fromEither) + .map(mapping(_)) end promptProject def promptModules(prompts: PromptsIO, project: Project): IO[js.Array[String]] = From 9fe96b71a5f5e8340eb0ede02b200d35d363bdb5 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Sat, 30 Nov 2024 20:19:56 +0000 Subject: [PATCH 3/4] Update to stable cue4s 0.0.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6eaca68..7c5ae62 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ scalaVersion := "3.3.4" // or any other Scala version >= 2.11.12 libraryDependencies ++= Seq( "com.monovore" %%% "decline-effect" % "2.4.1", - "tech.neander" %%% "cue4s-cats-effect" % "dev" + "tech.neander" %%% "cue4s-cats-effect" % "0.0.3" ) // This is an application with a main method From dd1d8c62a75e38976219c9a9226f4974a5380781 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Fri, 6 Dec 2024 12:52:46 +0000 Subject: [PATCH 4/4] Update to 0.0.6 --- build.sbt | 2 +- src/main/scala/dex/main.scala | 2 +- src/main/scala/dex/prompts.scala | 16 +++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index 7c5ae62..40056ec 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ scalaVersion := "3.3.4" // or any other Scala version >= 2.11.12 libraryDependencies ++= Seq( "com.monovore" %%% "decline-effect" % "2.4.1", - "tech.neander" %%% "cue4s-cats-effect" % "0.0.3" + "tech.neander" %%% "cue4s-cats-effect" % "0.0.6" ) // This is an application with a main method diff --git a/src/main/scala/dex/main.scala b/src/main/scala/dex/main.scala index 9c143a2..e363ba6 100644 --- a/src/main/scala/dex/main.scala +++ b/src/main/scala/dex/main.scala @@ -23,7 +23,7 @@ object Main def main = (BuildTool.opt, LibName.argument).mapN { (maybeBuildTool, maybeLibName) => - PromptsIO() + PromptsIO.make .use: prompts => for libName <- maybeLibName diff --git a/src/main/scala/dex/prompts.scala b/src/main/scala/dex/prompts.scala index d237c6e..89b5952 100644 --- a/src/main/scala/dex/prompts.scala +++ b/src/main/scala/dex/prompts.scala @@ -10,7 +10,7 @@ import cue4s.*, catseffect.PromptsIO def promptLibName(prompts: PromptsIO): IO[LibName] = prompts - .io(Prompt.Input("Which library?")) + .text("Which library?") .map(_.toEither) .flatMap(IO.fromEither) .map(LibName(_)) @@ -27,7 +27,7 @@ def promptProject(prompts: PromptsIO, project: js.Array[Project]): IO[Project] = if mapping.isEmpty then IO.raiseError(CompletionError.Error("no repos found")) else prompts - .io(Prompt.SingleChoice("From which repo?", mapping.keySet.toList.sorted)) + .singleChoice("From which repo?", mapping.keySet.toList.sorted) .map(_.toEither) .flatMap(IO.fromEither) .map(mapping(_)) @@ -37,7 +37,7 @@ def promptModules(prompts: PromptsIO, project: Project): IO[js.Array[String]] = val choices = project.artifacts.toList prompts - .io(Prompt.MultipleChoice.withNoneSelected("Which modules?", choices)) + .multiChoiceNoneSelected("Which modules?", choices) .map(_.toEither) .flatMap(IO.fromEither) .map(_.toJSArray) @@ -45,18 +45,16 @@ end promptModules def promptVersion(prompts: PromptsIO, project: ProjectDetails): IO[String] = prompts - .io(Prompt.SingleChoice("Which version?", project.versions.toList)) + .singleChoice("Which version?", project.versions.toList) .map(_.toEither) .flatMap(IO.fromEither) end promptVersion def promptBuildTool(prompts: PromptsIO): IO[BuildTool] = prompts - .io( - Prompt.SingleChoice( - "Which build tool?", - BuildTool.values.map(_.toString).toList - ) + .singleChoice( + "Which build tool?", + BuildTool.values.map(_.toString).toList ) .map(_.toEither) .flatMap(IO.fromEither)