Skip to content

Commit

Permalink
* Refactor shouldPrompt and default fields to take current value …
Browse files Browse the repository at this point in the history
…of the given field instead of the field object itself

* Handle multiple choices in `ListField`, and add `SingleValueListField` with the original functionality of `ListField`
* Add `StatefulField` interface which can be used in complex rendering functions (like `ListField`)
* Update demo with a multi-choice list example
* Update demo gif
* Update README
* Bump version to 0.2.0
  • Loading branch information
david-simon committed Oct 29, 2021
1 parent e05db2c commit 43c43de
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 175 deletions.
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies {
<dependency>
<groupId>xyz.davidsimon</groupId>
<artifactId>interakt</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</dependency>
```

Expand All @@ -36,7 +36,12 @@ Using a few convenience methods, we can achieve all this in just a few lines of
prompt {
text("foo:", default = "default value")
integer("bar:", default = 42)
list("baz:", listOf("1", "2", "3"), true)
singleList("baz:", listOf("1", "2", "3"), true)
list("multi:", listOf(
ListField.Choice("one", 1),
ListField.Choice("two", 2),
ListField.Choice("three", 3)
))

println("Result:")
for((key, value) in execute()) {
Expand All @@ -52,11 +57,14 @@ Base class for all other fields

**Options:**

| Parameter | Type | Description |
|---------------|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| promptMessage | `String` | Message to display before user input |
| shouldPrompt | `(PromptResult, PromptField<T>) -> Boolean` | Controls whether the field should be prompted. Receives the answers entered so far and the current field's instance |
| default | `(PromptResult, PromptField<T>) -> T?` | Provides a default value that will be pre-filled |
| Parameter | Type | Description |
|---------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------|
| promptMessage | `String` | Message to display before user input |
| shouldPrompt | `(PromptResult, T?) -> Boolean` | Controls whether the field should be prompted. Receives the answers entered so far and the current field's instance |
| default | `(PromptResult, T?) -> T?` | Provides a default value that will be pre-filled |

### `StatefulField`
Base class for fields that have a complex rendering function. Has event handlers that receive the current `RenderState`.

### `TextField`
Prompts user for simple text input
Expand All @@ -65,14 +73,17 @@ Prompts user for simple text input
Prompts user for integer input

### `ListField`
Prompts the user to choose from a list of items. The displayed name and the actual value of the items may differ.
Prompts the user to choose items from a list. The displayed name and the actual value of the items may differ.

**Options:**

| Parameter | Type | Description |
|-----------|-------------------------------------|-------------------------------|
| choices | `(PromptResult) -> List<Choice<T>>` | Returns the available choices |

### `SingleValueListField`
Prompts the user to choose **ONE** item from a list.

### `TextListField`
Prompts the user to choose from a list of text items. Optionally the user may enter a custom value.

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {

allprojects {
group = "xyz.davidsimon"
version = "0.1.0"
version = "0.2.0"
description = "An easy to use library to create interactive command line prompts"
}

Expand Down
19 changes: 10 additions & 9 deletions demo/src/main/kotlin/xyz/davidsimon/interakt/demo/Main.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
package xyz.davidsimon.interakt.demo

import kotlinx.coroutines.runBlocking
import xyz.davidsimon.interakt.field.integer
import xyz.davidsimon.interakt.field.list
import xyz.davidsimon.interakt.field.text
import xyz.davidsimon.interakt.field.*
import xyz.davidsimon.interakt.prompt

fun main() {
runBlocking {
prompt {
val text = text("foo:", default = "default value")
val integer = integer("bar:", default = 42)
val textList = list("baz:", listOf("1", "2", "3"), true)

val res = execute()
text("foo:", default = "default value")
integer("bar:", default = 42)
singleList("baz:", listOf("1", "2", "3"), true)
list("multi:", listOf(
ListField.Choice("one", 1),
ListField.Choice("two", 2),
ListField.Choice("three", 3)
))

println("Result:")
for((key, value) in res) {
for((key, value) in execute()) {
println("${key.promptMessage} $value")
}
}
Expand Down
16 changes: 8 additions & 8 deletions lib/src/main/kotlin/xyz/davidsimon/interakt/Prompt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import org.jline.keymap.BindingReader
import org.jline.reader.LineReaderBuilder
import org.jline.terminal.Terminal
import org.jline.terminal.TerminalBuilder
import xyz.davidsimon.interakt.field.*
import xyz.davidsimon.interakt.field.PromptField


fun <T> promptIfNull(): (PromptResult, PromptField<T>) -> Boolean {
return { pr: PromptResult, pf: PromptField<T> -> pr[pf] == null }
fun <T> promptIfNull(): (PromptResult, T) -> Boolean {
return { _: PromptResult, pf: T? -> pf == null }
}

fun <T> default(vararg defaults: T): (Any, Any) -> T? {
return { _: Any, _: Any -> defaults.find { it != null } }
fun <T> wrapDefault(vararg defaults: T): (PromptResult, T?) -> T? {
return { _: Any, _: T? -> defaults.find { it != null } }
}

val alwaysPrompt = { _: Any, _: Any -> true }
val defaultNull = { _: Any, _: Any -> null }
fun <T> alwaysPrompt() = { _: Any, _: T? -> true }
fun <T> defaultNull(): (PromptResult, T) -> T? = { _, _ -> null }

open class Prompt(
private val terminal: Terminal = TerminalBuilder.terminal(),
Expand Down Expand Up @@ -47,7 +47,7 @@ open class Prompt(
field as PromptField<Any>

val partialResults = PromptResult(results)
if (!field.shouldPrompt(partialResults, field)) continue
if (!field.shouldPrompt(partialResults, partialResults[field])) continue

results[field] = field.render(partialResults, terminal, lineReader, bindingReader, writer)
}
Expand Down
22 changes: 9 additions & 13 deletions lib/src/main/kotlin/xyz/davidsimon/interakt/field/IntegerField.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,18 @@ import xyz.davidsimon.interakt.util.formatPromptMessage
import java.io.PrintWriter

class IntegerField(
promptMessage: String,
shouldPrompt: (PromptResult, IntegerField) -> Boolean,
default: (PromptResult, IntegerField) -> Int?
) : PromptField<Int>(
promptMessage,
shouldPrompt as (PromptResult, PromptField<Int>) -> Boolean,
default as (PromptResult, PromptField<Int>) -> Int?
) {
override val promptMessage: String,
override val shouldPrompt: (PromptResult, Int?) -> Boolean,
override val default: (PromptResult, Int?) -> Int?
) : PromptField<Int> {
override suspend fun render(
pr: PromptResult,
terminal: Terminal,
lineReader: LineReader,
bindingReader: BindingReader,
writer: PrintWriter
): Int? {
var text: String? = default(pr, this)?.toString()
var text: String? = default(pr, pr[this])?.toString()
var retVal: Int?
var firstTry = true
val invalidNumberPrompt = AttributedStringBuilder()
Expand Down Expand Up @@ -63,12 +59,12 @@ class IntegerField(

fun Prompt.integer(
message: String,
shouldPrompt: ((PromptResult, IntegerField) -> Boolean) = promptIfNull(),
default: ((PromptResult, IntegerField) -> Int?) = defaultNull
shouldPrompt: ((PromptResult, Int?) -> Boolean) = promptIfNull(),
default: ((PromptResult, Int?) -> Int?) = defaultNull()
): IntegerField = addField(IntegerField(message, shouldPrompt, default))

fun Prompt.integer(
message: String,
shouldPrompt: ((PromptResult, IntegerField) -> Boolean) = promptIfNull(),
shouldPrompt: ((PromptResult, Int?) -> Boolean) = promptIfNull(),
default: Int? = null
): IntegerField = addField(IntegerField(message, shouldPrompt, default(default)))
): IntegerField = addField(IntegerField(message, shouldPrompt, wrapDefault(default)))
Loading

0 comments on commit 43c43de

Please sign in to comment.