Skip to content

Commit

Permalink
Add ClickCommand.printHelpOnEmptyArgs
Browse files Browse the repository at this point in the history
Fixes #41
  • Loading branch information
ajalt committed Jan 21, 2019
1 parent 0a1589a commit d538bf8
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 43 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

## [Unreleased]
### Added
- `printHelpOnEmptyArgs` parameter to `CliktCommand` constructor. ([#41](https://github.com/ajalt/clikt/issues/41))

### Fixed
- Usage errors now correctly print subcommand names. ([#47](https://github.com/ajalt/clikt/issues/47))
- Arguments with `multiple(required=true)` now report an error if no argument is given on the command line. ([#36](https://github.com/ajalt/clikt/issues/36))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ import kotlin.system.exitProcess
* name.
* @param invokeWithoutSubcommand Used when this command has subcommands, and this command is called
* without a subcommand. If true, [run] will be called. By default, a [PrintHelpMessage] is thrown instead.
* @param printHelpOnEmptyArgs If this command is called with no values on the command line, print a
* help message (by throwing [PrintHelpMessage]) if this is true, otherwise run normally.
*/
@Suppress("PropertyName")
abstract class CliktCommand(
help: String = "",
epilog: String = "",
name: String? = null,
val invokeWithoutSubcommand: Boolean = false) {
val invokeWithoutSubcommand: Boolean = false,
val printHelpOnEmptyArgs: Boolean = false) {
val commandName = name ?: javaClass.simpleName.split("$").last().toLowerCase()
val commandHelp = help
val commandHelpEpilog = epilog
Expand Down
27 changes: 15 additions & 12 deletions clikt/src/main/kotlin/com/github/ajalt/clikt/core/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,23 @@ import kotlin.reflect.KProperty
* options, the conflicting name will not be used for the help option. If the set is empty, or contains no
* unique names, no help option will be added.
* @property helpOptionMessage The description of the help option.
* @property helpFormatter The help formatter for this command
* @property helpFormatter The help formatter for this command.
* @property tokenTransformer An optional transformation function that is called to transform command line
* tokens (options and commands) before parsing. This can be used to implement e.g. case insensitive
* behavior.
* @property console The console to use to print messages.
*/
class Context(val parent: Context?,
val command: CliktCommand,
val allowInterspersedArgs: Boolean,
val autoEnvvarPrefix: String?,
val helpOptionNames: Set<String>,
val helpOptionMessage: String,
val helpFormatter: HelpFormatter,
val tokenTransformer: Context.(String) -> String,
val console: CliktConsole) {
class Context(
val parent: Context?,
val command: CliktCommand,
val allowInterspersedArgs: Boolean,
val autoEnvvarPrefix: String?,
val helpOptionNames: Set<String>,
val helpOptionMessage: String,
val helpFormatter: HelpFormatter,
val tokenTransformer: Context.(String) -> String,
val console: CliktConsole
) {
var invokedSubcommand: CliktCommand? = null
internal set
var obj: Any? = null
Expand Down Expand Up @@ -85,7 +88,7 @@ class Context(val parent: Context?,
var helpOptionMessage: String = parent?.helpOptionMessage ?: "Show this message and exit"
/** The help formatter for this command*/
var helpFormatter: HelpFormatter = parent?.helpFormatter ?: PlaintextHelpFormatter()
/** An optional transformation function that is called to transform command line*/
/** An optional transformation function that is called to transform command line */
var tokenTransformer: Context.(String) -> String = parent?.tokenTransformer ?: { it }
/**
* The prefix to add to inferred envvar names.
Expand All @@ -100,7 +103,7 @@ class Context(val parent: Context?,
/**
* The console that will handle reading and writing text.
*
* The default uses [System.in] and [System.out].
* The default uses [System. in] and [System.out].
*/
var console: CliktConsole = parent?.console ?: defaultCliktConsole()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ internal object Parser {
}
prefixes.remove("")

if (startingArgI > args.lastIndex && command.printHelpOnEmptyArgs) {
throw PrintHelpMessage(command)
}

val positionalArgs = ArrayList<String>()
var i = startingArgI
var subcommand: CliktCommand? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.github.ajalt.clikt.core
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.testing.NeverCalledCliktCommand
import com.github.ajalt.clikt.testing.splitArgv
import io.kotlintest.data.forall
import io.kotlintest.shouldBe
Expand Down Expand Up @@ -77,8 +78,15 @@ class CliktCommandTest {
}
}


@Test
fun `aliases`() = forall(
fun `printHelpOnEmptyArgs = true`() {
class C : NeverCalledCliktCommand(printHelpOnEmptyArgs = true)
shouldThrow<PrintHelpMessage> { C().parse(splitArgv("")) }
}

@Test
fun aliases() = forall(
row("-xx", "x", emptyList()),
row("a", "a", listOf("b")),
row("a", "a", listOf("b")),
Expand All @@ -105,4 +113,19 @@ class CliktCommandTest {

C().parse(splitArgv(argv))
}

@Test
fun `command usage`() {
class Parent : NeverCalledCliktCommand() {
val arg by argument()
}

shouldThrow<UsageError> {
Parent().parse(splitArgv(""))
}.helpMessage() shouldBe """
|Usage: parent [OPTIONS] ARG
|
|Error: Missing argument "ARG".
""".trimMargin()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.junit.Test

class TextExtensionsTest {
@Test
fun `wrapText`() = forall(
fun wrapText() = forall(
row("abc".wrapText(), "abc"),
row("abc\n".wrapText(), "abc"),
row("abc\n".wrapText(width = 2), "abc"),
Expand All @@ -28,7 +28,7 @@ class TextExtensionsTest {
}

@Test
fun `appendRepeat`() = forall(
fun appendRepeat() = forall(
row("a", 0, ""),
row("a", 1, "a"),
row("a", 2, "aa"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import org.junit.Test
@Suppress("unused")
class OptionTest {
@Test
fun `inferEnvvar`() = forall(
fun inferEnvvar() = forall(
row(setOf("--foo"), null, null, null),
row(setOf("--bar"), null, "FOO", "FOO_BAR"),
row(setOf("/bar"), null, "FOO", "FOO_BAR"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.arguments.pair
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.testing.NeverCalledCliktCommand
import com.github.ajalt.clikt.testing.splitArgv
import io.kotlintest.data.forall
import io.kotlintest.shouldBe
Expand All @@ -19,7 +20,7 @@ import org.junit.Test

class SubcommandTest {
@Test
fun `subcommand`() = forall(
fun subcommand() = forall(
row("--xx 2 sub --xx 3 --yy 4"),
row("--xx 2 sub -x 3 --yy 4"),
row("--xx 2 sub -x3 --yy 4"),
Expand Down Expand Up @@ -242,26 +243,11 @@ class SubcommandTest {
.parse(splitArgv(argv))
}

@Test
fun `command usage`() {
class Parent : NoRunCliktCommand() {
val arg by argument()
}

shouldThrow<UsageError> {
Parent().parse(splitArgv(""))
}.helpMessage() shouldBe """
|Usage: parent [OPTIONS] ARG
|
|Error: Missing argument "ARG".
""".trimMargin()
}

@Test
fun `subcommand usage`() {
class Parent : NoRunCliktCommand()
class Child : NoRunCliktCommand()
class Grandchild : NoRunCliktCommand() {
class Grandchild : NeverCalledCliktCommand() {
val arg by argument()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package com.github.ajalt.clikt.testing
import com.github.ajalt.clikt.core.CliktCommand
import io.kotlintest.fail

open class NeverCalledCliktCommand(help: String = "",
epilog: String = "",
name: String? = null,
invokeWithoutSubcommand: Boolean = false)
: CliktCommand(help, epilog, name, invokeWithoutSubcommand) {
open class NeverCalledCliktCommand(
help: String = "",
epilog: String = "",
name: String? = null,
invokeWithoutSubcommand: Boolean = false,
printHelpOnEmptyArgs: Boolean = false
) : CliktCommand(help, epilog, name, invokeWithoutSubcommand, printHelpOnEmptyArgs) {
override fun run() = fail("run should not be called")
}
36 changes: 32 additions & 4 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ an `init` block, or on an existing instance.

## Executing Nested Commands

For commands with no children, [`run`](api/clikt/com.github.ajalt.clikt.core/-clikt-command/run.html) is called whenever the
command line is parsed (unless parsing is aborted from an error or an
option like `--help`).
For commands with no children,
[`run`](api/clikt/com.github.ajalt.clikt.core/-clikt-command/run.html) is called whenever the
command line is parsed (unless parsing is aborted from an error or an option like `--help`).

If a command has children, this isn't the case. Instead, its `run` is
called only if a child command is invoked, just before the subcommand's
Expand Down Expand Up @@ -262,7 +262,7 @@ and
class Cli : NoRunCliktCommand()
fun main(args: Array<String>) = Cli()
.context { helpOptionMessage = "print the help" }
.main(splitArgv(""))
.main(args)
```

Any they work like:
Expand All @@ -274,3 +274,31 @@ Usage: cli [OPTIONS]
Options:
-h, --help print the help
```

## Printing the help message when no arguments are given

Normally, if a command is called with no values on the command line, a usage error is printed if
there are required parameters, or
[`run`](api/clikt/com.github.ajalt.clikt.core/-clikt-command/run.html) is called if there aren't
any.

You can change this behavior by passing `printHelpOnEmptyArgs = true` to your's command's
constructor. This will cause a help message to be printed when to values are provided on the command
line, regardless of the parameters in your command.

```kotlin
class Cli : CliktCommand() {
override fun run() { echo("Command ran") }
}
fun main(args: Array<String>) = Cli().main(args)
```

Which will print the help message, even without `--help`:

```
$ ./cli
Usage: cli [OPTIONS]
Options:
-h, --help print the help
```

0 comments on commit d538bf8

Please sign in to comment.