Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept syntactic IDE suggestions and replace function calls for more direct options #315

Merged
merged 1 commit into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zio-cli/shared/src/main/scala/zio/cli/AutoCorrect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private[cli] object AutoCorrect {
for {
row <- 1 to rowCount
col <- 1 to columnCount
} yield {
} {
val cost = if (normalFirst.charAt(row - 1) == normalSecond.charAt(col - 1)) 0 else 1

matrix(row)(col) = Seq(
Expand Down
17 changes: 7 additions & 10 deletions zio-cli/shared/src/main/scala/zio/cli/CliApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ object CliApp {
def footer(newFooter: HelpDoc): CliApp[R, E, A] =
copy(footer = self.footer + newFooter)

def printDocs(helpDoc: HelpDoc): UIO[Unit] =
private def printDocs(helpDoc: HelpDoc): UIO[Unit] =
printLine(helpDoc.toPlaintext(80)).!

def run(args: List[String]): ZIO[R, CliError[E], Option[A]] = {
Expand All @@ -83,12 +83,11 @@ object CliApp {
// TODO add rendering of built-in options such as help
printLine(
(fancyName + header + synopsisHelpDoc + helpDoc + self.footer).toPlaintext(columnWidth = 300)
).map(_ => None).mapError(CliError.IO(_))

).mapBoth(CliError.IO(_), _ => None)
case ShowCompletionScript(path, shellType) =>
printLine(
CompletionScript(path, if (self.command.names.nonEmpty) self.command.names else Set(self.name), shellType)
).map(_ => None).mapError(CliError.IO(_))
).mapBoth(CliError.IO(_), _ => None)
case ShowCompletions(index, _) =>
envs.flatMap { envMap =>
val compWords = envMap.collect {
Expand All @@ -101,8 +100,8 @@ object CliApp {
.flatMap { completions =>
ZIO.foreachDiscard(completions)(word => printLine(word))
}
}.map(_ => None).mapError(CliError.BuiltIn(_))
case ShowWizard(command) => {
}.mapBoth(CliError.BuiltIn(_), _ => None)
case ShowWizard(command) =>
val fancyName = p(code(self.figFont.render(self.name)))
val header = p(text("WIZARD of ") + text(self.name) + text(self.version) + text(" -- ") + self.summary)
val explanation = p(s"Wizard mode assist you in constructing commands for $name$version")
Expand All @@ -112,10 +111,8 @@ object CliApp {
Wizard(command, config, fancyName + header + explanation).execute.mapError(CliError.BuiltIn(_))
output <- run(parameters)
} yield output).catchSome { case CliError.BuiltIn(_) =>
ZIO.succeed(None)
ZIO.none
}
}

}

// prepend a first argument in case the CliApp's command is expected to consume it
Expand All @@ -134,7 +131,7 @@ object CliApp {
e => printDocs(e.error) *> ZIO.fail(CliError.Parsing(e)),
{
case CommandDirective.UserDefined(_, value) =>
self.execute(value).map(Some(_)).mapError(CliError.Execution(_))
self.execute(value).mapBoth(CliError.Execution(_), Some(_))
case CommandDirective.BuiltIn(x) =>
executeBuiltIn(x).catchSome { case err @ CliError.Parsing(e) =>
printDocs(e.error) *> ZIO.fail(err)
Expand Down
63 changes: 29 additions & 34 deletions zio-cli/shared/src/main/scala/zio/cli/Command.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ object Command {
}

final case class Single[OptionsType, ArgsType](
val name: String,
name: String,
help: HelpDoc,
options: Options[OptionsType],
args: Args[ArgsType]
Expand Down Expand Up @@ -130,43 +130,38 @@ object Command {
ZIO.fail(
ValidationError(
ValidationErrorType.CommandMismatch,
HelpDoc.p(s"Missing command name: ${name}")
HelpDoc.p(s"Missing command name: $name")
)
)

val parseUserDefinedArgs =
for {
commandOptionsAndArgs <- args match {
case head :: tail =>
ZIO
.succeed(tail)
.when(conf.normalizeCase(head) == conf.normalizeCase(name))
.some
.orElseFail {
ValidationError(
ValidationErrorType.CommandMismatch,
HelpDoc.p(s"Missing command name: ${name}")
)
}
case Nil =>
ZIO.fail {
ValidationError(
ValidationErrorType.CommandMismatch,
HelpDoc.p(s"Missing command name: ${name}")
)
}
}
tuple1 = splitForcedArgs(commandOptionsAndArgs)
(optionsAndArgs, forcedCommandArgs) = tuple1
tuple2 <- Options.validate(options, optionsAndArgs, conf)
(error, commandArgs, optionsType) = tuple2
tuple <- self.args.validate(commandArgs ++ forcedCommandArgs, conf).catchSome { case e =>
error match {
case None => ZIO.fail(e)
case Some(err) => ZIO.fail(err)
}
}
(argsLeftover, argsType) = tuple
commandOptionsAndArgs <-
args match {
case head :: tail =>
ZIO
.succeed(tail)
.when(conf.normalizeCase(head) == conf.normalizeCase(name))
.someOrFail {
ValidationError(
ValidationErrorType.CommandMismatch,
HelpDoc.p(s"Missing command name: $name")
)
}
case Nil =>
ZIO.fail {
ValidationError(
ValidationErrorType.CommandMismatch,
HelpDoc.p(s"Missing command name: $name")
)
}
}
tuple1 = splitForcedArgs(commandOptionsAndArgs)
(optionsAndArgs, forcedCommandArgs) = tuple1
tuple2 <- Options.validate(options, optionsAndArgs, conf)
(optionsError, commandArgs, optionsType) = tuple2
tuple <- self.args.validate(commandArgs ++ forcedCommandArgs, conf).mapError(optionsError.getOrElse(_))
(argsLeftover, argsType) = tuple
} yield CommandDirective.userDefined(argsLeftover, (optionsType, argsType))

val exhaustiveSearch: IO[ValidationError, CommandDirective[(OptionsType, ArgsType)]] =
Expand All @@ -176,7 +171,7 @@ object Command {
ZIO.fail(
ValidationError(
ValidationErrorType.CommandMismatch,
HelpDoc.p(s"Missing command name: ${name}")
HelpDoc.p(s"Missing command name: $name")
)
)

Expand Down
89 changes: 37 additions & 52 deletions zio-cli/shared/src/main/scala/zio/cli/Options.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package zio.cli

import zio.cli.HelpDoc.Span._
import zio.cli.HelpDoc.p
import zio.{IO, ZIO, Zippable}
import zio.{IO, UIO, ZIO, Zippable}
import zio.cli.oauth2._

import java.time.{
Expand Down Expand Up @@ -43,12 +43,12 @@ sealed trait Options[+A] extends Parameter { self =>

def ??(that: String): Options[A] =
modifySingle(new SingleModifier {
override def apply[A](single: Single[A]): Single[A] = single.copy(description = single.description + p(that))
override def apply[A1](single: Single[A1]): Single[A1] = single.copy(description = single.description + p(that))
})

def alias(name: String, names: String*): Options[A] =
modifySingle(new SingleModifier {
override def apply[A](single: Single[A]): Single[A] = single.copy(aliases = single.aliases ++ (name +: names))
override def apply[A1](single: Single[A1]): Single[A1] = single.copy(aliases = single.aliases ++ (name +: names))
})

final def as[B, C, Z](f: (B, C) => Z)(implicit ev: A <:< (B, C)): Options[Z] =
Expand Down Expand Up @@ -209,31 +209,25 @@ object Options extends OptionsPlatformSpecific {
case head :: tail =>
head
.parse(input, conf)
.flatMap(parsed =>
parsed match {
case (Nil, input) =>
findOptions(input, tail, conf).map { case (otherArgs, otherOptions, map) =>
(otherArgs, head :: otherOptions, map)
}
case (name :: values, leftover) =>
ZIO.succeed((leftover, tail, Predef.Map(name -> values)))
}
)
.flatMap {
case (Nil, input) =>
findOptions(input, tail, conf).map { case (otherArgs, otherOptions, map) =>
(otherArgs, head :: otherOptions, map)
}
case (name :: values, leftover) =>
ZIO.succeed((leftover, tail, Predef.Map(name -> values)))
}
.catchSome { case e @ ValidationError(validationErrorType, _) =>
validationErrorType match {
case ValidationErrorType.UnclusteredFlag(list, tail) =>
matchUnclustered(list, tail, options, conf).catchAll { case _ =>
ZIO.fail(e)
}
matchUnclustered(list, tail, options, conf).orElseFail(e)
case ValidationErrorType.MissingFlag =>
findOptions(input, tail, conf).map { case (otherArgs, otherOptions, map) =>
(otherArgs, head :: otherOptions, map)
}
case ValidationErrorType.CorrectedFlag =>
for {
tuple <- findOptions(input, tail, conf).catchSome { case _ =>
ZIO.fail(e)
}
tuple <- findOptions(input, tail, conf).orElseFail(e)
(otherArgs, otherOptions, map) = tuple
_ <- ZIO.when(map.isEmpty)(ZIO.fail(e))
} yield (otherArgs, head :: otherOptions, map)
Expand Down Expand Up @@ -263,20 +257,20 @@ object Options extends OptionsPlatformSpecific {
}

// Sums the list associated with the same key.
@tailrec
private def merge(
map1: Predef.Map[String, List[String]],
map2: List[(String, List[String])]
): Predef.Map[String, List[String]] =
map2 match {
case Nil => map1
case head :: tail => {
case Nil => map1
case head :: tail =>
// replace with updatedWith for Scala 2.13
val newMap = map1.get(head._1) match {
case None => map1 + head
case Some(elem) => map1.updated(head._1, elem ++ head._2)
}
merge(newMap, tail)
}
}

/**
Expand All @@ -287,7 +281,7 @@ object Options extends OptionsPlatformSpecific {
input: List[String],
options: List[Options[_] with Input],
conf: CliConfig
): IO[Nothing, (Option[ValidationError], List[String], Predef.Map[String, List[String]])] =
): UIO[(Option[ValidationError], List[String], Predef.Map[String, List[String]])] =
(input, options) match {
case (Nil, _) => ZIO.succeed((None, Nil, Predef.Map.empty))
case (_, Nil) => ZIO.succeed((None, input, Predef.Map.empty))
Expand All @@ -296,7 +290,8 @@ object Options extends OptionsPlatformSpecific {
tuple1 <- findOptions(input, options, conf)
(otherArgs, otherOptions, map1) = tuple1
tuple2 <-
if (map1.isEmpty) ZIO.succeed((None, input, map1)) else matchOptions(otherArgs, otherOptions, conf)
if (map1.isEmpty) ZIO.succeed((None, input, map1))
else matchOptions(otherArgs, otherOptions, conf)
(error, otherArgs, map2) = tuple2
} yield (error, otherArgs, merge(map1, map2.toList)))
.catchAll(e => ZIO.succeed((Some(e), input, Predef.Map.empty)))
Expand All @@ -316,21 +311,15 @@ object Options extends OptionsPlatformSpecific {
for {
matched <- matchOptions(args, options.flatten, conf)
(error, commandArgs, matchedOptions) = matched
a <- options.validate(matchedOptions, conf).catchAll { case e =>
error match {
case Some(err) => ZIO.fail(err)
case None => ZIO.fail(e)
}
}
a <- options.validate(matchedOptions, conf).mapError(error.getOrElse(_))
} yield (error, commandArgs, a)

case object Empty extends Options[Unit] with Pipeline {
lazy val synopsis: UsageSynopsis = UsageSynopsis.None

override def flatten: List[Options[_] with Input] = Nil

override def validate(args: Predef.Map[String, List[String]], conf: CliConfig): IO[ValidationError, Unit] =
ZIO.succeed(())
override def validate(args: Predef.Map[String, List[String]], conf: CliConfig): IO[ValidationError, Unit] = ZIO.unit

override def modifySingle(f: SingleModifier): Options[Unit] = Empty

Expand Down Expand Up @@ -416,7 +405,7 @@ object Options extends OptionsPlatformSpecific {
} match {
case Nil =>
ZIO.fail(
ValidationError(ValidationErrorType.MissingValue, p(error(s"Expected to find ${fullName} option.")))
ValidationError(ValidationErrorType.MissingValue, p(error(s"Expected to find $fullName option.")))
)
case head :: Nil =>
head match {
Expand All @@ -434,7 +423,7 @@ object Options extends OptionsPlatformSpecific {
ZIO.fail(
ValidationError(
ValidationErrorType.InvalidValue,
p(error(s"""More than one reference to option ${fullName}."""))
p(error(s"""More than one reference to option $fullName."""))
)
)
}
Expand Down Expand Up @@ -466,19 +455,19 @@ object Options extends OptionsPlatformSpecific {
ZIO.fail(
ValidationError(
ValidationErrorType.CorrectedFlag,
p(error(s"""The flag "$head" is not recognized. Did you mean ${fullName}?"""))
p(error(s"""The flag "$head" is not recognized. Did you mean $fullName?"""))
)
)
else
ZIO.fail(
ValidationError(
ValidationErrorType.MissingFlag,
p(error(s"Expected to find ${fullName} option."))
p(error(s"Expected to find $fullName option."))
)
)
case Nil =>
ZIO.fail(
ValidationError(ValidationErrorType.MissingFlag, p(error(s"Expected to find ${fullName} option.")))
ValidationError(ValidationErrorType.MissingFlag, p(error(s"Expected to find $fullName option.")))
)
}

Expand Down Expand Up @@ -674,21 +663,17 @@ object Options extends OptionsPlatformSpecific {
argumentOption
.validate(args, conf)
.foldZIO(
err =>
err match {
case e @ ValidationError(errorType, _) =>
errorType match {
case ValidationErrorType.KeyValuesDetected(list) =>
ZIO
.foreach(list) { case keyValue =>
extractKeyValue(keyValue)
}
.map(_.toMap)
case _ =>
ZIO.fail(e)
}
},
keyValue => extractKeyValue(keyValue).map(List(_).toMap)
{ case e @ ValidationError(errorType, _) =>
errorType match {
case ValidationErrorType.KeyValuesDetected(list) =>
ZIO
.foreach(list)(extractKeyValue)
.map(_.toMap)
case _ =>
ZIO.fail(e)
}
},
extractKeyValue(_).map(List(_).toMap)
)
}

Expand Down
Loading