From 343958bcef3e6589a55c5d8c824259b5a6550a0e Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 11:03:34 +0800 Subject: [PATCH 01/17] wip --- mainargs/src-2/Macros.scala | 9 +++++---- mainargs/src/Invoker.scala | 20 ++++++++++++++------ mainargs/src/Model.scala | 20 -------------------- mainargs/src/Renderer.scala | 11 +++++++---- mainargs/src/TokensReader.scala | 15 ++++++++++++++- mainargs/test/src/VarargsTests.scala | 8 ++++---- 6 files changed, 44 insertions(+), 39 deletions(-) diff --git a/mainargs/src-2/Macros.scala b/mainargs/src-2/Macros.scala index 04b6e8f..2e31b51 100755 --- a/mainargs/src-2/Macros.scala +++ b/mainargs/src-2/Macros.scala @@ -115,10 +115,11 @@ class Macros(val c: Context) { case _ => q"new _root_.mainargs.arg()" } val argSig = if (vararg) q""" - _root_.mainargs.ArgSig.createVararg[$varargUnwrappedType, $curCls]( - ${arg.name.decoded}, - $instantiateArg, - ).widen[_root_.scala.Any] + _root_.mainargs.ArgSig.create[_root_.mainargs.Leftover[$varargUnwrappedType], $curCls]( + ${arg.name.decoded}, + $instantiateArg, + $defaultOpt + ).widen[_root_.scala.Any] """ else q""" _root_.mainargs.ArgSig.create[$varargUnwrappedType, $curCls]( ${arg.name.decoded}, diff --git a/mainargs/src/Invoker.scala b/mainargs/src/Invoker.scala index a543f25..0f8c9d4 100644 --- a/mainargs/src/Invoker.scala +++ b/mainargs/src/Invoker.scala @@ -13,7 +13,10 @@ object Invoker { cep.main.argSigs, allowPositional, allowRepeats, - cep.main.leftoverArgSig.nonEmpty + cep.main.argSigs0.exists { + case x: ArgSig.Simple[_, _] => x.reader.isLeftover + case _ => false + } ) .flatMap(invoke(cep.companion(), cep.main, _)) } @@ -28,9 +31,11 @@ object Invoker { a match { case a: ArgSig.Flag[B] => Right(ParamResult.Success(Flag(kvs.contains(a)).asInstanceOf[T])) - case a: ArgSig.Simple[T, B] => Right(makeReadCall(kvs, base, a)) - case a: ArgSig.Leftover[T, B] => - Right(makeReadVarargsCall(a, extras).map(x => Leftover(x: _*).asInstanceOf[T])) + + case a: ArgSig.Simple[T, B] => + if (!a.reader.isLeftover) Right(makeReadCall(kvs, base, a)) + else Right(makeReadVarargsCall(a, extras).map(x => Leftover(x: _*).asInstanceOf[T])) + case a: ArgSig.Class[T, B] => Left( invoke0[T, B]( @@ -87,7 +92,10 @@ object Invoker { main.argSigs, allowPositional, allowRepeats, - main.leftoverArgSig.nonEmpty + main.argSigs0.exists { + case x: ArgSig.Simple[_, _] => x.reader.isLeftover + case _ => false + } ) .flatMap(Invoker.invoke(mains.base(), main, _)) ) @@ -148,7 +156,7 @@ object Invoker { } def makeReadVarargsCall[T, B]( - arg: ArgSig.Leftover[T, B], + arg: ArgSig.Simple[T, B], values: Seq[String] ): ParamResult[Seq[T]] = { val attempts = diff --git a/mainargs/src/Model.scala b/mainargs/src/Model.scala index 57d0082..e6734f7 100644 --- a/mainargs/src/Model.scala +++ b/mainargs/src/Model.scala @@ -4,13 +4,6 @@ sealed trait ArgSig[T, B] { def widen[V >: T] = this.asInstanceOf[ArgSig[V, B]] } object ArgSig { - def createVararg[T, B](name0: String, arg: mainargs.arg)(implicit - argParser: ArgReader.Leftover[T] - ) = { - val name = scala.Option(arg.name).getOrElse(name0) - val docOpt = scala.Option(arg.doc) - Leftover[T, B](name, docOpt, argParser.reader) - } def create[T, B](name0: String, arg: mainargs.arg, defaultOpt: Option[B => T])(implicit argParser: ArgReader[T] ): ArgSig[T, B] = { @@ -24,8 +17,6 @@ object ArgSig { argParser match { case ArgReader.Flag() => ArgSig.Flag[B](nameOpt, shortOpt, docOpt) case ArgReader.Class(parser) => Class(parser.mains) - case ArgReader.Leftover(reader: TokensReader[T]) => - Leftover[T, B](scala.Option(arg.name).getOrElse(name0), docOpt, reader) case ArgReader.Simple(reader) => Simple[T, B](nameOpt, shortOpt, docOpt, defaultOpt, reader, arg.positional) } @@ -67,11 +58,6 @@ object ArgSig { } case class Class[T, B](reader: ClassMains[T]) extends ArgSig[T, B] - - case class Leftover[T, B](name0: String, doc: Option[String], reader: TokensReader[T]) - extends ArgSig.Terminal[T, B] { - def name = Some(name0) - } } sealed trait ArgReader[T] @@ -82,9 +68,6 @@ object ArgReader { implicit def createClass[T: SubParser]: Class[T] = Class(implicitly[SubParser[T]]) case class Class[T](x: SubParser[T]) extends ArgReader[T] - implicit def createLeftover[T: TokensReader]: Leftover[T] = Leftover(implicitly[TokensReader[T]]) - case class Leftover[T](reader: TokensReader[T]) extends ArgReader[mainargs.Leftover[T]] - implicit def createFlag: Flag = Flag() case class Flag() extends ArgReader[mainargs.Flag] } @@ -115,9 +98,6 @@ case class MainData[T, B]( val argSigs: Seq[ArgSig.Terminal[_, B]] = argSigs0.iterator.flatMap[ArgSig.Terminal[_, B]](ArgSig.flatten(_)).toVector - val leftoverArgSig: Seq[ArgSig.Leftover[_, _]] = - argSigs.collect { case x: ArgSig.Leftover[_, B] => x } - } object MainData { diff --git a/mainargs/src/Renderer.scala b/mainargs/src/Renderer.scala index 2430af1..9126638 100644 --- a/mainargs/src/Renderer.scala +++ b/mainargs/src/Renderer.scala @@ -19,22 +19,25 @@ object Renderer { val shortPrefix = arg.shortName.map(c => s"-$c") val nameSuffix = arg.name.map(s => s"--$s") (shortPrefix ++ nameSuffix).mkString(" ") + + case arg: ArgSig.Simple[_, _] if arg.reader.isLeftover => None + s"${arg.name} <${arg.reader.shortName}>..." + case arg: ArgSig.Simple[_, _] => val shortPrefix = arg.shortName.map(c => s"-$c") val typeSuffix = s"<${arg.typeString}>" val nameSuffix = if (arg.positional) arg.name else arg.name.map(s => s"--$s") (shortPrefix ++ nameSuffix ++ Seq(typeSuffix)).mkString(" ") - case arg: ArgSig.Leftover[_, _] => - s"${arg.name0} <${arg.reader.shortName}>..." + } /** * Returns a `Some[string]` with the sortable string or a `None` if it is an leftover. */ private def sortableName(arg: ArgSig.Terminal[_, _]): Option[String] = arg match { - case l: ArgSig.Leftover[_, _] => - None + case arg: ArgSig.Simple[_, _] if arg.reader.isLeftover => None + case a: ArgSig.Named[_, _] => a.shortName.map(_.toString).orElse(a.name).orElse(Some("")) case a: ArgSig.Terminal[_, _] => diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 4e4c9cb..c15a4a5 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -6,7 +6,8 @@ class TokensReader[T]( val read: Seq[String] => Either[String, T], val alwaysRepeatable: Boolean = false, val allowEmpty: Boolean = false, - val noTokens: Boolean = false + val noTokens: Boolean = false, + val isLeftover: Boolean = false ) object TokensReader { def tryEither[T](f: => T) = @@ -25,6 +26,18 @@ object TokensReader { extends TokensReader[Float]("float", strs => tryEither(strs.last.toFloat)) implicit object DoubleRead extends TokensReader[Double]("double", strs => tryEither(strs.last.toDouble)) + implicit def LeftoverRead[T: TokensReader]: TokensReader[Leftover[T]] = + new TokensReader[Leftover[T]]( + "leftover", + strs => { + val (failures, successes) = + strs.map(s => implicitly[TokensReader[T]].read(Seq(s))).partitionMap(identity) + + if (failures.nonEmpty) Left(failures.head) + else Right(Leftover(successes:_*)) + }, + isLeftover = true + ) implicit def OptionRead[T: TokensReader]: TokensReader[Option[T]] = new TokensReader[Option[T]]( implicitly[TokensReader[T]].shortName, diff --git a/mainargs/test/src/VarargsTests.scala b/mainargs/test/src/VarargsTests.scala index cea2a97..9b38d8a 100644 --- a/mainargs/test/src/VarargsTests.scala +++ b/mainargs/test/src/VarargsTests.scala @@ -42,7 +42,7 @@ trait VarargsTests extends TestSuite { case Result.Failure.InvalidArguments( List( Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), + ArgSig.Simple(Some("nums"), _, _, _, _, _), Seq("--nums"), """java.lang.NumberFormatException: For input string: "--nums"""" | """java.lang.NumberFormatException: --nums""" @@ -57,7 +57,7 @@ trait VarargsTests extends TestSuite { case Result.Failure.InvalidArguments( List( Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), + ArgSig.Simple(Some("nums"), _, _, _, _, _), Seq("--nums"), "java.lang.NumberFormatException: For input string: \"--nums\"" | "java.lang.NumberFormatException: --nums" @@ -89,13 +89,13 @@ trait VarargsTests extends TestSuite { case Result.Failure.InvalidArguments( List( Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), + ArgSig.Simple(Some("nums"), _, _, _, _, _), Seq("aa"), "java.lang.NumberFormatException: For input string: \"aa\"" | "java.lang.NumberFormatException: aa" ), Result.ParamError.Failed( - ArgSig.Leftover("nums", _, _), + ArgSig.Simple(Some("nums"), _, _, _, _, _), Seq("bb"), "java.lang.NumberFormatException: For input string: \"bb\"" | "java.lang.NumberFormatException: bb" From adf829db4709153489fdad34591cae585b4d98ba Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 11:44:20 +0800 Subject: [PATCH 02/17] test --- mainargs/src/Invoker.scala | 30 ++++++++-------- mainargs/src/Renderer.scala | 4 +-- mainargs/src/TokenGrouping.scala | 10 +++--- mainargs/src/TokensReader.scala | 23 ++++++------ mainargs/test/src/VarargsTests.scala | 12 ++----- mainargs/test/src/WrappedVarargsTests.scala | 39 +++++++++++++++++++++ 6 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 mainargs/test/src/WrappedVarargsTests.scala diff --git a/mainargs/src/Invoker.scala b/mainargs/src/Invoker.scala index 0f8c9d4..611e033 100644 --- a/mainargs/src/Invoker.scala +++ b/mainargs/src/Invoker.scala @@ -20,6 +20,7 @@ object Invoker { ) .flatMap(invoke(cep.companion(), cep.main, _)) } + def invoke0[T, B]( base: B, mainData: MainData[T, B], @@ -34,7 +35,7 @@ object Invoker { case a: ArgSig.Simple[T, B] => if (!a.reader.isLeftover) Right(makeReadCall(kvs, base, a)) - else Right(makeReadVarargsCall(a, extras).map(x => Leftover(x: _*).asInstanceOf[T])) + else Right(makeReadVarargsCall(a, extras)) case a: ArgSig.Class[T, B] => Left( @@ -158,21 +159,20 @@ object Invoker { def makeReadVarargsCall[T, B]( arg: ArgSig.Simple[T, B], values: Seq[String] - ): ParamResult[Seq[T]] = { - val attempts = - for (token <- values) - yield tryEither( - arg.reader.read(Seq(token)), - Result.ParamError.Exception(arg, Seq(token), _) - ) match { - case Left(x) => Left(x) - case Right(Left(errMsg)) => Left(Result.ParamError.Failed(arg, Seq(token), errMsg)) - case Right(Right(v)) => Right(v) - } + ): ParamResult[T] = { + val eithers = + tryEither( + arg.reader.read(values), + Result.ParamError.Exception(arg, values, _) + ) match { + case Left(x) => Left(x) + case Right(Left(errMsg)) => Left(Result.ParamError.Failed(arg, values, errMsg)) + case Right(Right(v)) => Right(v) + } - attempts.collect { case Left(x) => x } match { - case Nil => ParamResult.Success(attempts.collect { case Right(x) => x }) - case bad => ParamResult.Failure(bad) + eithers match{ + case Left(s) => ParamResult.Failure(Seq(s)) + case Right(v) => ParamResult.Success(v) } } } diff --git a/mainargs/src/Renderer.scala b/mainargs/src/Renderer.scala index 9126638..8739502 100644 --- a/mainargs/src/Renderer.scala +++ b/mainargs/src/Renderer.scala @@ -20,8 +20,8 @@ object Renderer { val nameSuffix = arg.name.map(s => s"--$s") (shortPrefix ++ nameSuffix).mkString(" ") - case arg: ArgSig.Simple[_, _] if arg.reader.isLeftover => None - s"${arg.name} <${arg.reader.shortName}>..." + case arg: ArgSig.Simple[_, _] if arg.reader.isLeftover => + s"${arg.name.get} <${arg.reader.asInstanceOf[TokensReader.LeftoverRead[_]].wrapped.shortName}>..." case arg: ArgSig.Simple[_, _] => val shortPrefix = arg.shortName.map(c => s"-$c") diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index 6ef5186..61d2a85 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -18,6 +18,7 @@ object TokenGrouping { val positionalArgSigs = argSigs .filter { + case x: ArgSig.Simple[_, _] if x.reader.isLeftover => false case x: ArgSig.Simple[_, _] if x.reader.noTokens => false case x: ArgSig.Simple[_, _] if x.positional => true case x => allowPositional @@ -39,14 +40,14 @@ object TokenGrouping { keywordArgMap.get(head) match { case Some(cliArg: ArgSig.Flag[_]) => rec(rest, Util.appendMap(current, cliArg, "")) - case Some(cliArg: ArgSig.Simple[_, _]) => + case Some(cliArg: ArgSig.Simple[_, _]) if !cliArg.reader.isLeftover => rest match { case next :: rest2 => rec(rest2, Util.appendMap(current, cliArg, next)) case Nil => Result.Failure.MismatchedArguments(Nil, Nil, Nil, incomplete = Some(cliArg)) } - case None => complete(remaining, current) + case _ => complete(remaining, current) } } else { positionalArgSigs.find(!current.contains(_)) match { @@ -56,9 +57,9 @@ object TokenGrouping { } case _ => complete(remaining, current) - } } + def complete( remaining: List[String], current: Map[ArgSig.Named[_, B], Vector[String]] @@ -73,12 +74,13 @@ object TokenGrouping { .toSeq val missing = argSigs - .collect { case x: ArgSig.Simple[_, _] => x } + .collect { case x: ArgSig.Simple[_, _] if !x.reader.isLeftover => x } .filter { x => !x.reader.allowEmpty && x.default.isEmpty && !current.contains(x) } + val unknown = if (allowLeftover) Nil else remaining if (missing.nonEmpty || duplicates.nonEmpty || unknown.nonEmpty) { Result.Failure.MismatchedArguments( diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index c15a4a5..1a607a3 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -26,18 +26,19 @@ object TokensReader { extends TokensReader[Float]("float", strs => tryEither(strs.last.toFloat)) implicit object DoubleRead extends TokensReader[Double]("double", strs => tryEither(strs.last.toDouble)) - implicit def LeftoverRead[T: TokensReader]: TokensReader[Leftover[T]] = - new TokensReader[Leftover[T]]( - "leftover", - strs => { - val (failures, successes) = - strs.map(s => implicitly[TokensReader[T]].read(Seq(s))).partitionMap(identity) + class LeftoverRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader[Leftover[T]]( + "leftover", + strs => { + val (failures, successes) = strs + .map(s => implicitly[TokensReader[T]].read(Seq(s))) + .partitionMap(identity) - if (failures.nonEmpty) Left(failures.head) - else Right(Leftover(successes:_*)) - }, - isLeftover = true - ) + if (failures.nonEmpty) Left(failures.head) + else Right(Leftover(successes: _*)) + }, + isLeftover = true + ) + implicit def LeftoverRead[T: TokensReader]: TokensReader[Leftover[T]] = new LeftoverRead[T] implicit def OptionRead[T: TokensReader]: TokensReader[Option[T]] = new TokensReader[Option[T]]( implicitly[TokensReader[T]].shortName, diff --git a/mainargs/test/src/VarargsTests.scala b/mainargs/test/src/VarargsTests.scala index 9b38d8a..d95c640 100644 --- a/mainargs/test/src/VarargsTests.scala +++ b/mainargs/test/src/VarargsTests.scala @@ -43,7 +43,7 @@ trait VarargsTests extends TestSuite { List( Result.ParamError.Failed( ArgSig.Simple(Some("nums"), _, _, _, _, _), - Seq("--nums"), + Seq("--nums", "31337"), """java.lang.NumberFormatException: For input string: "--nums"""" | """java.lang.NumberFormatException: --nums""" ) @@ -58,7 +58,7 @@ trait VarargsTests extends TestSuite { List( Result.ParamError.Failed( ArgSig.Simple(Some("nums"), _, _, _, _, _), - Seq("--nums"), + Seq("1", "2", "3", "--nums", "4"), "java.lang.NumberFormatException: For input string: \"--nums\"" | "java.lang.NumberFormatException: --nums" ) @@ -90,15 +90,9 @@ trait VarargsTests extends TestSuite { List( Result.ParamError.Failed( ArgSig.Simple(Some("nums"), _, _, _, _, _), - Seq("aa"), + Seq("aa", "bb", "3"), "java.lang.NumberFormatException: For input string: \"aa\"" | "java.lang.NumberFormatException: aa" - ), - Result.ParamError.Failed( - ArgSig.Simple(Some("nums"), _, _, _, _, _), - Seq("bb"), - "java.lang.NumberFormatException: For input string: \"bb\"" | - "java.lang.NumberFormatException: bb" ) ) ) => diff --git a/mainargs/test/src/WrappedVarargsTests.scala b/mainargs/test/src/WrappedVarargsTests.scala new file mode 100644 index 0000000..ae800ac --- /dev/null +++ b/mainargs/test/src/WrappedVarargsTests.scala @@ -0,0 +1,39 @@ +package mainargs +import utest._ + + +object WrappedVarargsTests extends VarargsTests { + // Test that we are able to wrap the `Leftover` type we use for Varargs in + // our own custom types, and have things work + class Wrapper[T](val unwrap: T) + class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader[Wrapper[T]]( + wrapped.shortName, + args => wrapped.read(args).map(new Wrapper(_)), + wrapped.alwaysRepeatable, + wrapped.allowEmpty, + wrapped.noTokens, + wrapped.isLeftover, + ) + + implicit def WrapperRead[T: TokensReader]: TokensReader[Wrapper[T]] = new WrapperRead[T] + + object Base { + @main + def pureVariadic(nums: Wrapper[Leftover[Int]]) = nums.unwrap.value.sum + + @main + def mixedVariadic(@arg(short = 'f') first: Int, args: Wrapper[Leftover[String]]) = { + first + args.unwrap.value.mkString + } + @main + def mixedVariadicWithDefault( + @arg(short = 'f') first: Int = 1337, + args: Wrapper[Leftover[String]] + ) = { + first + args.unwrap.value.mkString + } + } + + val check = new Checker(ParserForMethods(Base), allowPositional = true) + val isNewVarargsTests = true +} From 8999ee04daf5bb45ed3a0ad670f2634799ae5027 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 11:46:00 +0800 Subject: [PATCH 03/17] remove unused noTokens field --- mainargs/src/TokenGrouping.scala | 1 - mainargs/src/TokensReader.scala | 1 - mainargs/test/src/WrappedVarargsTests.scala | 1 - 3 files changed, 3 deletions(-) diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index 61d2a85..f75c998 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -19,7 +19,6 @@ object TokenGrouping { val positionalArgSigs = argSigs .filter { case x: ArgSig.Simple[_, _] if x.reader.isLeftover => false - case x: ArgSig.Simple[_, _] if x.reader.noTokens => false case x: ArgSig.Simple[_, _] if x.positional => true case x => allowPositional } diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 1a607a3..5292498 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -6,7 +6,6 @@ class TokensReader[T]( val read: Seq[String] => Either[String, T], val alwaysRepeatable: Boolean = false, val allowEmpty: Boolean = false, - val noTokens: Boolean = false, val isLeftover: Boolean = false ) object TokensReader { diff --git a/mainargs/test/src/WrappedVarargsTests.scala b/mainargs/test/src/WrappedVarargsTests.scala index ae800ac..0076aed 100644 --- a/mainargs/test/src/WrappedVarargsTests.scala +++ b/mainargs/test/src/WrappedVarargsTests.scala @@ -11,7 +11,6 @@ object WrappedVarargsTests extends VarargsTests { args => wrapped.read(args).map(new Wrapper(_)), wrapped.alwaysRepeatable, wrapped.allowEmpty, - wrapped.noTokens, wrapped.isLeftover, ) From 8ba2adae9e9c8fb8c753d165f4d8258a9d974e7b Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 12:37:45 +0800 Subject: [PATCH 04/17] wip --- mainargs/src/Invoker.scala | 18 +- mainargs/src/Model.scala | 4 +- mainargs/src/Renderer.scala | 19 +- mainargs/src/TokenGrouping.scala | 16 +- mainargs/src/TokensReader.scala | 165 +++++++++++------- ...rargsTests.scala => VarargsOldTests.scala} | 2 +- mainargs/test/src-jvm-2/AmmoniteTests.scala | 7 +- ...argsTests.scala => VarargsBaseTests.scala} | 2 +- mainargs/test/src/VarargsCustomTests.scala | 41 +++++ ...rargsTests.scala => VarargsNewTests.scala} | 2 +- ...sTests.scala => VarargsWrappedTests.scala} | 12 +- 11 files changed, 190 insertions(+), 98 deletions(-) rename mainargs/test/src-2/{OldVarargsTests.scala => VarargsOldTests.scala} (86%) rename mainargs/test/src/{VarargsTests.scala => VarargsBaseTests.scala} (98%) create mode 100644 mainargs/test/src/VarargsCustomTests.scala rename mainargs/test/src/{NewVarargsTests.scala => VarargsNewTests.scala} (91%) rename mainargs/test/src/{WrappedVarargsTests.scala => VarargsWrappedTests.scala} (78%) diff --git a/mainargs/src/Invoker.scala b/mainargs/src/Invoker.scala index 611e033..22e16ec 100644 --- a/mainargs/src/Invoker.scala +++ b/mainargs/src/Invoker.scala @@ -34,8 +34,10 @@ object Invoker { Right(ParamResult.Success(Flag(kvs.contains(a)).asInstanceOf[T])) case a: ArgSig.Simple[T, B] => - if (!a.reader.isLeftover) Right(makeReadCall(kvs, base, a)) - else Right(makeReadVarargsCall(a, extras)) + a.reader match{ + case r: TokensReader.Simple[T] => Right(makeReadCall(kvs, base, a, r)) + case r: TokensReader.Leftover[T, _] => Right(makeReadVarargsCall(a, extras, r)) + } case a: ArgSig.Class[T, B] => Left( @@ -127,7 +129,8 @@ object Invoker { def makeReadCall[T, B]( dict: Map[ArgSig.Named[_, B], Seq[String]], base: B, - arg: ArgSig.Simple[T, B] + arg: ArgSig.Simple[T, B], + reader: TokensReader.Simple[T] ): ParamResult[T] = { def prioritizedDefault = tryEither( arg.default.map(_(base)), @@ -137,14 +140,14 @@ object Invoker { case Right(v) => ParamResult.Success(v) } val tokens = dict.get(arg) match { - case None => if (arg.reader.allowEmpty) Some(Nil) else None + case None => if (reader.allowEmpty) Some(Nil) else None case Some(tokens) => Some(tokens) } val optResult = tokens match { case None => prioritizedDefault case Some(tokens) => tryEither( - arg.reader.read(tokens), + reader.read(tokens), Result.ParamError.Exception(arg, tokens, _) ) match { case Left(ex) => ParamResult.Failure(Seq(ex)) @@ -158,11 +161,12 @@ object Invoker { def makeReadVarargsCall[T, B]( arg: ArgSig.Simple[T, B], - values: Seq[String] + values: Seq[String], + reader: TokensReader.Leftover[T, _] ): ParamResult[T] = { val eithers = tryEither( - arg.reader.read(values), + reader.read(values), Result.ParamError.Exception(arg, values, _) ) match { case Left(x) => Left(x) diff --git a/mainargs/src/Model.scala b/mainargs/src/Model.scala index e6734f7..d96f81c 100644 --- a/mainargs/src/Model.scala +++ b/mainargs/src/Model.scala @@ -44,9 +44,7 @@ object ArgSig { default: Option[B => T], reader: TokensReader[T], positional: Boolean - ) extends ArgSig.Named[T, B] { - def typeString = reader.shortName - } + ) extends ArgSig.Named[T, B] case class Flag[B](name: Option[String], shortName: Option[Char], doc: Option[String]) extends ArgSig.Named[mainargs.Flag, B] diff --git a/mainargs/src/Renderer.scala b/mainargs/src/Renderer.scala index 8739502..83134fd 100644 --- a/mainargs/src/Renderer.scala +++ b/mainargs/src/Renderer.scala @@ -20,16 +20,17 @@ object Renderer { val nameSuffix = arg.name.map(s => s"--$s") (shortPrefix ++ nameSuffix).mkString(" ") - case arg: ArgSig.Simple[_, _] if arg.reader.isLeftover => - s"${arg.name.get} <${arg.reader.asInstanceOf[TokensReader.LeftoverRead[_]].wrapped.shortName}>..." - case arg: ArgSig.Simple[_, _] => - val shortPrefix = arg.shortName.map(c => s"-$c") - val typeSuffix = s"<${arg.typeString}>" - - val nameSuffix = if (arg.positional) arg.name else arg.name.map(s => s"--$s") - (shortPrefix ++ nameSuffix ++ Seq(typeSuffix)).mkString(" ") - + arg.reader match{ + case r: TokensReader.Simple[_] => + val shortPrefix = arg.shortName.map(c => s"-$c") + val typeSuffix = s"<${r.shortName}>" + val nameSuffix = if (arg.positional) arg.name else arg.name.map(s => s"--$s") + (shortPrefix ++ nameSuffix ++ Seq(typeSuffix)).mkString(" ") + + case r: TokensReader.Leftover[_, _] => + s"${arg.name.get} <${r.wrapped.shortName}>..." + } } /** diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index f75c998..ab8f4af 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -68,16 +68,24 @@ object TokenGrouping { .filter { case (a: ArgSig.Flag[_], vs) => vs.size > 1 && !allowRepeats case (a: ArgSig.Simple[_, _], vs) => - vs.size > 1 && !a.reader.alwaysRepeatable && !allowRepeats + a.reader match{ + case r: TokensReader.Simple[_] => vs.size > 1 && !r.alwaysRepeatable && !allowRepeats + case r: TokensReader.Leftover[_, _] => false + } + } .toSeq val missing = argSigs .collect { case x: ArgSig.Simple[_, _] if !x.reader.isLeftover => x } .filter { x => - !x.reader.allowEmpty && - x.default.isEmpty && - !current.contains(x) + x.reader match { + case r: TokensReader.Simple[_] => + !r.allowEmpty && + x.default.isEmpty && + !current.contains(x) + case r: TokensReader.Leftover[_, _] => false + } } val unknown = if (allowLeftover) Nil else remaining diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 5292498..3f1dba9 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -1,47 +1,82 @@ package mainargs import scala.collection.compat._ import scala.collection.mutable -class TokensReader[T]( - val shortName: String, - val read: Seq[String] => Either[String, T], - val alwaysRepeatable: Boolean = false, - val allowEmpty: Boolean = false, - val isLeftover: Boolean = false -) + +sealed trait TokensReader[T]{ + def read(strs: Seq[String]): Either[String, T] + def isLeftover: Boolean + def shortName: String +} + object TokensReader { + trait Simple[T] extends TokensReader[T] { + def shortName: String + def alwaysRepeatable: Boolean = false + def allowEmpty: Boolean = false + def isLeftover = false + } + + trait Leftover[T, V] extends TokensReader[T]{ + def isLeftover = true + def wrapped: TokensReader[V] + def shortName = wrapped.shortName + } + def tryEither[T](f: => T) = try Right(f) catch { case e: Throwable => Left(e.toString) } - implicit object StringRead extends TokensReader[String]("str", strs => Right(strs.last)) - implicit object BooleanRead - extends TokensReader[Boolean]("bool", strs => tryEither(strs.last.toBoolean)) - implicit object ByteRead extends TokensReader[Byte]("byte", strs => tryEither(strs.last.toByte)) - implicit object ShortRead - extends TokensReader[Short]("short", strs => tryEither(strs.last.toShort)) - implicit object IntRead extends TokensReader[Int]("int", strs => tryEither(strs.last.toInt)) - implicit object LongRead extends TokensReader[Long]("long", strs => tryEither(strs.last.toLong)) - implicit object FloatRead - extends TokensReader[Float]("float", strs => tryEither(strs.last.toFloat)) - implicit object DoubleRead - extends TokensReader[Double]("double", strs => tryEither(strs.last.toDouble)) - class LeftoverRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader[Leftover[T]]( - "leftover", - strs => { + implicit object StringRead extends Simple[String]{ + def shortName = "str" + def read(strs: Seq[String]) = Right(strs.last) + } + implicit object BooleanRead extends Simple[Boolean] { + def shortName = "bool" + def read(strs: Seq[String]) = tryEither(strs.last.toBoolean) + } + implicit object ByteRead extends Simple[Byte]{ + def shortName = "byte" + def read(strs: Seq[String]) = tryEither(strs.last.toByte) + } + implicit object ShortRead extends Simple[Short]{ + def shortName = "short" + def read(strs: Seq[String]) = tryEither(strs.last.toShort) + } + implicit object IntRead extends Simple[Int]{ + def shortName = "int" + def read(strs: Seq[String]) = tryEither(strs.last.toInt) + } + implicit object LongRead extends Simple[Long]{ + def shortName = "long" + def read(strs: Seq[String]) = tryEither(strs.last.toLong) + } + implicit object FloatRead extends Simple[Float]{ + def shortName = "float" + def read(strs: Seq[String]) = tryEither(strs.last.toFloat) + } + implicit object DoubleRead extends Simple[Double]{ + def shortName = "double" + def read(strs: Seq[String]) = tryEither(strs.last.toDouble) + } + + implicit def LeftoverRead[T: TokensReader.Simple]: TokensReader[mainargs.Leftover[T]] = + new LeftoverRead[T]()(implicitly[TokensReader.Simple[T]]) + + class LeftoverRead[T](implicit val wrapped: TokensReader.Simple[T]) extends Leftover[mainargs.Leftover[T], T]{ + def read(strs: Seq[String]) = { val (failures, successes) = strs .map(s => implicitly[TokensReader[T]].read(Seq(s))) .partitionMap(identity) if (failures.nonEmpty) Left(failures.head) else Right(Leftover(successes: _*)) - }, - isLeftover = true - ) - implicit def LeftoverRead[T: TokensReader]: TokensReader[Leftover[T]] = new LeftoverRead[T] + } + } - implicit def OptionRead[T: TokensReader]: TokensReader[Option[T]] = new TokensReader[Option[T]]( - implicitly[TokensReader[T]].shortName, - strs => { + implicit def OptionRead[T: TokensReader.Simple]: TokensReader[Option[T]] = new OptionRead[T] + class OptionRead[T: TokensReader.Simple] extends Simple[Option[T]]{ + def shortName = implicitly[TokensReader.Simple[T]].shortName + def read(strs: Seq[String]) = { strs.lastOption match { case None => Right(None) case Some(s) => implicitly[TokensReader[T]].read(Seq(s)) match { @@ -49,14 +84,18 @@ object TokensReader { case Right(s) => Right(Some(s)) } } - }, - allowEmpty = true - ) - implicit def SeqRead[C[_] <: Iterable[_], T: TokensReader](implicit - factory: Factory[T, C[T]] - ): TokensReader[C[T]] = new TokensReader[C[T]]( - implicitly[TokensReader[T]].shortName, - strs => { + } + override def allowEmpty = true + } + + implicit def SeqRead[C[_] <: Iterable[_], T: TokensReader.Simple] + (implicit factory: Factory[T, C[T]]): TokensReader[C[T]] = + new SeqRead[C, T] + + class SeqRead[C[_] <: Iterable[_], T: TokensReader.Simple](implicit factory: Factory[T, C[T]]) + extends Simple[C[T]]{ + def shortName = implicitly[TokensReader.Simple[T]].shortName + def read(strs: Seq[String]) = { strs .foldLeft(Right(factory.newBuilder): Either[String, mutable.Builder[T, C[T]]]) { case (Left(s), _) => Left(s) @@ -69,31 +108,33 @@ object TokensReader { } } .map(_.result()) - }, - alwaysRepeatable = true, - allowEmpty = true - ) + } + override def alwaysRepeatable = true + override def allowEmpty = true + } + implicit def MapRead[K: TokensReader, V: TokensReader]: TokensReader[Map[K, V]] = - new TokensReader[Map[K, V]]( - "k=v", - strs => { - strs.foldLeft[Either[String, Map[K, V]]](Right(Map())) { - case (Left(s), _) => Left(s) - case (Right(prev), token) => - token.split("=", 2) match { - case Array(k, v) => - for { - tuple <- Right((k, v)): Either[String, (String, String)] - (k, v) = tuple - key <- implicitly[TokensReader[K]].read(Seq(k)) - value <- implicitly[TokensReader[V]].read(Seq(v)) - } yield prev + (key -> value) + new MapRead[K, V] + class MapRead[K: TokensReader, V: TokensReader] extends Simple[Map[K, V]]{ + def shortName = "k=v" + def read(strs: Seq[String]) = { + strs.foldLeft[Either[String, Map[K, V]]](Right(Map())) { + case (Left(s), _) => Left(s) + case (Right(prev), token) => + token.split("=", 2) match { + case Array(k, v) => + for { + tuple <- Right((k, v)): Either[String, (String, String)] + (k, v) = tuple + key <- implicitly[TokensReader[K]].read(Seq(k)) + value <- implicitly[TokensReader[V]].read(Seq(v)) + } yield prev + (key -> value) - case _ => Left("parameter must be in k=v format") - } - } - }, - alwaysRepeatable = true, - allowEmpty = true - ) + case _ => Left("parameter must be in k=v format") + } + } + } + override def alwaysRepeatable = true + override def allowEmpty = true + } } diff --git a/mainargs/test/src-2/OldVarargsTests.scala b/mainargs/test/src-2/VarargsOldTests.scala similarity index 86% rename from mainargs/test/src-2/OldVarargsTests.scala rename to mainargs/test/src-2/VarargsOldTests.scala index 6e7bdef..88b217a 100644 --- a/mainargs/test/src-2/OldVarargsTests.scala +++ b/mainargs/test/src-2/VarargsOldTests.scala @@ -1,7 +1,7 @@ package mainargs import utest._ -object OldVarargsTests extends VarargsTests { +object VarargsOldTests extends VarargsBaseTests { object Base { @main diff --git a/mainargs/test/src-jvm-2/AmmoniteTests.scala b/mainargs/test/src-jvm-2/AmmoniteTests.scala index f0fab84..9c05ef1 100644 --- a/mainargs/test/src-jvm-2/AmmoniteTests.scala +++ b/mainargs/test/src-jvm-2/AmmoniteTests.scala @@ -21,8 +21,11 @@ case class AmmoniteConfig( ) object AmmoniteConfig { - implicit object PathRead - extends TokensReader[os.Path]("path", strs => Right(os.Path(strs.head, os.pwd))) + implicit object PathRead extends TokensReader.Simple[os.Path]{ + def shortName = "path" + def read(strs: Seq[String]) = Right(os.Path(strs.head, os.pwd)) + } + @main case class Core( @arg( diff --git a/mainargs/test/src/VarargsTests.scala b/mainargs/test/src/VarargsBaseTests.scala similarity index 98% rename from mainargs/test/src/VarargsTests.scala rename to mainargs/test/src/VarargsBaseTests.scala index d95c640..aeb77a2 100644 --- a/mainargs/test/src/VarargsTests.scala +++ b/mainargs/test/src/VarargsBaseTests.scala @@ -1,7 +1,7 @@ package mainargs import utest._ -trait VarargsTests extends TestSuite { +trait VarargsBaseTests extends TestSuite { def check: Checker[_] def isNewVarargsTests: Boolean val tests = Tests { diff --git a/mainargs/test/src/VarargsCustomTests.scala b/mainargs/test/src/VarargsCustomTests.scala new file mode 100644 index 0000000..77a4449 --- /dev/null +++ b/mainargs/test/src/VarargsCustomTests.scala @@ -0,0 +1,41 @@ +package mainargs +import utest._ + + +object VarargsCustomTests extends VarargsBaseTests { + // Test that we are able to replace the `Leftover` type entirely with our + // own implementation + class Wrapper[T](val unwrap: Seq[T]) + class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader.Leftover[Wrapper[T], T]{ + def read(strs: Seq[String]) = { + val (failures, successes) = strs + .map(s => implicitly[TokensReader[T]].read(Seq(s))) + .partitionMap(identity) + + if (failures.nonEmpty) Left(failures.head) + else Right(new Wrapper(successes)) + } + } + + implicit def WrapperRead[T: TokensReader]: TokensReader[Wrapper[T]] = new WrapperRead[T] + + object Base { + @main + def pureVariadic(nums: Wrapper[Int]) = nums.unwrap.sum + + @main + def mixedVariadic(@arg(short = 'f') first: Int, args: Wrapper[String]) = { + first + args.unwrap.mkString + } + @main + def mixedVariadicWithDefault( + @arg(short = 'f') first: Int = 1337, + args: Wrapper[String] + ) = { + first + args.unwrap.mkString + } + } + + val check = new Checker(ParserForMethods(Base), allowPositional = true) + val isNewVarargsTests = true +} diff --git a/mainargs/test/src/NewVarargsTests.scala b/mainargs/test/src/VarargsNewTests.scala similarity index 91% rename from mainargs/test/src/NewVarargsTests.scala rename to mainargs/test/src/VarargsNewTests.scala index c55065b..a0e9081 100644 --- a/mainargs/test/src/NewVarargsTests.scala +++ b/mainargs/test/src/VarargsNewTests.scala @@ -1,6 +1,6 @@ package mainargs import utest._ -object NewVarargsTests extends VarargsTests { +object VarargsNewTests extends VarargsBaseTests { object Base { @main def pureVariadic(nums: Leftover[Int]) = nums.value.sum diff --git a/mainargs/test/src/WrappedVarargsTests.scala b/mainargs/test/src/VarargsWrappedTests.scala similarity index 78% rename from mainargs/test/src/WrappedVarargsTests.scala rename to mainargs/test/src/VarargsWrappedTests.scala index 0076aed..201aa7a 100644 --- a/mainargs/test/src/WrappedVarargsTests.scala +++ b/mainargs/test/src/VarargsWrappedTests.scala @@ -2,17 +2,13 @@ package mainargs import utest._ -object WrappedVarargsTests extends VarargsTests { +object VarargsWrappedTests extends VarargsBaseTests { // Test that we are able to wrap the `Leftover` type we use for Varargs in // our own custom types, and have things work class Wrapper[T](val unwrap: T) - class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader[Wrapper[T]]( - wrapped.shortName, - args => wrapped.read(args).map(new Wrapper(_)), - wrapped.alwaysRepeatable, - wrapped.allowEmpty, - wrapped.isLeftover, - ) + class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader.Leftover[Wrapper[T], T]{ + def read(strs: Seq[String]) = wrapped.read(strs).map(new Wrapper(_)) + } implicit def WrapperRead[T: TokensReader]: TokensReader[Wrapper[T]] = new WrapperRead[T] From 7f9fc3d63f3416054c35c7ad8fc74d30a28c5885 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 12:41:23 +0800 Subject: [PATCH 05/17] . --- mainargs/src/TokenGrouping.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index ab8f4af..923978e 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -77,7 +77,6 @@ object TokenGrouping { .toSeq val missing = argSigs - .collect { case x: ArgSig.Simple[_, _] if !x.reader.isLeftover => x } .filter { x => x.reader match { case r: TokensReader.Simple[_] => From 097dda49387a9d2661b1f9ad644143b744a485bc Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 12:54:01 +0800 Subject: [PATCH 06/17] . --- mainargs/src/TokenGrouping.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index 923978e..64a0b4e 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -77,6 +77,7 @@ object TokenGrouping { .toSeq val missing = argSigs + .collect { case x: ArgSig.Simple[_, _] => x } .filter { x => x.reader match { case r: TokensReader.Simple[_] => From 00470d9da563fc8ebdda59fa08508feb1681a13a Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 12:59:21 +0800 Subject: [PATCH 07/17] eliminate ArgSig.Flag --- mainargs/src/Invoker.scala | 4 +--- mainargs/src/Model.scala | 7 ------- mainargs/src/Renderer.scala | 10 +++++----- mainargs/src/TokenGrouping.scala | 6 +++--- mainargs/src/TokensReader.scala | 11 +++++++++++ 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/mainargs/src/Invoker.scala b/mainargs/src/Invoker.scala index 22e16ec..9be6526 100644 --- a/mainargs/src/Invoker.scala +++ b/mainargs/src/Invoker.scala @@ -30,11 +30,9 @@ object Invoker { val readArgValues: Seq[Either[Result[Any], ParamResult[_]]] = for (a <- mainData.argSigs0) yield { a match { - case a: ArgSig.Flag[B] => - Right(ParamResult.Success(Flag(kvs.contains(a)).asInstanceOf[T])) - case a: ArgSig.Simple[T, B] => a.reader match{ + case r: TokensReader.Flag => Right(ParamResult.Success(Flag(kvs.contains(a)).asInstanceOf[T])) case r: TokensReader.Simple[T] => Right(makeReadCall(kvs, base, a, r)) case r: TokensReader.Leftover[T, _] => Right(makeReadVarargsCall(a, extras, r)) } diff --git a/mainargs/src/Model.scala b/mainargs/src/Model.scala index d96f81c..3e94b05 100644 --- a/mainargs/src/Model.scala +++ b/mainargs/src/Model.scala @@ -15,7 +15,6 @@ object ArgSig { } val docOpt = scala.Option(arg.doc) argParser match { - case ArgReader.Flag() => ArgSig.Flag[B](nameOpt, shortOpt, docOpt) case ArgReader.Class(parser) => Class(parser.mains) case ArgReader.Simple(reader) => Simple[T, B](nameOpt, shortOpt, docOpt, defaultOpt, reader, arg.positional) @@ -46,9 +45,6 @@ object ArgSig { positional: Boolean ) extends ArgSig.Named[T, B] - case class Flag[B](name: Option[String], shortName: Option[Char], doc: Option[String]) - extends ArgSig.Named[mainargs.Flag, B] - def flatten[T, B](x: ArgSig[T, B]): Seq[Terminal[T, B]] = x match { case x: Terminal[T, B] => Seq(x) case x: Class[T, B] => @@ -65,9 +61,6 @@ object ArgReader { implicit def createClass[T: SubParser]: Class[T] = Class(implicitly[SubParser[T]]) case class Class[T](x: SubParser[T]) extends ArgReader[T] - - implicit def createFlag: Flag = Flag() - case class Flag() extends ArgReader[mainargs.Flag] } trait SubParser[T] { diff --git a/mainargs/src/Renderer.scala b/mainargs/src/Renderer.scala index 83134fd..d947bdd 100644 --- a/mainargs/src/Renderer.scala +++ b/mainargs/src/Renderer.scala @@ -15,13 +15,13 @@ object Renderer { def normalizeNewlines(s: String) = s.replace("\r", "").replace("\n", newLine) def renderArgShort(arg: ArgSig.Terminal[_, _]) = arg match { - case arg: ArgSig.Flag[_] => - val shortPrefix = arg.shortName.map(c => s"-$c") - val nameSuffix = arg.name.map(s => s"--$s") - (shortPrefix ++ nameSuffix).mkString(" ") - case arg: ArgSig.Simple[_, _] => arg.reader match{ + case r: TokensReader.Flag => + val shortPrefix = arg.shortName.map(c => s"-$c") + val nameSuffix = arg.name.map(s => s"--$s") + (shortPrefix ++ nameSuffix).mkString(" ") + case r: TokensReader.Simple[_] => val shortPrefix = arg.shortName.map(c => s"-$c") val typeSuffix = s"<${r.shortName}>" diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index 64a0b4e..2b0c221 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -37,7 +37,7 @@ object TokenGrouping { case head :: rest => if (head.startsWith("-") && head.exists(_ != '-')) { keywordArgMap.get(head) match { - case Some(cliArg: ArgSig.Flag[_]) => + case Some(cliArg: ArgSig.Simple[_, _]) if cliArg.reader.isFlag => rec(rest, Util.appendMap(current, cliArg, "")) case Some(cliArg: ArgSig.Simple[_, _]) if !cliArg.reader.isLeftover => rest match { @@ -66,9 +66,9 @@ object TokenGrouping { val duplicates = current .filter { - case (a: ArgSig.Flag[_], vs) => vs.size > 1 && !allowRepeats case (a: ArgSig.Simple[_, _], vs) => a.reader match{ + case r: TokensReader.Flag => vs.size > 1 && !allowRepeats case r: TokensReader.Simple[_] => vs.size > 1 && !r.alwaysRepeatable && !allowRepeats case r: TokensReader.Leftover[_, _] => false } @@ -84,7 +84,7 @@ object TokenGrouping { !r.allowEmpty && x.default.isEmpty && !current.contains(x) - case r: TokensReader.Leftover[_, _] => false + case _ => false } } diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 3f1dba9..9e36b3a 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -5,6 +5,7 @@ import scala.collection.mutable sealed trait TokensReader[T]{ def read(strs: Seq[String]): Either[String, T] def isLeftover: Boolean + def isFlag: Boolean def shortName: String } @@ -14,10 +15,19 @@ object TokensReader { def alwaysRepeatable: Boolean = false def allowEmpty: Boolean = false def isLeftover = false + def isFlag = false + } + + trait Flag extends TokensReader[mainargs.Flag] { + def shortName = "" + def read(strs: Seq[String]) = ??? + def isLeftover = false + def isFlag = true } trait Leftover[T, V] extends TokensReader[T]{ def isLeftover = true + def isFlag = false def wrapped: TokensReader[V] def shortName = wrapped.shortName } @@ -26,6 +36,7 @@ object TokensReader { try Right(f) catch { case e: Throwable => Left(e.toString) } + implicit object FlagRead extends Flag implicit object StringRead extends Simple[String]{ def shortName = "str" def read(strs: Seq[String]) = Right(strs.last) From 1928dd5110239947c94224fab8695f35074c9b88 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 13:55:04 +0800 Subject: [PATCH 08/17] fix --- mainargs/test/src/VarargsCustomTests.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mainargs/test/src/VarargsCustomTests.scala b/mainargs/test/src/VarargsCustomTests.scala index 77a4449..f29a553 100644 --- a/mainargs/test/src/VarargsCustomTests.scala +++ b/mainargs/test/src/VarargsCustomTests.scala @@ -8,9 +8,9 @@ object VarargsCustomTests extends VarargsBaseTests { class Wrapper[T](val unwrap: Seq[T]) class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader.Leftover[Wrapper[T], T]{ def read(strs: Seq[String]) = { - val (failures, successes) = strs - .map(s => implicitly[TokensReader[T]].read(Seq(s))) - .partitionMap(identity) + val results = strs.map(s => implicitly[TokensReader[T]].read(Seq(s))) + val failures = results.collect{case Left(x) => x} + val successes = results.collect{case Right(x) => x} if (failures.nonEmpty) Left(failures.head) else Right(new Wrapper(successes)) From 833f1cccd2e1931836a36bd5d8fdee071c019698 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 14:18:10 +0800 Subject: [PATCH 09/17] comment --- mainargs/src-2/Macros.scala | 10 +- mainargs/src-3/Macros.scala | 10 +- mainargs/src/Invoker.scala | 89 +++++++------- mainargs/src/Model.scala | 108 ----------------- mainargs/src/Parser.scala | 11 +- mainargs/src/Renderer.scala | 57 +++++---- mainargs/src/Result.scala | 12 +- mainargs/src/TokenGrouping.scala | 30 ++--- mainargs/src/TokensReader.scala | 128 +++++++++++++++++--- mainargs/test/src-jvm-2/AmmoniteTests.scala | 2 +- mainargs/test/src/ClassTests.scala | 14 +-- mainargs/test/src/CoreTests.scala | 24 ++-- mainargs/test/src/PositionalTests.scala | 8 +- mainargs/test/src/VarargsBaseTests.scala | 10 +- mainargs/test/src/VarargsCustomTests.scala | 8 +- mainargs/test/src/VarargsWrappedTests.scala | 4 +- 16 files changed, 251 insertions(+), 274 deletions(-) delete mode 100644 mainargs/src/Model.scala diff --git a/mainargs/src-2/Macros.scala b/mainargs/src-2/Macros.scala index 2e31b51..f5b3574 100755 --- a/mainargs/src-2/Macros.scala +++ b/mainargs/src-2/Macros.scala @@ -37,10 +37,8 @@ class Macros(val c: Context) { q""" new _root_.mainargs.ParserForClass( - _root_.mainargs.ClassMains[${weakTypeOf[T]}]( - $route.asInstanceOf[_root_.mainargs.MainData[${weakTypeOf[T]}, Any]], - () => $companionObj - ) + $route.asInstanceOf[_root_.mainargs.MainData[${weakTypeOf[T]}, Any]], + () => $companionObj ) """ } @@ -119,13 +117,13 @@ class Macros(val c: Context) { ${arg.name.decoded}, $instantiateArg, $defaultOpt - ).widen[_root_.scala.Any] + ) """ else q""" _root_.mainargs.ArgSig.create[$varargUnwrappedType, $curCls]( ${arg.name.decoded}, $instantiateArg, $defaultOpt - ).widen[_root_.scala.Any] + ) """ c.internal.setPos(argSig, methodPos) diff --git a/mainargs/src-3/Macros.scala b/mainargs/src-3/Macros.scala index 4145ca5..6cd75ab 100644 --- a/mainargs/src-3/Macros.scala +++ b/mainargs/src-3/Macros.scala @@ -41,11 +41,7 @@ object Macros { companionModuleType match case '[bCompanion] => val mainData = createMainData[B, Any](annotatedMethod, mainAnnotationInstance) - '{ - new ParserForClass[B]( - ClassMains[B](${ mainData }, () => ${ Ident(companionModule).asExpr }) - ) - } + '{ new ParserForClass[B](${ mainData }, () => ${ Ident(companionModule).asExpr }) } } def createMainData[T: Type, B: Type](using Quotes)(method: quotes.reflect.Symbol, annotation: quotes.reflect.Term): Expr[MainData[T, B]] = { @@ -63,13 +59,13 @@ object Macros { case Some('{ $v: `t`}) => '{ Some(((_: B) => $v)) } case None => '{ None } } - val argReader = Expr.summon[mainargs.ArgReader[t]].getOrElse { + val tokensReader = Expr.summon[mainargs.TokensReader[t]].getOrElse { report.throwError( s"No mainargs.ArgReader found for parameter ${param.name}", param.pos.get ) } - '{ (ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ argReader })).asInstanceOf[ArgSig[Any, B]] } + '{ (ArgSig.create[t, B](${ Expr(param.name) }, ${ arg }, ${ defaultParam })(using ${ tokensReader })) } }) val invokeRaw: Expr[(B, Seq[Any]) => T] = { diff --git a/mainargs/src/Invoker.scala b/mainargs/src/Invoker.scala index 9be6526..6544039 100644 --- a/mainargs/src/Invoker.scala +++ b/mainargs/src/Invoker.scala @@ -2,7 +2,7 @@ package mainargs object Invoker { def construct[T]( - cep: ClassMains[T], + cep: TokensReader.Class[T], args: Seq[String], allowPositional: Boolean, allowRepeats: Boolean @@ -10,42 +10,37 @@ object Invoker { TokenGrouping .groupArgs( args, - cep.main.argSigs, + cep.main.flattenedArgSigs, allowPositional, allowRepeats, - cep.main.argSigs0.exists { - case x: ArgSig.Simple[_, _] => x.reader.isLeftover - case _ => false - } + cep.main.argSigs0.exists(_.reader.isLeftover) ) - .flatMap(invoke(cep.companion(), cep.main, _)) + .flatMap((group: TokenGrouping[Any]) => invoke(cep.companion(), cep.main, group)) } def invoke0[T, B]( base: B, mainData: MainData[T, B], - kvs: Map[ArgSig.Named[_, B], Seq[String]], + kvs: Map[ArgSig, Seq[String]], extras: Seq[String] ): Result[T] = { val readArgValues: Seq[Either[Result[Any], ParamResult[_]]] = for (a <- mainData.argSigs0) yield { - a match { - case a: ArgSig.Simple[T, B] => - a.reader match{ - case r: TokensReader.Flag => Right(ParamResult.Success(Flag(kvs.contains(a)).asInstanceOf[T])) - case r: TokensReader.Simple[T] => Right(makeReadCall(kvs, base, a, r)) - case r: TokensReader.Leftover[T, _] => Right(makeReadVarargsCall(a, extras, r)) - } - - case a: ArgSig.Class[T, B] => + a.reader match { + case r: TokensReader.Flag => + Right(ParamResult.Success(Flag(kvs.contains(a)).asInstanceOf[T])) + case r: TokensReader.Simple[T] => Right(makeReadCall(kvs, base, a, r)) + case r: TokensReader.Leftover[T, _] => Right(makeReadVarargsCall(a, extras, r)) + case r: TokensReader.Class[T] => Left( invoke0[T, B]( - a.reader.companion().asInstanceOf[B], - a.reader.main.asInstanceOf[MainData[T, B]], + r.companion().asInstanceOf[B], + r.main.asInstanceOf[MainData[T, B]], kvs, extras ) ) + } } @@ -85,21 +80,25 @@ object Invoker { allowPositional: Boolean, allowRepeats: Boolean ): Either[Result.Failure.Early, (MainData[Any, B], Result[Any])] = { - def groupArgs(main: MainData[Any, B], argsList: Seq[String]) = Right( - main, - TokenGrouping - .groupArgs( - argsList, - main.argSigs, - allowPositional, - allowRepeats, - main.argSigs0.exists { - case x: ArgSig.Simple[_, _] => x.reader.isLeftover - case _ => false - } - ) - .flatMap(Invoker.invoke(mains.base(), main, _)) - ) + def groupArgs(main: MainData[Any, B], argsList: Seq[String]) = { + def invokeLocal(group: TokenGrouping[Any]) = + invoke(mains.base(), main.asInstanceOf[MainData[Any, Any]], group) + Right( + main, + TokenGrouping + .groupArgs( + argsList, + main.flattenedArgSigs, + allowPositional, + allowRepeats, + main.argSigs0.exists { + case x: ArgSig => x.reader.isLeftover + case _ => false + } + ) + .flatMap(invokeLocal) + ) + } mains.value match { case Seq() => Left(Result.Failure.Early.NoMainMethodsDetected()) case Seq(main) => groupArgs(main, args) @@ -124,11 +123,11 @@ object Invoker { try Right(t) catch { case e: Throwable => Left(error(e)) } } - def makeReadCall[T, B]( - dict: Map[ArgSig.Named[_, B], Seq[String]], - base: B, - arg: ArgSig.Simple[T, B], - reader: TokensReader.Simple[T] + def makeReadCall[T]( + dict: Map[ArgSig, Seq[String]], + base: Any, + arg: ArgSig, + reader: TokensReader.Simple[_] ): ParamResult[T] = { def prioritizedDefault = tryEither( arg.default.map(_(base)), @@ -154,13 +153,13 @@ object Invoker { case Right(Right(v)) => ParamResult.Success(Some(v)) } } - optResult.map(_.get) + optResult.map(_.get.asInstanceOf[T]) } - def makeReadVarargsCall[T, B]( - arg: ArgSig.Simple[T, B], + def makeReadVarargsCall[T]( + arg: ArgSig, values: Seq[String], - reader: TokensReader.Leftover[T, _] + reader: TokensReader.Leftover[_, _] ): ParamResult[T] = { val eithers = tryEither( @@ -172,9 +171,9 @@ object Invoker { case Right(Right(v)) => Right(v) } - eithers match{ + eithers match { case Left(s) => ParamResult.Failure(Seq(s)) - case Right(v) => ParamResult.Success(v) + case Right(v) => ParamResult.Success(v.asInstanceOf[T]) } } } diff --git a/mainargs/src/Model.scala b/mainargs/src/Model.scala deleted file mode 100644 index 3e94b05..0000000 --- a/mainargs/src/Model.scala +++ /dev/null @@ -1,108 +0,0 @@ -package mainargs - -sealed trait ArgSig[T, B] { - def widen[V >: T] = this.asInstanceOf[ArgSig[V, B]] -} -object ArgSig { - def create[T, B](name0: String, arg: mainargs.arg, defaultOpt: Option[B => T])(implicit - argParser: ArgReader[T] - ): ArgSig[T, B] = { - val nameOpt = scala.Option(arg.name).orElse(if (name0.length == 1 || arg.noDefaultName) None - else Some(name0)) - val shortOpt = arg.short match { - case '\u0000' => if (name0.length != 1 || arg.noDefaultName) None else Some(name0(0)); - case c => Some(c) - } - val docOpt = scala.Option(arg.doc) - argParser match { - case ArgReader.Class(parser) => Class(parser.mains) - case ArgReader.Simple(reader) => - Simple[T, B](nameOpt, shortOpt, docOpt, defaultOpt, reader, arg.positional) - } - } - - sealed trait Terminal[T, B] extends ArgSig[T, B] { - def name: Option[String] - def doc: Option[String] - } - - sealed trait Named[T, B] extends Terminal[T, B] { - def shortName: Option[Char] - } - - /** - * Models what is known by the router about a single argument: that it has - * a [[name]], a human-readable [[typeString]] describing what the type is - * (just for logging and reading, not a replacement for a `TypeTag`) and - * possible a function that can compute its default value - */ - case class Simple[T, B]( - name: Option[String], - shortName: Option[Char], - doc: Option[String], - default: Option[B => T], - reader: TokensReader[T], - positional: Boolean - ) extends ArgSig.Named[T, B] - - def flatten[T, B](x: ArgSig[T, B]): Seq[Terminal[T, B]] = x match { - case x: Terminal[T, B] => Seq(x) - case x: Class[T, B] => - x.reader.main.argSigs.flatMap(x => flatten(x.asInstanceOf[Terminal[T, B]])) - } - - case class Class[T, B](reader: ClassMains[T]) extends ArgSig[T, B] -} - -sealed trait ArgReader[T] -object ArgReader { - implicit def createSimple[T: TokensReader]: Simple[T] = Simple(implicitly[TokensReader[T]]) - case class Simple[T](x: TokensReader[T]) extends ArgReader[T] - - implicit def createClass[T: SubParser]: Class[T] = Class(implicitly[SubParser[T]]) - case class Class[T](x: SubParser[T]) extends ArgReader[T] -} - -trait SubParser[T] { - def mains: ClassMains[T] -} - -case class MethodMains[B](value: Seq[MainData[Any, B]], base: () => B) - -case class ClassMains[T](main: MainData[T, Any], companion: () => Any) - -/** - * What is known about a single endpoint for our routes. It has a [[name]], - * [[argSigs]] for each argument, and a macro-generated [[invoke0]] - * that performs all the necessary argument parsing and de-serialization. - * - * Realistically, you will probably spend most of your time calling [[Invoker.invoke]] - * instead, which provides a nicer API to call it that mimmicks the API of - * calling a Scala method. - */ -case class MainData[T, B]( - name: String, - argSigs0: Seq[ArgSig[_, B]], - doc: Option[String], - invokeRaw: (B, Seq[Any]) => T -) { - - val argSigs: Seq[ArgSig.Terminal[_, B]] = - argSigs0.iterator.flatMap[ArgSig.Terminal[_, B]](ArgSig.flatten(_)).toVector -} - -object MainData { - def create[T, B]( - methodName: String, - main: mainargs.main, - argSigs: Seq[ArgSig[Any, B]], - invokeRaw: (B, Seq[Any]) => T - ) = { - MainData( - Option(main.name).getOrElse(methodName), - argSigs, - Option(main.doc), - invokeRaw - ) - } -} diff --git a/mainargs/src/Parser.scala b/mainargs/src/Parser.scala index 072c623..3894e32 100644 --- a/mainargs/src/Parser.scala +++ b/mainargs/src/Parser.scala @@ -175,7 +175,8 @@ class ParserForMethods[B](val mains: MethodMains[B]) { } object ParserForClass extends ParserForClassCompanionVersionSpecific -class ParserForClass[T](val mains: ClassMains[T]) extends SubParser[T] { +class ParserForClass[T](val main: MainData[T, Any], val companion: () => Any) + extends TokensReader.Class[T] { def helpText( totalWidth: Int = 100, docsOnNewLine: Boolean = false, @@ -184,10 +185,10 @@ class ParserForClass[T](val mains: ClassMains[T]) extends SubParser[T] { sorted: Boolean = true ): String = { Renderer.formatMainMethodSignature( - mains.main, + main, 0, totalWidth, - Renderer.getLeftColWidth(mains.main.argSigs), + Renderer.getLeftColWidth(main.flattenedArgSigs), docsOnNewLine, Option(customName), Option(customDoc), @@ -281,7 +282,7 @@ class ParserForClass[T](val mains: ClassMains[T]) extends SubParser[T] { case f: Result.Failure => Left( Renderer.renderResult( - mains.main, + main, f, totalWidth, printHelpOnExit, @@ -323,6 +324,6 @@ class ParserForClass[T](val mains: ClassMains[T]) extends SubParser[T] { allowPositional: Boolean = false, allowRepeats: Boolean = false ): Result[T] = { - Invoker.construct[T](mains, args, allowPositional, allowRepeats) + Invoker.construct[T](this, args, allowPositional, allowRepeats) } } diff --git a/mainargs/src/Renderer.scala b/mainargs/src/Renderer.scala index d947bdd..caf6165 100644 --- a/mainargs/src/Renderer.scala +++ b/mainargs/src/Renderer.scala @@ -5,7 +5,7 @@ import scala.math object Renderer { - def getLeftColWidth(items: Seq[ArgSig.Terminal[_, _]]) = { + def getLeftColWidth(items: Seq[ArgSig]) = { if (items.isEmpty) 0 else items.map(renderArgShort(_).length).max } @@ -14,39 +14,36 @@ object Renderer { def normalizeNewlines(s: String) = s.replace("\r", "").replace("\n", newLine) - def renderArgShort(arg: ArgSig.Terminal[_, _]) = arg match { - case arg: ArgSig.Simple[_, _] => - arg.reader match{ - case r: TokensReader.Flag => - val shortPrefix = arg.shortName.map(c => s"-$c") - val nameSuffix = arg.name.map(s => s"--$s") - (shortPrefix ++ nameSuffix).mkString(" ") - - case r: TokensReader.Simple[_] => - val shortPrefix = arg.shortName.map(c => s"-$c") - val typeSuffix = s"<${r.shortName}>" - val nameSuffix = if (arg.positional) arg.name else arg.name.map(s => s"--$s") - (shortPrefix ++ nameSuffix ++ Seq(typeSuffix)).mkString(" ") - - case r: TokensReader.Leftover[_, _] => - s"${arg.name.get} <${r.wrapped.shortName}>..." - } + def renderArgShort(arg: ArgSig) = arg.reader match { + case r: TokensReader.Flag => + val shortPrefix = arg.shortName.map(c => s"-$c") + val nameSuffix = arg.name.map(s => s"--$s") + (shortPrefix ++ nameSuffix).mkString(" ") + + case r: TokensReader.Simple[_] => + val shortPrefix = arg.shortName.map(c => s"-$c") + val typeSuffix = s"<${r.shortName}>" + val nameSuffix = if (arg.positional) arg.name else arg.name.map(s => s"--$s") + (shortPrefix ++ nameSuffix ++ Seq(typeSuffix)).mkString(" ") + + case r: TokensReader.Leftover[_, _] => + s"${arg.name.get} <${r.wrapped.shortName}>..." } /** * Returns a `Some[string]` with the sortable string or a `None` if it is an leftover. */ - private def sortableName(arg: ArgSig.Terminal[_, _]): Option[String] = arg match { - case arg: ArgSig.Simple[_, _] if arg.reader.isLeftover => None + private def sortableName(arg: ArgSig): Option[String] = arg match { + case arg: ArgSig if arg.reader.isLeftover => None - case a: ArgSig.Named[_, _] => + case a: ArgSig => a.shortName.map(_.toString).orElse(a.name).orElse(Some("")) - case a: ArgSig.Terminal[_, _] => + case a: ArgSig => a.name.orElse(Some("")) } - object ArgOrd extends math.Ordering[ArgSig.Terminal[_, _]] { - override def compare(x: ArgSig.Terminal[_, _], y: ArgSig.Terminal[_, _]): Int = + object ArgOrd extends math.Ordering[ArgSig] { + override def compare(x: ArgSig, y: ArgSig): Int = (sortableName(x), sortableName(y)) match { case (None, None) => 0 // don't sort leftovers case (None, Some(_)) => 1 // keep left overs at the end @@ -56,7 +53,7 @@ object Renderer { } def renderArg( - arg: ArgSig.Terminal[_, _], + arg: ArgSig, leftOffset: Int, wrappedWidth: Int ): (String, String) = { @@ -72,8 +69,8 @@ object Renderer { customDocs: Map[String, String], sorted: Boolean ): String = { - val flattenedAll: Seq[ArgSig.Terminal[_, _]] = - mainMethods.map(_.argSigs) + val flattenedAll: Seq[ArgSig] = + mainMethods.map(_.flattenedArgSigs) .flatten val leftColWidth = getLeftColWidth(flattenedAll) mainMethods match { @@ -141,8 +138,8 @@ object Renderer { val argLeftCol = if (docsOnNewLine) leftIndent + 8 else leftColWidth + leftIndent + 2 + 2 val sortedArgs = - if (sorted) main.argSigs.sorted(ArgOrd) - else main.argSigs + if (sorted) main.flattenedArgSigs.sorted(ArgOrd) + else main.flattenedArgSigs val args = sortedArgs.map(renderArg(_, argLeftCol, totalWidth)) @@ -239,7 +236,7 @@ object Renderer { def expectedMsg() = { if (printHelpOnError) { - val leftColWidth = getLeftColWidth(main.argSigs) + val leftColWidth = getLeftColWidth(main.flattenedArgSigs) "Expected Signature: " + Renderer.formatMainMethodSignature( main, diff --git a/mainargs/src/Result.scala b/mainargs/src/Result.scala index 9e8601e..60c5147 100644 --- a/mainargs/src/Result.scala +++ b/mainargs/src/Result.scala @@ -47,10 +47,10 @@ object Result { * did not line up with the arguments expected */ case class MismatchedArguments( - missing: Seq[ArgSig.Simple[_, _]] = Nil, + missing: Seq[ArgSig] = Nil, unknown: Seq[String] = Nil, - duplicate: Seq[(ArgSig.Named[_, _], Seq[String])] = Nil, - incomplete: Option[ArgSig.Simple[_, _]] = None + duplicate: Seq[(ArgSig, Seq[String])] = Nil, + incomplete: Option[ArgSig] = None ) extends Failure /** @@ -66,21 +66,21 @@ object Result { /** * Something went wrong trying to de-serialize the input parameter */ - case class Failed(arg: ArgSig.Terminal[_, _], tokens: Seq[String], errMsg: String) + case class Failed(arg: ArgSig, tokens: Seq[String], errMsg: String) extends ParamError /** * Something went wrong trying to de-serialize the input parameter; * the thrown exception is stored in [[ex]] */ - case class Exception(arg: ArgSig.Terminal[_, _], tokens: Seq[String], ex: Throwable) + case class Exception(arg: ArgSig, tokens: Seq[String], ex: Throwable) extends ParamError /** * Something went wrong trying to evaluate the default value * for this input parameter */ - case class DefaultFailed(arg: ArgSig.Simple[_, _], ex: Throwable) extends ParamError + case class DefaultFailed(arg: ArgSig, ex: Throwable) extends ParamError } } diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index 2b0c221..854939c 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -2,44 +2,44 @@ package mainargs import scala.annotation.tailrec -case class TokenGrouping[B](remaining: List[String], grouped: Map[ArgSig.Named[_, B], Seq[String]]) +case class TokenGrouping[B](remaining: List[String], grouped: Map[ArgSig, Seq[String]]) object TokenGrouping { def groupArgs[B]( flatArgs0: Seq[String], - argSigs0: Seq[ArgSig[_, B]], + argSigs0: Seq[ArgSig], allowPositional: Boolean, allowRepeats: Boolean, allowLeftover: Boolean ): Result[TokenGrouping[B]] = { - val argSigs: Seq[ArgSig.Named[_, B]] = argSigs0 - .map(ArgSig.flatten(_).collect { case x: ArgSig.Named[_, _] => x }) + val argSigs: Seq[ArgSig] = argSigs0 + .map(ArgSig.flatten(_).collect { case x: ArgSig => x }) .flatten val positionalArgSigs = argSigs .filter { - case x: ArgSig.Simple[_, _] if x.reader.isLeftover => false - case x: ArgSig.Simple[_, _] if x.positional => true + case x: ArgSig if x.reader.isLeftover => false + case x: ArgSig if x.positional => true case x => allowPositional } val flatArgs = flatArgs0.toList val keywordArgMap = argSigs - .filter { case x: ArgSig.Simple[_, _] if x.positional => false; case _ => true } + .filter { case x: ArgSig if x.positional => false; case _ => true } .flatMap { x => (x.name.map("--" + _) ++ x.shortName.map("-" + _)).map(_ -> x) } - .toMap[String, ArgSig.Named[_, B]] + .toMap[String, ArgSig] @tailrec def rec( remaining: List[String], - current: Map[ArgSig.Named[_, B], Vector[String]] + current: Map[ArgSig, Vector[String]] ): Result[TokenGrouping[B]] = { remaining match { case head :: rest => if (head.startsWith("-") && head.exists(_ != '-')) { keywordArgMap.get(head) match { - case Some(cliArg: ArgSig.Simple[_, _]) if cliArg.reader.isFlag => + case Some(cliArg: ArgSig) if cliArg.reader.isFlag => rec(rest, Util.appendMap(current, cliArg, "")) - case Some(cliArg: ArgSig.Simple[_, _]) if !cliArg.reader.isLeftover => + case Some(cliArg: ArgSig) if !cliArg.reader.isLeftover => rest match { case next :: rest2 => rec(rest2, Util.appendMap(current, cliArg, next)) case Nil => @@ -61,13 +61,13 @@ object TokenGrouping { def complete( remaining: List[String], - current: Map[ArgSig.Named[_, B], Vector[String]] + current: Map[ArgSig, Vector[String]] ): Result[TokenGrouping[B]] = { val duplicates = current .filter { - case (a: ArgSig.Simple[_, _], vs) => - a.reader match{ + case (a: ArgSig, vs) => + a.reader match { case r: TokensReader.Flag => vs.size > 1 && !allowRepeats case r: TokensReader.Simple[_] => vs.size > 1 && !r.alwaysRepeatable && !allowRepeats case r: TokensReader.Leftover[_, _] => false @@ -77,7 +77,7 @@ object TokenGrouping { .toSeq val missing = argSigs - .collect { case x: ArgSig.Simple[_, _] => x } + .collect { case x: ArgSig => x } .filter { x => x.reader match { case r: TokensReader.Simple[_] => diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 9e36b3a..6ba2ecc 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -2,7 +2,10 @@ package mainargs import scala.collection.compat._ import scala.collection.mutable -sealed trait TokensReader[T]{ +/** + * Represents the ability to parse CLI input arguments into a type [[T]] + */ +sealed trait TokensReader[T] { def read(strs: Seq[String]): Either[String, T] def isLeftover: Boolean def isFlag: Boolean @@ -10,7 +13,8 @@ sealed trait TokensReader[T]{ } object TokensReader { - trait Simple[T] extends TokensReader[T] { + sealed trait Terminal[T] extends TokensReader[T] + trait Simple[T] extends Terminal[T] { def shortName: String def alwaysRepeatable: Boolean = false def allowEmpty: Boolean = false @@ -18,26 +22,35 @@ object TokensReader { def isFlag = false } - trait Flag extends TokensReader[mainargs.Flag] { + trait Flag extends Terminal[mainargs.Flag] { def shortName = "" def read(strs: Seq[String]) = ??? def isLeftover = false def isFlag = true } - trait Leftover[T, V] extends TokensReader[T]{ + trait Leftover[T, V] extends Terminal[T] { def isLeftover = true def isFlag = false def wrapped: TokensReader[V] def shortName = wrapped.shortName } + trait Class[T] extends TokensReader[T] { + def isLeftover = false + def isFlag = false + def shortName = ??? + def read(strs: Seq[String]) = ??? + def companion: () => Any + def main: MainData[T, Any] + } + def tryEither[T](f: => T) = try Right(f) catch { case e: Throwable => Left(e.toString) } implicit object FlagRead extends Flag - implicit object StringRead extends Simple[String]{ + implicit object StringRead extends Simple[String] { def shortName = "str" def read(strs: Seq[String]) = Right(strs.last) } @@ -45,27 +58,27 @@ object TokensReader { def shortName = "bool" def read(strs: Seq[String]) = tryEither(strs.last.toBoolean) } - implicit object ByteRead extends Simple[Byte]{ + implicit object ByteRead extends Simple[Byte] { def shortName = "byte" def read(strs: Seq[String]) = tryEither(strs.last.toByte) } - implicit object ShortRead extends Simple[Short]{ + implicit object ShortRead extends Simple[Short] { def shortName = "short" def read(strs: Seq[String]) = tryEither(strs.last.toShort) } - implicit object IntRead extends Simple[Int]{ + implicit object IntRead extends Simple[Int] { def shortName = "int" def read(strs: Seq[String]) = tryEither(strs.last.toInt) } - implicit object LongRead extends Simple[Long]{ + implicit object LongRead extends Simple[Long] { def shortName = "long" def read(strs: Seq[String]) = tryEither(strs.last.toLong) } - implicit object FloatRead extends Simple[Float]{ + implicit object FloatRead extends Simple[Float] { def shortName = "float" def read(strs: Seq[String]) = tryEither(strs.last.toFloat) } - implicit object DoubleRead extends Simple[Double]{ + implicit object DoubleRead extends Simple[Double] { def shortName = "double" def read(strs: Seq[String]) = tryEither(strs.last.toDouble) } @@ -73,7 +86,8 @@ object TokensReader { implicit def LeftoverRead[T: TokensReader.Simple]: TokensReader[mainargs.Leftover[T]] = new LeftoverRead[T]()(implicitly[TokensReader.Simple[T]]) - class LeftoverRead[T](implicit val wrapped: TokensReader.Simple[T]) extends Leftover[mainargs.Leftover[T], T]{ + class LeftoverRead[T](implicit val wrapped: TokensReader.Simple[T]) + extends Leftover[mainargs.Leftover[T], T] { def read(strs: Seq[String]) = { val (failures, successes) = strs .map(s => implicitly[TokensReader[T]].read(Seq(s))) @@ -85,7 +99,7 @@ object TokensReader { } implicit def OptionRead[T: TokensReader.Simple]: TokensReader[Option[T]] = new OptionRead[T] - class OptionRead[T: TokensReader.Simple] extends Simple[Option[T]]{ + class OptionRead[T: TokensReader.Simple] extends Simple[Option[T]] { def shortName = implicitly[TokensReader.Simple[T]].shortName def read(strs: Seq[String]) = { strs.lastOption match { @@ -99,12 +113,13 @@ object TokensReader { override def allowEmpty = true } - implicit def SeqRead[C[_] <: Iterable[_], T: TokensReader.Simple] - (implicit factory: Factory[T, C[T]]): TokensReader[C[T]] = + implicit def SeqRead[C[_] <: Iterable[_], T: TokensReader.Simple](implicit + factory: Factory[T, C[T]] + ): TokensReader[C[T]] = new SeqRead[C, T] class SeqRead[C[_] <: Iterable[_], T: TokensReader.Simple](implicit factory: Factory[T, C[T]]) - extends Simple[C[T]]{ + extends Simple[C[T]] { def shortName = implicitly[TokensReader.Simple[T]].shortName def read(strs: Seq[String]) = { strs @@ -126,7 +141,7 @@ object TokensReader { implicit def MapRead[K: TokensReader, V: TokensReader]: TokensReader[Map[K, V]] = new MapRead[K, V] - class MapRead[K: TokensReader, V: TokensReader] extends Simple[Map[K, V]]{ + class MapRead[K: TokensReader, V: TokensReader] extends Simple[Map[K, V]] { def shortName = "k=v" def read(strs: Seq[String]) = { strs.foldLeft[Either[String, Map[K, V]]](Right(Map())) { @@ -149,3 +164,82 @@ object TokensReader { override def allowEmpty = true } } + +object ArgSig { + def create[T, B](name0: String, arg: mainargs.arg, defaultOpt: Option[B => T]) + (implicit tokensReader: TokensReader[T]): ArgSig = { + val nameOpt = scala.Option(arg.name).orElse(if (name0.length == 1 || arg.noDefaultName) None + else Some(name0)) + val shortOpt = arg.short match { + case '\u0000' => if (name0.length != 1 || arg.noDefaultName) None else Some(name0(0)); + case c => Some(c) + } + val docOpt = scala.Option(arg.doc) + ArgSig( + nameOpt, + shortOpt, + docOpt, + defaultOpt.asInstanceOf[Option[Any => Any]], + tokensReader, + arg.positional + ) + } + + def flatten[T](x: ArgSig): Seq[ArgSig] = x.reader match { + case _: TokensReader.Terminal[T] => Seq(x) + case cls: TokensReader.Class[_] => cls.main.argSigs0.flatMap(flatten(_)) + } +} + +/** + * Models what is known by the router about a single argument: that it has + * a [[name]], a human-readable [[typeString]] describing what the type is + * (just for logging and reading, not a replacement for a `TypeTag`) and + * possible a function that can compute its default value + */ +case class ArgSig( + name: Option[String], + shortName: Option[Char], + doc: Option[String], + default: Option[Any => Any], + reader: TokensReader[_], + positional: Boolean +) + +case class MethodMains[B](value: Seq[MainData[Any, B]], base: () => B) + +/** + * What is known about a single endpoint for our routes. It has a [[name]], + * [[flattenedArgSigs]] for each argument, and a macro-generated [[invoke0]] + * that performs all the necessary argument parsing and de-serialization. + * + * Realistically, you will probably spend most of your time calling [[Invoker.invoke]] + * instead, which provides a nicer API to call it that mimmicks the API of + * calling a Scala method. + */ +case class MainData[T, B]( + name: String, + argSigs0: Seq[ArgSig], + doc: Option[String], + invokeRaw: (B, Seq[Any]) => T +) { + + val flattenedArgSigs: Seq[ArgSig] = + argSigs0.iterator.flatMap[ArgSig](ArgSig.flatten(_)).toVector +} + +object MainData { + def create[T, B]( + methodName: String, + main: mainargs.main, + argSigs: Seq[ArgSig], + invokeRaw: (B, Seq[Any]) => T + ) = { + MainData( + Option(main.name).getOrElse(methodName), + argSigs, + Option(main.doc), + invokeRaw + ) + } +} diff --git a/mainargs/test/src-jvm-2/AmmoniteTests.scala b/mainargs/test/src-jvm-2/AmmoniteTests.scala index 9c05ef1..42e16eb 100644 --- a/mainargs/test/src-jvm-2/AmmoniteTests.scala +++ b/mainargs/test/src-jvm-2/AmmoniteTests.scala @@ -21,7 +21,7 @@ case class AmmoniteConfig( ) object AmmoniteConfig { - implicit object PathRead extends TokensReader.Simple[os.Path]{ + implicit object PathRead extends TokensReader.Simple[os.Path] { def shortName = "path" def read(strs: Seq[String]) = Right(os.Path(strs.head, os.pwd)) } diff --git a/mainargs/test/src/ClassTests.scala b/mainargs/test/src/ClassTests.scala index f12fa1e..3725963 100644 --- a/mainargs/test/src/ClassTests.scala +++ b/mainargs/test/src/ClassTests.scala @@ -32,7 +32,7 @@ object ClassTests extends TestSuite { fooParser.constructRaw(Seq("-x", "1")) ==> Result.Failure.MismatchedArguments( Seq( - ArgSig.Simple( + ArgSig( None, Some('y'), None, @@ -62,7 +62,7 @@ object ClassTests extends TestSuite { barParser.constructRaw(Seq("-w", "-x", "1", "-z", "xxx")) ==> Result.Failure.MismatchedArguments( Seq( - ArgSig.Simple( + ArgSig( None, Some('y'), None, @@ -83,7 +83,7 @@ object ClassTests extends TestSuite { barParser.constructRaw(Seq("-w", "-x", "1", "-y", "2")) ==> Result.Failure.MismatchedArguments( Seq( - ArgSig.Simple( + ArgSig( Some("zzzz"), Some('z'), None, @@ -105,7 +105,7 @@ object ClassTests extends TestSuite { barParser.constructRaw(Seq("-w", "-x", "1")) ==> Result.Failure.MismatchedArguments( Seq( - ArgSig.Simple( + ArgSig( None, Some('y'), None, @@ -113,7 +113,7 @@ object ClassTests extends TestSuite { mainargs.TokensReader.IntRead, false ), - ArgSig.Simple( + ArgSig( Some("zzzz"), Some('z'), None, @@ -138,12 +138,12 @@ object ClassTests extends TestSuite { case Result.Failure.InvalidArguments( Seq( Result.ParamError.Failed( - ArgSig.Simple(None, Some('x'), None, None, _, false), + ArgSig(None, Some('x'), None, None, _, false), Seq("xxx"), _ ), Result.ParamError.Failed( - ArgSig.Simple(None, Some('y'), None, None, _, false), + ArgSig(None, Some('y'), None, None, _, false), Seq("hohoho"), _ ) diff --git a/mainargs/test/src/CoreTests.scala b/mainargs/test/src/CoreTests.scala index 079550b..49551a1 100644 --- a/mainargs/test/src/CoreTests.scala +++ b/mainargs/test/src/CoreTests.scala @@ -58,9 +58,9 @@ class CoreTests(allowPositional: Boolean) extends TestSuite { names == List("foo", "bar", "qux", "ex") ) - val evaledArgs = check.mains.value.map(_.argSigs.map { - case ArgSig.Simple(name, s, docs, None, parser, _) => (s, docs, None, parser) - case ArgSig.Simple(name, s, docs, Some(default), parser, _) => + val evaledArgs = check.mains.value.map(_.flattenedArgSigs.map { + case ArgSig(name, s, docs, None, parser, _) => (s, docs, None, parser) + case ArgSig(name, s, docs, Some(default), parser, _) => (s, docs, Some(default(CoreBase)), parser) }) @@ -113,7 +113,7 @@ class CoreTests(allowPositional: Boolean) extends TestSuite { test("missingParams") { test - assertMatch(check.parseInvoke(List("bar"))) { case Result.Failure.MismatchedArguments( - Seq(ArgSig.Simple(None, Some('i'), _, _, _, _)), + Seq(ArgSig(None, Some('i'), _, _, _, _)), Nil, Nil, None @@ -121,7 +121,7 @@ class CoreTests(allowPositional: Boolean) extends TestSuite { } test - assertMatch(check.parseInvoke(List("qux", "-s", "omg"))) { case Result.Failure.MismatchedArguments( - Seq(ArgSig.Simple(None, Some('i'), _, _, _, _)), + Seq(ArgSig(None, Some('i'), _, _, _, _)), Nil, Nil, None @@ -150,21 +150,21 @@ object CorePositionalDisabledOnlyTests extends TestSuite { test - check( List("bar", "2"), MismatchedArguments( - missing = List(ArgSig.Simple(None, Some('i'), None, None, TokensReader.IntRead, false)), + missing = List(ArgSig(None, Some('i'), None, None, TokensReader.IntRead, false)), unknown = List("2") ) ) test - check( List("qux", "2"), MismatchedArguments( - missing = List(ArgSig.Simple(None, Some('i'), None, None, TokensReader.IntRead, false)), + missing = List(ArgSig(None, Some('i'), None, None, TokensReader.IntRead, false)), unknown = List("2") ) ) test - check( List("qux", "3", "x"), MismatchedArguments( - missing = List(ArgSig.Simple(None, Some('i'), None, None, TokensReader.IntRead, false)), + missing = List(ArgSig(None, Some('i'), None, None, TokensReader.IntRead, false)), unknown = List("3", "x") ) ) @@ -178,7 +178,7 @@ object CorePositionalDisabledOnlyTests extends TestSuite { test("invalidParams") - check( List("bar", "lol"), MismatchedArguments( - missing = List(ArgSig.Simple(None, Some('i'), None, None, TokensReader.IntRead, false)), + missing = List(ArgSig(None, Some('i'), None, None, TokensReader.IntRead, false)), unknown = List("lol") ) ) @@ -187,7 +187,7 @@ object CorePositionalDisabledOnlyTests extends TestSuite { test("redundantParams") - check( List("qux", "1", "-i", "2"), MismatchedArguments( - missing = List(ArgSig.Simple(None, Some('i'), None, None, TokensReader.IntRead, false)), + missing = List(ArgSig(None, Some('i'), None, None, TokensReader.IntRead, false)), unknown = List("1", "-i", "2") ) ) @@ -217,7 +217,7 @@ object CorePositionalEnabledOnlyTests extends TestSuite { ) { case Result.Failure.InvalidArguments( List(Result.ParamError.Failed( - ArgSig.Simple(None, Some('i'), _, _, _, _), + ArgSig(None, Some('i'), _, _, _, _), Seq("lol"), _ )) @@ -230,7 +230,7 @@ object CorePositionalEnabledOnlyTests extends TestSuite { case Result.Failure.MismatchedArguments( Nil, Nil, - Seq((ArgSig.Simple(None, Some('i'), _, _, _, _), Seq("1", "2"))), + Seq((ArgSig(None, Some('i'), _, _, _, _), Seq("1", "2"))), None ) => } diff --git a/mainargs/test/src/PositionalTests.scala b/mainargs/test/src/PositionalTests.scala index a2847c7..93a4547 100644 --- a/mainargs/test/src/PositionalTests.scala +++ b/mainargs/test/src/PositionalTests.scala @@ -14,8 +14,8 @@ object PositionalTests extends TestSuite { List("true", "true", "true"), Result.Failure.MismatchedArguments( Vector( - ArgSig.Simple(None, Some('x'), None, None, TokensReader.BooleanRead, false), - ArgSig.Simple(None, Some('z'), None, None, TokensReader.BooleanRead, false) + ArgSig(None, Some('x'), None, None, TokensReader.BooleanRead, false), + ArgSig(None, Some('z'), None, None, TokensReader.BooleanRead, false) ), List("true", "true"), List(), @@ -30,8 +30,8 @@ object PositionalTests extends TestSuite { List("-x", "true", "-y", "false", "-z", "false"), Result.Failure.MismatchedArguments( Vector( - ArgSig.Simple(None, Some('y'), None, None, TokensReader.BooleanRead, true), - ArgSig.Simple(None, Some('z'), None, None, TokensReader.BooleanRead, false) + ArgSig(None, Some('y'), None, None, TokensReader.BooleanRead, true), + ArgSig(None, Some('z'), None, None, TokensReader.BooleanRead, false) ), List("-y", "false", "-z", "false"), List(), diff --git a/mainargs/test/src/VarargsBaseTests.scala b/mainargs/test/src/VarargsBaseTests.scala index aeb77a2..4e7b41b 100644 --- a/mainargs/test/src/VarargsBaseTests.scala +++ b/mainargs/test/src/VarargsBaseTests.scala @@ -42,7 +42,7 @@ trait VarargsBaseTests extends TestSuite { case Result.Failure.InvalidArguments( List( Result.ParamError.Failed( - ArgSig.Simple(Some("nums"), _, _, _, _, _), + ArgSig(Some("nums"), _, _, _, _, _), Seq("--nums", "31337"), """java.lang.NumberFormatException: For input string: "--nums"""" | """java.lang.NumberFormatException: --nums""" @@ -57,7 +57,7 @@ trait VarargsBaseTests extends TestSuite { case Result.Failure.InvalidArguments( List( Result.ParamError.Failed( - ArgSig.Simple(Some("nums"), _, _, _, _, _), + ArgSig(Some("nums"), _, _, _, _, _), Seq("1", "2", "3", "--nums", "4"), "java.lang.NumberFormatException: For input string: \"--nums\"" | "java.lang.NumberFormatException: --nums" @@ -75,7 +75,7 @@ trait VarargsBaseTests extends TestSuite { test("notEnoughNormalArgsStillFails") { assertMatch(check.parseInvoke(List("mixedVariadic"))) { case Result.Failure.MismatchedArguments( - Seq(ArgSig.Simple(Some("first"), _, _, _, _, _)), + Seq(ArgSig(Some("first"), _, _, _, _, _)), Nil, Nil, None @@ -89,7 +89,7 @@ trait VarargsBaseTests extends TestSuite { case Result.Failure.InvalidArguments( List( Result.ParamError.Failed( - ArgSig.Simple(Some("nums"), _, _, _, _, _), + ArgSig(Some("nums"), _, _, _, _, _), Seq("aa", "bb", "3"), "java.lang.NumberFormatException: For input string: \"aa\"" | "java.lang.NumberFormatException: aa" @@ -104,7 +104,7 @@ trait VarargsBaseTests extends TestSuite { case Result.Failure.InvalidArguments( List( Result.ParamError.Failed( - ArgSig.Simple(Some("first"), _, _, _, _, _), + ArgSig(Some("first"), _, _, _, _, _), Seq("aa"), "java.lang.NumberFormatException: For input string: \"aa\"" | "java.lang.NumberFormatException: aa" diff --git a/mainargs/test/src/VarargsCustomTests.scala b/mainargs/test/src/VarargsCustomTests.scala index f29a553..428dccf 100644 --- a/mainargs/test/src/VarargsCustomTests.scala +++ b/mainargs/test/src/VarargsCustomTests.scala @@ -1,16 +1,16 @@ package mainargs import utest._ - object VarargsCustomTests extends VarargsBaseTests { // Test that we are able to replace the `Leftover` type entirely with our // own implementation class Wrapper[T](val unwrap: Seq[T]) - class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader.Leftover[Wrapper[T], T]{ + class WrapperRead[T](implicit val wrapped: TokensReader[T]) + extends TokensReader.Leftover[Wrapper[T], T] { def read(strs: Seq[String]) = { val results = strs.map(s => implicitly[TokensReader[T]].read(Seq(s))) - val failures = results.collect{case Left(x) => x} - val successes = results.collect{case Right(x) => x} + val failures = results.collect { case Left(x) => x } + val successes = results.collect { case Right(x) => x } if (failures.nonEmpty) Left(failures.head) else Right(new Wrapper(successes)) diff --git a/mainargs/test/src/VarargsWrappedTests.scala b/mainargs/test/src/VarargsWrappedTests.scala index 201aa7a..479dce1 100644 --- a/mainargs/test/src/VarargsWrappedTests.scala +++ b/mainargs/test/src/VarargsWrappedTests.scala @@ -1,12 +1,12 @@ package mainargs import utest._ - object VarargsWrappedTests extends VarargsBaseTests { // Test that we are able to wrap the `Leftover` type we use for Varargs in // our own custom types, and have things work class Wrapper[T](val unwrap: T) - class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader.Leftover[Wrapper[T], T]{ + class WrapperRead[T](implicit val wrapped: TokensReader[T]) + extends TokensReader.Leftover[Wrapper[T], T] { def read(strs: Seq[String]) = wrapped.read(strs).map(new Wrapper(_)) } From 08778a615474770c342e0b84235b724854ac5e0b Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 14:30:08 +0800 Subject: [PATCH 10/17] tidying --- mainargs/src/TokensReader.scala | 24 ++++++++++++--------- mainargs/test/src/VarargsCustomTests.scala | 7 +++--- mainargs/test/src/VarargsWrappedTests.scala | 4 +++- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 6ba2ecc..a51ca66 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -6,7 +6,6 @@ import scala.collection.mutable * Represents the ability to parse CLI input arguments into a type [[T]] */ sealed trait TokensReader[T] { - def read(strs: Seq[String]): Either[String, T] def isLeftover: Boolean def isFlag: Boolean def shortName: String @@ -16,6 +15,7 @@ object TokensReader { sealed trait Terminal[T] extends TokensReader[T] trait Simple[T] extends Terminal[T] { def shortName: String + def read(strs: Seq[String]): Either[String, T] def alwaysRepeatable: Boolean = false def allowEmpty: Boolean = false def isLeftover = false @@ -24,7 +24,6 @@ object TokensReader { trait Flag extends Terminal[mainargs.Flag] { def shortName = "" - def read(strs: Seq[String]) = ??? def isLeftover = false def isFlag = true } @@ -32,6 +31,7 @@ object TokensReader { trait Leftover[T, V] extends Terminal[T] { def isLeftover = true def isFlag = false + def read(strs: Seq[String]): Either[String, T] def wrapped: TokensReader[V] def shortName = wrapped.shortName } @@ -40,7 +40,6 @@ object TokensReader { def isLeftover = false def isFlag = false def shortName = ??? - def read(strs: Seq[String]) = ??? def companion: () => Any def main: MainData[T, Any] } @@ -90,7 +89,12 @@ object TokensReader { extends Leftover[mainargs.Leftover[T], T] { def read(strs: Seq[String]) = { val (failures, successes) = strs - .map(s => implicitly[TokensReader[T]].read(Seq(s))) + .map(s => + implicitly[TokensReader[T]] match{ + case r: TokensReader.Simple[T] => r.read(Seq(s)) + case r: TokensReader.Leftover[T, _] => r.read(Seq(s)) + } + ) .partitionMap(identity) if (failures.nonEmpty) Left(failures.head) @@ -104,7 +108,7 @@ object TokensReader { def read(strs: Seq[String]) = { strs.lastOption match { case None => Right(None) - case Some(s) => implicitly[TokensReader[T]].read(Seq(s)) match { + case Some(s) => implicitly[TokensReader.Simple[T]].read(Seq(s)) match { case Left(s) => Left(s) case Right(s) => Right(Some(s)) } @@ -126,7 +130,7 @@ object TokensReader { .foldLeft(Right(factory.newBuilder): Either[String, mutable.Builder[T, C[T]]]) { case (Left(s), _) => Left(s) case (Right(builder), token) => - implicitly[TokensReader[T]].read(Seq(token)) match { + implicitly[TokensReader.Simple[T]].read(Seq(token)) match { case Left(s) => Left(s) case Right(v) => builder += v @@ -139,9 +143,9 @@ object TokensReader { override def allowEmpty = true } - implicit def MapRead[K: TokensReader, V: TokensReader]: TokensReader[Map[K, V]] = + implicit def MapRead[K: TokensReader.Simple, V: TokensReader.Simple]: TokensReader[Map[K, V]] = new MapRead[K, V] - class MapRead[K: TokensReader, V: TokensReader] extends Simple[Map[K, V]] { + class MapRead[K: TokensReader.Simple, V: TokensReader.Simple] extends Simple[Map[K, V]] { def shortName = "k=v" def read(strs: Seq[String]) = { strs.foldLeft[Either[String, Map[K, V]]](Right(Map())) { @@ -152,8 +156,8 @@ object TokensReader { for { tuple <- Right((k, v)): Either[String, (String, String)] (k, v) = tuple - key <- implicitly[TokensReader[K]].read(Seq(k)) - value <- implicitly[TokensReader[V]].read(Seq(v)) + key <- implicitly[TokensReader.Simple[K]].read(Seq(k)) + value <- implicitly[TokensReader.Simple[V]].read(Seq(v)) } yield prev + (key -> value) case _ => Left("parameter must be in k=v format") diff --git a/mainargs/test/src/VarargsCustomTests.scala b/mainargs/test/src/VarargsCustomTests.scala index 428dccf..e431946 100644 --- a/mainargs/test/src/VarargsCustomTests.scala +++ b/mainargs/test/src/VarargsCustomTests.scala @@ -5,10 +5,10 @@ object VarargsCustomTests extends VarargsBaseTests { // Test that we are able to replace the `Leftover` type entirely with our // own implementation class Wrapper[T](val unwrap: Seq[T]) - class WrapperRead[T](implicit val wrapped: TokensReader[T]) + class WrapperRead[T](implicit val wrapped: TokensReader.Simple[T]) extends TokensReader.Leftover[Wrapper[T], T] { def read(strs: Seq[String]) = { - val results = strs.map(s => implicitly[TokensReader[T]].read(Seq(s))) + val results = strs.map(s => implicitly[TokensReader.Simple[T]].read(Seq(s))) val failures = results.collect { case Left(x) => x } val successes = results.collect { case Right(x) => x } @@ -17,7 +17,8 @@ object VarargsCustomTests extends VarargsBaseTests { } } - implicit def WrapperRead[T: TokensReader]: TokensReader[Wrapper[T]] = new WrapperRead[T] + implicit def WrapperRead[T: TokensReader.Simple]: TokensReader[Wrapper[T]] = + new WrapperRead[T] object Base { @main diff --git a/mainargs/test/src/VarargsWrappedTests.scala b/mainargs/test/src/VarargsWrappedTests.scala index 479dce1..3379903 100644 --- a/mainargs/test/src/VarargsWrappedTests.scala +++ b/mainargs/test/src/VarargsWrappedTests.scala @@ -7,7 +7,9 @@ object VarargsWrappedTests extends VarargsBaseTests { class Wrapper[T](val unwrap: T) class WrapperRead[T](implicit val wrapped: TokensReader[T]) extends TokensReader.Leftover[Wrapper[T], T] { - def read(strs: Seq[String]) = wrapped.read(strs).map(new Wrapper(_)) + def read(strs: Seq[String]) = wrapped + .asInstanceOf[TokensReader.Leftover[T, _]] + .read(strs).map(new Wrapper(_)) } implicit def WrapperRead[T: TokensReader]: TokensReader[Wrapper[T]] = new WrapperRead[T] From 2f5f282b22e32286129fc9ebf0fa196e3c5ce22b Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 15:03:23 +0800 Subject: [PATCH 11/17] . --- mainargs/src/TokensReader.scala | 20 ++++++++++++++------ mainargs/test/src/VarargsWrappedTests.scala | 6 ++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index a51ca66..3abcff3 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -4,16 +4,25 @@ import scala.collection.mutable /** * Represents the ability to parse CLI input arguments into a type [[T]] + * + * Has a fixed number of direct subtypes - [[Simple]], [[Flag]], [[Leftover]], + * and [[Class]] - but each of those can be extended by an arbitrary number of + * user-specified instances. */ sealed trait TokensReader[T] { def isLeftover: Boolean def isFlag: Boolean - def shortName: String } object TokensReader { + sealed trait Terminal[T] extends TokensReader[T] - trait Simple[T] extends Terminal[T] { + + sealed trait ShortNamed[T] extends Terminal[T] { + def shortName: String + } + + trait Simple[T] extends ShortNamed[T] { def shortName: String def read(strs: Seq[String]): Either[String, T] def alwaysRepeatable: Boolean = false @@ -23,16 +32,15 @@ object TokensReader { } trait Flag extends Terminal[mainargs.Flag] { - def shortName = "" def isLeftover = false def isFlag = true } - trait Leftover[T, V] extends Terminal[T] { + trait Leftover[T, V] extends ShortNamed[T] { def isLeftover = true def isFlag = false def read(strs: Seq[String]): Either[String, T] - def wrapped: TokensReader[V] + def wrapped: ShortNamed[V] def shortName = wrapped.shortName } @@ -82,7 +90,7 @@ object TokensReader { def read(strs: Seq[String]) = tryEither(strs.last.toDouble) } - implicit def LeftoverRead[T: TokensReader.Simple]: TokensReader[mainargs.Leftover[T]] = + implicit def LeftoverRead[T: TokensReader.Simple]: TokensReader.ShortNamed[mainargs.Leftover[T]] = new LeftoverRead[T]()(implicitly[TokensReader.Simple[T]]) class LeftoverRead[T](implicit val wrapped: TokensReader.Simple[T]) diff --git a/mainargs/test/src/VarargsWrappedTests.scala b/mainargs/test/src/VarargsWrappedTests.scala index 3379903..4dbaa56 100644 --- a/mainargs/test/src/VarargsWrappedTests.scala +++ b/mainargs/test/src/VarargsWrappedTests.scala @@ -5,14 +5,16 @@ object VarargsWrappedTests extends VarargsBaseTests { // Test that we are able to wrap the `Leftover` type we use for Varargs in // our own custom types, and have things work class Wrapper[T](val unwrap: T) - class WrapperRead[T](implicit val wrapped: TokensReader[T]) + class WrapperRead[T](implicit val wrapped: TokensReader.ShortNamed[T]) extends TokensReader.Leftover[Wrapper[T], T] { + def read(strs: Seq[String]) = wrapped .asInstanceOf[TokensReader.Leftover[T, _]] .read(strs).map(new Wrapper(_)) } - implicit def WrapperRead[T: TokensReader]: TokensReader[Wrapper[T]] = new WrapperRead[T] + implicit def WrapperRead[T: TokensReader.ShortNamed]: TokensReader[Wrapper[T]] = + new WrapperRead[T] object Base { @main From 38e13a54dd875ec0848b35bcdc2ab687bb4dbddf Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 15:15:01 +0800 Subject: [PATCH 12/17] . --- mainargs/src/TokensReader.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 3abcff3..4844ac4 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -47,7 +47,6 @@ object TokensReader { trait Class[T] extends TokensReader[T] { def isLeftover = false def isFlag = false - def shortName = ??? def companion: () => Any def main: MainData[T, Any] } From 8f09de523a711311fed12ee92fd055b3e597d0aa Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 15:26:50 +0800 Subject: [PATCH 13/17] . --- mainargs/src/TokensReader.scala | 54 ++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 4844ac4..8d36931 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -10,8 +10,10 @@ import scala.collection.mutable * user-specified instances. */ sealed trait TokensReader[T] { - def isLeftover: Boolean - def isFlag: Boolean + def isLeftover = false + def isFlag = false + def isClass = false + def isSimple = false } object TokensReader { @@ -19,36 +21,66 @@ object TokensReader { sealed trait Terminal[T] extends TokensReader[T] sealed trait ShortNamed[T] extends Terminal[T] { + /** + * The label that shows up in the CLI help message, e.g. the `bar` in + * `--foo ` + */ def shortName: String } + /** + * A [[TokensReader]] for a single CLI parameter that takes a value + * e.g. `--foo bar` + */ trait Simple[T] extends ShortNamed[T] { - def shortName: String + /** + * Converts the given input tokens to a [[T]] or an error `String`. + * The input is a `Seq` because input tokens can be passed more than once, + * e.g. `--foo bar --foo qux` will result in [[read]] being passed + * `["foo", "qux"]` + */ def read(strs: Seq[String]): Either[String, T] + + /** + * Whether is CLI param is repeatable + */ def alwaysRepeatable: Boolean = false + + /** + * Whether this CLI param can be no passed from the CLI, even if a default + * value is not specified. In that case, [[read]] receives an empty `Seq` + */ def allowEmpty: Boolean = false - def isLeftover = false - def isFlag = false + override def isSimple = true } + /** + * A [[TokensReader]] for a flag that does not take any value, e.g. `--foo` + */ trait Flag extends Terminal[mainargs.Flag] { - def isLeftover = false - def isFlag = true + override def isFlag = true } + /** + * A [[TokensReader]] for parsing the left-over parameters that do not belong + * to any other flag or parameter. + */ trait Leftover[T, V] extends ShortNamed[T] { - def isLeftover = true - def isFlag = false def read(strs: Seq[String]): Either[String, T] + def wrapped: ShortNamed[V] def shortName = wrapped.shortName + override def isLeftover = true } + /** + * A [[TokensReader]] that can parse an instance of the class [[T]], which + * may contain multiple fields each parsed by their own [[TokensReader]] + */ trait Class[T] extends TokensReader[T] { - def isLeftover = false - def isFlag = false def companion: () => Any def main: MainData[T, Any] + override def isClass = true } def tryEither[T](f: => T) = From db3b684f2b5cd7fcb975de1b6c85eb9e9ad07ebb Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 21:42:59 +0800 Subject: [PATCH 14/17] . --- build.sc | 2 +- mainargs/src/Invoker.scala | 4 ++++ mainargs/src/TokenGrouping.scala | 3 ++- mainargs/src/TokensReader.scala | 20 ++++++++++++++++---- mainargs/test/src/ConstantTests.scala | 27 +++++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 mainargs/test/src/ConstantTests.scala diff --git a/build.sc b/build.sc index 1838c86..8b8bd75 100644 --- a/build.sc +++ b/build.sc @@ -19,7 +19,7 @@ val scalaJSVersions = scalaVersions.map((_, "1.10.1")) val scalaNativeVersions = scalaVersions.map((_, "0.4.7")) trait MainArgsPublishModule extends PublishModule with CrossScalaModule with Mima { - def publishVersion = VcsVersion.vcsState().format() + def publishVersion = "0.5.0-M1" override def mimaPreviousVersions = Seq("0.2.3").filterNot(_ => scalaVersion().startsWith("3.") && this.isInstanceOf[ScalaNativeModule] diff --git a/mainargs/src/Invoker.scala b/mainargs/src/Invoker.scala index 6544039..a937d66 100644 --- a/mainargs/src/Invoker.scala +++ b/mainargs/src/Invoker.scala @@ -30,6 +30,10 @@ object Invoker { case r: TokensReader.Flag => Right(ParamResult.Success(Flag(kvs.contains(a)).asInstanceOf[T])) case r: TokensReader.Simple[T] => Right(makeReadCall(kvs, base, a, r)) + case r: TokensReader.Constant[T] => Right(r.read() match { + case Left(s) => ParamResult.Failure(Seq(Result.ParamError.Failed(a, Nil, s))) + case Right(v) => ParamResult.Success(v) + }) case r: TokensReader.Leftover[T, _] => Right(makeReadVarargsCall(a, extras, r)) case r: TokensReader.Class[T] => Left( diff --git a/mainargs/src/TokenGrouping.scala b/mainargs/src/TokenGrouping.scala index 854939c..b275e59 100644 --- a/mainargs/src/TokenGrouping.scala +++ b/mainargs/src/TokenGrouping.scala @@ -18,7 +18,7 @@ object TokenGrouping { val positionalArgSigs = argSigs .filter { - case x: ArgSig if x.reader.isLeftover => false + case x: ArgSig if x.reader.isLeftover || x.reader.isConstant => false case x: ArgSig if x.positional => true case x => allowPositional } @@ -71,6 +71,7 @@ object TokenGrouping { case r: TokensReader.Flag => vs.size > 1 && !allowRepeats case r: TokensReader.Simple[_] => vs.size > 1 && !r.alwaysRepeatable && !allowRepeats case r: TokensReader.Leftover[_, _] => false + case r: TokensReader.Constant[_] => false } } diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 8d36931..44beb4f 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -5,14 +5,15 @@ import scala.collection.mutable /** * Represents the ability to parse CLI input arguments into a type [[T]] * - * Has a fixed number of direct subtypes - [[Simple]], [[Flag]], [[Leftover]], - * and [[Class]] - but each of those can be extended by an arbitrary number of - * user-specified instances. + * Has a fixed number of direct subtypes - [[Simple]], [[Constant]], [[Flag]], + * [[Leftover]], and [[Class]] - but each of those can be extended by an + * arbitrary number of user-specified instances. */ sealed trait TokensReader[T] { def isLeftover = false def isFlag = false def isClass = false + def isConstant = false def isSimple = false } @@ -54,6 +55,17 @@ object TokensReader { override def isSimple = true } + /** + * A [[TokensReader]] that doesn't read any tokens and just returns a value. + * Useful sometimes for injecting things into main methods that aren't + * strictly computed from CLI argument tokens but nevertheless need to get + * passed in. + */ + trait Constant[T] extends Terminal[T] { + def read(): Either[String, T] + override def isConstant = true + } + /** * A [[TokensReader]] for a flag that does not take any value, e.g. `--foo` */ @@ -121,7 +133,7 @@ object TokensReader { def read(strs: Seq[String]) = tryEither(strs.last.toDouble) } - implicit def LeftoverRead[T: TokensReader.Simple]: TokensReader.ShortNamed[mainargs.Leftover[T]] = + implicit def LeftoverRead[T: TokensReader.Simple]: TokensReader.Leftover[mainargs.Leftover[T], T] = new LeftoverRead[T]()(implicitly[TokensReader.Simple[T]]) class LeftoverRead[T](implicit val wrapped: TokensReader.Simple[T]) diff --git a/mainargs/test/src/ConstantTests.scala b/mainargs/test/src/ConstantTests.scala new file mode 100644 index 0000000..d5e9d0e --- /dev/null +++ b/mainargs/test/src/ConstantTests.scala @@ -0,0 +1,27 @@ +package mainargs +import utest._ + +object ConstantTests extends TestSuite { + + case class Injected() + implicit def InjectedTokensReader: TokensReader.Constant[Injected] = + new TokensReader.Constant[Injected]{ + def read() = Right(new Injected()) + } + object Base { + @main + def flaggy(a: Injected, b: Boolean) = a.toString + " " + b + } + val check = new Checker(ParserForMethods(Base), allowPositional = true) + + val tests = Tests { + test - check( + List("-b", "true"), + Result.Success("Injected() true") + ) + test - check( + List("-b", "false"), + Result.Success("Injected() false") + ) + } +} From 11bd9666d80a53c2469d451eb4a2a42d9b7a4888 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 21:51:25 +0800 Subject: [PATCH 15/17] fix --- mainargs/src/Parser.scala | 2 +- mainargs/src/Renderer.scala | 6 +++--- mainargs/src/TokensReader.scala | 3 +++ mainargs/test/src-jvm-2/AmmoniteTests.scala | 7 +++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/mainargs/src/Parser.scala b/mainargs/src/Parser.scala index 3894e32..d225b7a 100644 --- a/mainargs/src/Parser.scala +++ b/mainargs/src/Parser.scala @@ -188,7 +188,7 @@ class ParserForClass[T](val main: MainData[T, Any], val companion: () => Any) main, 0, totalWidth, - Renderer.getLeftColWidth(main.flattenedArgSigs), + Renderer.getLeftColWidth(main.renderedArgSigs), docsOnNewLine, Option(customName), Option(customDoc), diff --git a/mainargs/src/Renderer.scala b/mainargs/src/Renderer.scala index caf6165..738818f 100644 --- a/mainargs/src/Renderer.scala +++ b/mainargs/src/Renderer.scala @@ -138,8 +138,8 @@ object Renderer { val argLeftCol = if (docsOnNewLine) leftIndent + 8 else leftColWidth + leftIndent + 2 + 2 val sortedArgs = - if (sorted) main.flattenedArgSigs.sorted(ArgOrd) - else main.flattenedArgSigs + if (sorted) main.renderedArgSigs.sorted(ArgOrd) + else main.renderedArgSigs val args = sortedArgs.map(renderArg(_, argLeftCol, totalWidth)) @@ -236,7 +236,7 @@ object Renderer { def expectedMsg() = { if (printHelpOnError) { - val leftColWidth = getLeftColWidth(main.flattenedArgSigs) + val leftColWidth = getLeftColWidth(main.renderedArgSigs) "Expected Signature: " + Renderer.formatMainMethodSignature( main, diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 44beb4f..9f4a8a5 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -281,6 +281,9 @@ case class MainData[T, B]( val flattenedArgSigs: Seq[ArgSig] = argSigs0.iterator.flatMap[ArgSig](ArgSig.flatten(_)).toVector + + val renderedArgSigs: Seq[ArgSig] = + flattenedArgSigs.filter(!_.reader.isConstant) } object MainData { diff --git a/mainargs/test/src-jvm-2/AmmoniteTests.scala b/mainargs/test/src-jvm-2/AmmoniteTests.scala index 42e16eb..697c8e7 100644 --- a/mainargs/test/src-jvm-2/AmmoniteTests.scala +++ b/mainargs/test/src-jvm-2/AmmoniteTests.scala @@ -26,8 +26,14 @@ object AmmoniteConfig { def read(strs: Seq[String]) = Right(os.Path(strs.head, os.pwd)) } + case class InjectedConstant() + + implicit object InjectedTokensReader extends TokensReader.Constant[InjectedConstant] { + def read() = Right(new InjectedConstant()) + } @main case class Core( + injectedConstant: InjectedConstant, @arg( name = "no-default-predef", doc = "Disable the default predef and run Ammonite with the minimal predef possible" @@ -212,6 +218,7 @@ object AmmoniteTests extends TestSuite { Right( AmmoniteConfig( AmmoniteConfig.Core( + injectedConstant = AmmoniteConfig.InjectedConstant(), noDefaultPredef = Flag(), silent = Flag(), watch = Flag(), From bc6e254f83f1277167e2f48485a142e60c639c67 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 27 Apr 2023 22:14:21 +0800 Subject: [PATCH 16/17] . --- mainargs/src/Renderer.scala | 2 +- mainargs/src/TokensReader.scala | 6 +++--- mainargs/test/src/VarargsCustomTests.scala | 3 ++- mainargs/test/src/VarargsWrappedTests.scala | 4 +++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mainargs/src/Renderer.scala b/mainargs/src/Renderer.scala index 738818f..2b648b6 100644 --- a/mainargs/src/Renderer.scala +++ b/mainargs/src/Renderer.scala @@ -27,7 +27,7 @@ object Renderer { (shortPrefix ++ nameSuffix ++ Seq(typeSuffix)).mkString(" ") case r: TokensReader.Leftover[_, _] => - s"${arg.name.get} <${r.wrapped.shortName}>..." + s"${arg.name.get} <${r.shortName}>..." } /** diff --git a/mainargs/src/TokensReader.scala b/mainargs/src/TokensReader.scala index 9f4a8a5..e35f0e8 100644 --- a/mainargs/src/TokensReader.scala +++ b/mainargs/src/TokensReader.scala @@ -80,8 +80,7 @@ object TokensReader { trait Leftover[T, V] extends ShortNamed[T] { def read(strs: Seq[String]): Either[String, T] - def wrapped: ShortNamed[V] - def shortName = wrapped.shortName + def shortName: String override def isLeftover = true } @@ -136,7 +135,7 @@ object TokensReader { implicit def LeftoverRead[T: TokensReader.Simple]: TokensReader.Leftover[mainargs.Leftover[T], T] = new LeftoverRead[T]()(implicitly[TokensReader.Simple[T]]) - class LeftoverRead[T](implicit val wrapped: TokensReader.Simple[T]) + class LeftoverRead[T](implicit wrapped: TokensReader.Simple[T]) extends Leftover[mainargs.Leftover[T], T] { def read(strs: Seq[String]) = { val (failures, successes) = strs @@ -151,6 +150,7 @@ object TokensReader { if (failures.nonEmpty) Left(failures.head) else Right(Leftover(successes: _*)) } + def shortName = wrapped.shortName } implicit def OptionRead[T: TokensReader.Simple]: TokensReader[Option[T]] = new OptionRead[T] diff --git a/mainargs/test/src/VarargsCustomTests.scala b/mainargs/test/src/VarargsCustomTests.scala index e431946..0073874 100644 --- a/mainargs/test/src/VarargsCustomTests.scala +++ b/mainargs/test/src/VarargsCustomTests.scala @@ -5,7 +5,7 @@ object VarargsCustomTests extends VarargsBaseTests { // Test that we are able to replace the `Leftover` type entirely with our // own implementation class Wrapper[T](val unwrap: Seq[T]) - class WrapperRead[T](implicit val wrapped: TokensReader.Simple[T]) + class WrapperRead[T](implicit wrapped: TokensReader.Simple[T]) extends TokensReader.Leftover[Wrapper[T], T] { def read(strs: Seq[String]) = { val results = strs.map(s => implicitly[TokensReader.Simple[T]].read(Seq(s))) @@ -15,6 +15,7 @@ object VarargsCustomTests extends VarargsBaseTests { if (failures.nonEmpty) Left(failures.head) else Right(new Wrapper(successes)) } + def shortName = wrapped.shortName } implicit def WrapperRead[T: TokensReader.Simple]: TokensReader[Wrapper[T]] = diff --git a/mainargs/test/src/VarargsWrappedTests.scala b/mainargs/test/src/VarargsWrappedTests.scala index 4dbaa56..00d03bb 100644 --- a/mainargs/test/src/VarargsWrappedTests.scala +++ b/mainargs/test/src/VarargsWrappedTests.scala @@ -5,12 +5,14 @@ object VarargsWrappedTests extends VarargsBaseTests { // Test that we are able to wrap the `Leftover` type we use for Varargs in // our own custom types, and have things work class Wrapper[T](val unwrap: T) - class WrapperRead[T](implicit val wrapped: TokensReader.ShortNamed[T]) + class WrapperRead[T](implicit wrapped: TokensReader.ShortNamed[T]) extends TokensReader.Leftover[Wrapper[T], T] { def read(strs: Seq[String]) = wrapped .asInstanceOf[TokensReader.Leftover[T, _]] .read(strs).map(new Wrapper(_)) + + def shortName = wrapped.shortName } implicit def WrapperRead[T: TokensReader.ShortNamed]: TokensReader[Wrapper[T]] = From 0b2dc41c42f5b868a8516bb485dd65bcc0ecda9c Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 29 Apr 2023 08:23:48 +0800 Subject: [PATCH 17/17] . --- build.sc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sc b/build.sc index 9887418..4f7ec65 100644 --- a/build.sc +++ b/build.sc @@ -19,7 +19,7 @@ val scalaJSVersions = scalaVersions.map((_, "1.10.1")) val scalaNativeVersions = scalaVersions.map((_, "0.4.7")) trait MainArgsPublishModule extends PublishModule with CrossScalaModule with Mima { - def publishVersion = "0.5.0-M1" + def publishVersion = VcsVersion.vcsState().format() override def mimaPreviousVersions = Seq("0.2.3").filterNot(_ => scalaVersion().startsWith("3.") && this.isInstanceOf[ScalaNativeModule]