From b73cb5639342930a8b8367f7f33f7c5ca9292495 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Wed, 9 Nov 2022 14:25:14 +0100 Subject: [PATCH 1/3] Add pprint to test dependencies --- build.sbt | 7 +++++-- project/Deps.scala | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 66b2d8fd..7ff0789b 100644 --- a/build.sbt +++ b/build.sbt @@ -115,8 +115,11 @@ lazy val tests = crossProject(JSPlatform, JVMPlatform, NativePlatform) .settings( shared, caseAppPrefix, - publish / skip := true, - libraryDependencies += Deps.utest.value % Test, + publish / skip := true, + libraryDependencies ++= Seq( + Deps.pprint.value % Test, + Deps.utest.value % Test + ), testFrameworks += new TestFramework("utest.runner.Framework") ) diff --git a/project/Deps.scala b/project/Deps.scala index b7041063..94672956 100644 --- a/project/Deps.scala +++ b/project/Deps.scala @@ -11,6 +11,7 @@ object Deps { def catsEffect3 = setting("org.typelevel" %%% "cats-effect" % "3.3.14") def dataClass = "io.github.alexarchambault" %% "data-class" % "0.2.6" def macroParadise = "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.patch + def pprint = setting("com.lihaoyi" %%% "pprint" % "0.8.0") def scalaCompiler = setting("org.scala-lang" % "scala-compiler" % scalaVersion.value) def scalaReflect = setting("org.scala-lang" % "scala-reflect" % scalaVersion.value) def shapeless = setting("com.chuusai" %%% "shapeless" % "2.3.10") From fe6cad5bfe49ad6cf2f1acdea21033083d910a20 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Wed, 9 Nov 2022 14:42:05 +0100 Subject: [PATCH 2/3] Reset MiMA --- project/Mima.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project/Mima.scala b/project/Mima.scala index d11e122c..0d60b4f3 100644 --- a/project/Mima.scala +++ b/project/Mima.scala @@ -8,13 +8,12 @@ import scala.sys.process._ object Mima { def binaryCompatibilityVersions: Set[String] = - Seq("git", "tag", "--merged", "HEAD^", "--contains", "cacc9a0340fde584a10a814037db6a8947881931") + Seq("git", "tag", "--merged", "HEAD^", "--contains", "5a653bdd41972587bd3fb3d9751c0e0cf11c1e74") .!! .linesIterator .map(_.trim) .filter(_.startsWith("v")) .map(_.stripPrefix("v")) - .filter(_ != "2.1.0-M19") .toSet def settings = Def.settings( From 22123fd76ec86c6e52fec482a24c129081faa4de Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Wed, 9 Nov 2022 14:25:39 +0100 Subject: [PATCH 3/3] Ensure WithFullHelp args have a non-empty origin field --- .../core/help/WithFullHelpCompanion.scala | 68 +++++++++++++++---- .../core/help/WithFullHelpCompanion.scala | 64 +++++++++++++---- .../src/test/scala/caseapp/ParserTests.scala | 18 +++++ 3 files changed, 126 insertions(+), 24 deletions(-) diff --git a/core/shared/src/main/scala-2/caseapp/core/help/WithFullHelpCompanion.scala b/core/shared/src/main/scala-2/caseapp/core/help/WithFullHelpCompanion.scala index d0d5b3da..1f0799fa 100644 --- a/core/shared/src/main/scala-2/caseapp/core/help/WithFullHelpCompanion.scala +++ b/core/shared/src/main/scala-2/caseapp/core/help/WithFullHelpCompanion.scala @@ -1,19 +1,63 @@ package caseapp.core.help -import caseapp.core.parser.Parser -import caseapp.{ExtraName, HelpMessage} +import caseapp.{ExtraName, Group, HelpMessage, Parser} +import caseapp.core.parser.{Argument, NilParser, StandardArgument} +import caseapp.core.{Arg, Error} +import caseapp.core.parser.RecursiveConsParser +import caseapp.core.util.Formatter + +import shapeless.{HNil, :: => :*:} abstract class WithFullHelpCompanion { - implicit def parser[T, D](implicit underlying: Parser.Aux[T, D]): Parser[WithFullHelp[T]] = - Parser.nil - .addAll[WithHelp[T]].apply - .add[Boolean]( - "helpFull", - default = Some(false), - extraNames = Seq(ExtraName("fullHelp")), - helpMessage = Some(HelpMessage("Print help message, including hidden options, and exit")) - ) - .as[WithFullHelp[T]] + implicit def parser[T, D](implicit + underlying: Parser.Aux[T, D] + ): Parser.Aux[ + WithFullHelp[T], + (Option[Boolean] :*: Option[Boolean] :*: D :*: HNil) :*: Option[Boolean] :*: HNil + ] = { + + val baseHelpArgument = StandardArgument[Boolean]( + Arg("helpFull") + .withExtraNames(Seq( + ExtraName("fullHelp"), + ExtraName("-help-full"), + ExtraName("-full-help") + )) + .withGroup(Some(Group("Help"))) + .withOrigin(Some("WithFullHelp")) + .withHelpMessage(Some( + HelpMessage("Print help message, including hidden options, and exit") + )) + .withIsFlag(true) + ).withDefault(() => Some(false)) + + // accept "-help" too (single dash) + val helpArgument: Argument[Boolean] = + new Argument[Boolean] { + def arg = baseHelpArgument.arg + def withDefaultOrigin(origin: String) = + this + def init = baseHelpArgument.init + def step( + args: List[String], + index: Int, + d: Option[Boolean], + nameFormatter: Formatter[ExtraName] + ): Either[(Error, List[String]), Option[(Option[Boolean], List[String])]] = + args match { + case ("-help-full" | "-full-help") :: rem => Right(Some((Some(true), rem))) + case _ => baseHelpArgument.step(args, index, d, nameFormatter) + } + def get(d: Option[Boolean], nameFormatter: Formatter[ExtraName]) = + baseHelpArgument.get(d, nameFormatter) + } + + val withHelpParser = WithHelp.parser[T, D](underlying) + + val p = RecursiveConsParser(withHelpParser, helpArgument :: NilParser) + + p.to[WithFullHelp[T]] + } } diff --git a/core/shared/src/main/scala-3/caseapp/core/help/WithFullHelpCompanion.scala b/core/shared/src/main/scala-3/caseapp/core/help/WithFullHelpCompanion.scala index e94e269b..6433c4d2 100644 --- a/core/shared/src/main/scala-3/caseapp/core/help/WithFullHelpCompanion.scala +++ b/core/shared/src/main/scala-3/caseapp/core/help/WithFullHelpCompanion.scala @@ -1,19 +1,59 @@ package caseapp.core.help -import caseapp.core.parser.Parser -import caseapp.{ExtraName, HelpMessage} +import caseapp.{ExtraName, Group, HelpMessage, Parser} +import caseapp.core.Scala3Helpers.* +import caseapp.core.parser.{Argument, NilParser, StandardArgument} +import caseapp.core.{Arg, Error} +import caseapp.core.parser.RecursiveConsParser +import caseapp.core.util.Formatter abstract class WithFullHelpCompanion { - implicit def parser[T: Parser]: Parser[WithFullHelp[T]] = - Parser.nil - .addAll[WithHelp[T]](using WithHelp.parser[T]) - .add[Boolean]( - "helpFull", - default = Some(false), - extraNames = Seq(ExtraName("fullHelp")), - helpMessage = Some(HelpMessage("Print help message, including hidden options, and exit")) - ) - .as[WithFullHelp[T]] + implicit def parser[T](implicit + underlying: Parser[T] + ): Parser[WithFullHelp[T]] = { + + val baseHelpArgument = StandardArgument[Boolean]( + Arg("helpFull") + .withExtraNames(Seq( + ExtraName("fullHelp"), + ExtraName("-help-full"), + ExtraName("-full-help") + )) + .withGroup(Some(Group("Help"))) + .withOrigin(Some("WithFullHelp")) + .withHelpMessage(Some( + HelpMessage("Print help message, including hidden options, and exit") + )) + .withIsFlag(true) + ).withDefault(() => Some(false)) + + // accept "-help" too (single dash) + val helpArgument: Argument[Boolean] = + new Argument[Boolean] { + def arg = baseHelpArgument.arg + def withDefaultOrigin(origin: String) = + this + def init = baseHelpArgument.init + def step( + args: List[String], + index: Int, + d: Option[Boolean], + nameFormatter: Formatter[ExtraName] + ): Either[(Error, List[String]), Option[(Option[Boolean], List[String])]] = + args match { + case ("-help-full" | "-full-help") :: rem => Right(Some((Some(true), rem))) + case _ => baseHelpArgument.step(args, index, d, nameFormatter) + } + def get(d: Option[Boolean], nameFormatter: Formatter[ExtraName]) = + baseHelpArgument.get(d, nameFormatter) + } + + val withHelpParser = WithHelp.parser[T](underlying) + + val p = RecursiveConsParser(withHelpParser, helpArgument :: NilParser) + + p.to[WithFullHelp[T]] + } } diff --git a/tests/shared/src/test/scala/caseapp/ParserTests.scala b/tests/shared/src/test/scala/caseapp/ParserTests.scala index 121a0c1d..0cf34f6c 100644 --- a/tests/shared/src/test/scala/caseapp/ParserTests.scala +++ b/tests/shared/src/test/scala/caseapp/ParserTests.scala @@ -1,6 +1,7 @@ package caseapp import caseapp.core.{Arg, Error, Indexed} +import caseapp.core.help.{WithFullHelp, WithHelp} import caseapp.core.parser.{Argument, NilParser, StandardArgument} import caseapp.core.parser.ParserOps import caseapp.core.util.Formatter @@ -87,6 +88,23 @@ object ParserTests extends TestSuite { assert(numFooArg.origin == Some("FewArgs")) } + test("WithHelp args have an origin") { + case class Dummy() + val parser: Parser[WithHelp[Dummy]] = WithHelp.parser + val args = parser.args + assert(args.nonEmpty) + assert(args.forall(_.origin.contains("WithHelp"))) + } + + test("WithFullHelp args have an origin") { + case class Dummy() + val parser: Parser[WithFullHelp[Dummy]] = WithFullHelp.parser + val args = parser.args + assert(args.exists(_.origin.contains("WithHelp"))) + assert(args.exists(_.origin.contains("WithFullHelp"))) + assert(args.forall(_.origin.exists(o => o == "WithHelp" || o == "WithFullHelp"))) + } + test("Custom Argument type") { case class Helper(n: Int, values: List[String])