Skip to content

Commit

Permalink
Configurable synopsis (#255)
Browse files Browse the repository at this point in the history
* configurable synopsis

* fix

* changed expression

* default for helpdoc

* fix

* update docs
  • Loading branch information
pablf authored Aug 14, 2023
1 parent 567a3b5 commit 36d3ba6
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 12 deletions.
31 changes: 27 additions & 4 deletions docs/cli-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ You can construct directly a `CliConfig`:
```scala mdoc:silent
final case class CliConfig(
caseSensitive: Boolean,
autoCorrectLimit: Int
autoCorrectLimit: Int,
finalCheckBuiltIn: Boolean = true,
showAllNames: Boolean = true,
showTypes: Boolean = true
)
```

There are two parameters controlled by `CliConfig`: case sensitivity and autocorrection behaviour.
`CliConfig` allows to control case sensitivity, autocorrection behaviour, command processing and
help appearance.
### Case sensitivity
It is controlled by field `caseSensitive`. If it is `true`, then a `CliApp` will determine as distinct uppercase and lowercase versions of a letter in a command. On the other hand, `caseSensitive = false` implies that the `CliApp` will treat uppercase and lowercase letters as the same. In the Git example, we would have:

Expand Down Expand Up @@ -45,6 +49,23 @@ Expected to find --branch option.
```


## Command processing
`finalCheckBuiltIn` controls whether after an invalid command is entered by the user, there is a final check searching for a flag like `--help`, `-h` or `--wizard`. In this case, the corresponding Help or Wizard Mode of the parent command is triggered. Note that this can only trigger the parent command predefined option because the entered command is not valid, so it is an "emergency" check.

## Help appearance
`showAllNames` controls whether all the names of an option are shown in the usage synopsis of a command:
```
command (-o, --option text) # showAllNames = true
```
`showTypes` controls whether the type of the option is shown in the usage synopsis of a command.
```
command (-o, --option text) # showAllNames = true, showTypes = true
command --option text # showAllNames = false, showTypes = true
command (-o, --option ) # showAllNames = true, showTypes = false
command --option # showAllNames = false, showTypes = false
```


## Default configuration

The default configuration is given by
Expand All @@ -54,6 +75,8 @@ object CliConfig {
}
```
This means that a `CliApp` that does not specify any `CliConfig` and uses `CliConfig.default` will:
- ignore if the letters of a command are written in uppercase or lowercase and
- correct automatically up to two mistakes when writing the name of an option in a command of `CliApp`.
- ignore if the letters of a command are written in uppercase or lowercase,
- correct automatically up to two mistakes when writing the name of an option in a command of `CliApp`,
- trigger Help or Wizard Mode if the corresponding option is found after an invalid command was entered and
- show full usage synopsis of commands.

3 changes: 2 additions & 1 deletion zio-cli/shared/src/main/scala/zio/cli/CliApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ object CliApp {

val header = p(text(self.name) + text(self.version) + text(" -- ") + self.summary)

val synopsisHelpDoc = h1("usage") + synopsis.enumerate
val synopsisHelpDoc = h1("usage") + synopsis
.enumerate(config)
.map(span => text("$ ") + span)
.map(HelpDoc.p)
.foldRight(HelpDoc.empty)(_ + _)
Expand Down
12 changes: 10 additions & 2 deletions zio-cli/shared/src/main/scala/zio/cli/CliConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ import zio.{URIO, ZIO}
* @param caseSensitive
* Whether or not to be case sensitive.
* @param autoCorrectLimit
* Threshold for when to show auto correct suggestions
* Threshold for when to show auto correct suggestions.
* @param finalCheckBuiltIn
* Whether or not to check for a BuiltIn option even if it is not a valid command.
* @param showAllNames
* Whether or not to show all the names of an option in the synopsis of a command.
* @param showTypes
* Whether or not to show the type of an option in the synopsis of a command.
*/
final case class CliConfig(
caseSensitive: Boolean,
autoCorrectLimit: Int,
finalCheckBuiltIn: Boolean = true
finalCheckBuiltIn: Boolean = true,
showAllNames: Boolean = true,
showTypes: Boolean = true
) {
def normalizeCase(text: String): String = if (caseSensitive) text else text.toLowerCase()

Expand Down
24 changes: 19 additions & 5 deletions zio-cli/shared/src/main/scala/zio/cli/UsageSynopsis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import zio.cli.HelpDoc._
sealed trait UsageSynopsis { self =>
final def +(that: UsageSynopsis): UsageSynopsis = UsageSynopsis.Sequence(self, that)

final def enumerate: List[Span] = {
final def enumerate(config: CliConfig): List[Span] = {
import UsageSynopsis._

def simplify(g: UsageSynopsis): UsageSynopsis =
Expand Down Expand Up @@ -35,9 +35,23 @@ sealed trait UsageSynopsis { self =>
def render(g: UsageSynopsis): List[Span] =
g match {
case Named(names, acceptedValues) =>
val mainSpan =
Span.text(names.mkString(", ")) + acceptedValues.fold(Span.empty)(c => Span.space + Span.text(c))
if (names.length > 1) Span.text("(") + mainSpan + Span.text(")") :: Nil else mainSpan :: Nil
val typeInfo =
if (config.showTypes) acceptedValues.fold(Span.empty)(c => Span.space + Span.text(c))
else Span.empty
val namesToShow =
if (config.showAllNames) names
else if (names.length > 1)
names
.filter(_.startsWith("--"))
.headOption
.map(_ :: Nil)
.getOrElse(names)
else names
val nameInfo = Span.text(namesToShow.mkString(", "))
if (config.showAllNames && names.length > 1)
Span.text("(") + nameInfo + typeInfo + Span.text(")") :: Nil
else
nameInfo + typeInfo :: Nil

case Optional(value) =>
render(value).map(synopsis => Span.text("[") + synopsis + Span.text("]"))
Expand Down Expand Up @@ -82,7 +96,7 @@ sealed trait UsageSynopsis { self =>
render(simplify(self))
}

final def helpDoc: HelpDoc = enumerate match {
final def helpDoc: HelpDoc = enumerate(CliConfig.default) match {
case Nil => HelpDoc.empty
case head :: Nil => p(head)
case list => list.map(p).foldRight(HelpDoc.empty)(_ + _)
Expand Down

0 comments on commit 36d3ba6

Please sign in to comment.