mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 04:50:36 +00:00
Implement stdio prompt function
This commit is contained in:
parent
bbf1c9f4f7
commit
ee64d2ef9c
8 changed files with 306 additions and 3 deletions
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -9,9 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- Added a new global `stdio` which replaces `console` and adds a couple new functions:
|
||||
- `write` writes a string directly to stdout, without any newlines
|
||||
- `ewrite` writes a string directly to stderr, without any newlines
|
||||
- Added a new global `stdio` which replaces `console`
|
||||
- Added `stdio.write` which writes a string directly to stdout, without any newlines
|
||||
- Added `stdio.ewrite` which writes a string directly to stderr, without any newlines
|
||||
- Added `stdio.prompt` which will prompt the user for different kinds of input
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local text = stdio.prompt()
|
||||
|
||||
local text2 = stdio.prompt("text", "Please write some text")
|
||||
|
||||
local didConfirm = stdio.prompt("confirm", "Please confirm this action")
|
||||
|
||||
local optionIndex = stdio.prompt("select", "Please select an option", { "one", "two", "three" })
|
||||
|
||||
local optionIndices = stdio.prompt(
|
||||
"multiselect",
|
||||
"Please select one or more options",
|
||||
{ "one", "two", "three", "four", "five" }
|
||||
)
|
||||
```
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -149,6 +149,18 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af3c796f3b0b408d9fd581611b47fa850821fcb84aa640b83a3c1a5be2d691f2"
|
||||
dependencies = [
|
||||
"console",
|
||||
"shell-words",
|
||||
"tempfile",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
|
@ -194,6 +206,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -426,6 +447,15 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.5"
|
||||
|
@ -544,6 +574,7 @@ version = "0.2.2"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"console",
|
||||
"dialoguer",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
"mlua",
|
||||
|
@ -787,6 +818,15 @@ version = "0.6.28"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.14"
|
||||
|
@ -962,6 +1002,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
|
@ -1028,6 +1074,20 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
|
@ -1421,3 +1481,9 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
|||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||
|
|
9
lune.yml
9
lune.yml
|
@ -86,6 +86,15 @@ globals:
|
|||
stdio.ewrite:
|
||||
args:
|
||||
- type: string
|
||||
stdio.prompt:
|
||||
must_use: true
|
||||
args:
|
||||
- required: false
|
||||
type: string
|
||||
- required: false
|
||||
type: string
|
||||
- required: false
|
||||
type: string | boolean | table
|
||||
# Task
|
||||
task.cancel:
|
||||
args:
|
||||
|
|
|
@ -387,6 +387,35 @@
|
|||
"@roblox/global/stdio.format/return/0": {
|
||||
"documentation": "The formatted string"
|
||||
},
|
||||
"@roblox/global/stdio.prompt": {
|
||||
"code_sample": "",
|
||||
"documentation": "Prompts for user input using the wanted kind of prompt:\n\n* `\"text\"` - Prompts for a plain text string from the user\n* `\"confirm\"` - Prompts the user to confirm with y / n\n* `\"select\"` - Prompts the user to select *one* value from a list\n* `\"multiselect\"` - Prompts the user to select *one or more* values from a list\n* `nil` - Equivalent to `\"text\"` with no extra arguments",
|
||||
"learn_more_link": "",
|
||||
"params": [
|
||||
{
|
||||
"documentation": "@roblox/global/stdio.prompt/param/0",
|
||||
"name": "kind"
|
||||
},
|
||||
{
|
||||
"documentation": "@roblox/global/stdio.prompt/param/1",
|
||||
"name": "message"
|
||||
},
|
||||
{
|
||||
"documentation": "@roblox/global/stdio.prompt/param/2",
|
||||
"name": "defaultOrOptions"
|
||||
}
|
||||
],
|
||||
"returns": []
|
||||
},
|
||||
"@roblox/global/stdio.prompt/param/0": {
|
||||
"documentation": "The kind of prompt to use"
|
||||
},
|
||||
"@roblox/global/stdio.prompt/param/1": {
|
||||
"documentation": "The message to show the user"
|
||||
},
|
||||
"@roblox/global/stdio.prompt/param/2": {
|
||||
"documentation": "The default value for the prompt, or options to choose from for selection prompts"
|
||||
},
|
||||
"@roblox/global/stdio.style": {
|
||||
"code_sample": "",
|
||||
"documentation": "Return an ANSI string that can be used to modify the persistent output style.\n\nPass `\"reset\"` to get a string that can reset the persistent output style.\n\n### Example usage\n\n```lua\nstdio.write(stdio.style(\"bold\"))\nprint(\"This text will be bold\")\nstdio.write(stdio.style(\"reset\"))\nprint(\"This text will be normal\")\n```",
|
||||
|
|
|
@ -351,6 +351,28 @@ declare stdio: {
|
|||
@param s The string to write to stderr
|
||||
]=]
|
||||
ewrite: (s: string) -> (),
|
||||
--[=[
|
||||
@within stdio
|
||||
|
||||
Prompts for user input using the wanted kind of prompt:
|
||||
|
||||
* `"text"` - Prompts for a plain text string from the user
|
||||
* `"confirm"` - Prompts the user to confirm with y / n
|
||||
* `"select"` - Prompts the user to select *one* value from a list
|
||||
* `"multiselect"` - Prompts the user to select *one or more* values from a list
|
||||
* `nil` - Equivalent to `"text"` with no extra arguments
|
||||
|
||||
@param kind The kind of prompt to use
|
||||
@param message The message to show the user
|
||||
@param defaultOrOptions The default value for the prompt, or options to choose from for selection prompts
|
||||
]=]
|
||||
prompt: (
|
||||
(() -> string)
|
||||
& ((kind: "text", message: string?, defaultOrOptions: string?) -> string)
|
||||
& ((kind: "confirm", message: string, defaultOrOptions: boolean?) -> boolean)
|
||||
& ((kind: "select", message: string?, defaultOrOptions: { string }) -> number?)
|
||||
& ((kind: "multiselect", message: string?, defaultOrOptions: { string }) -> { number }?)
|
||||
),
|
||||
}
|
||||
|
||||
--[=[
|
||||
|
|
|
@ -21,6 +21,7 @@ tokio.workspace = true
|
|||
reqwest.workspace = true
|
||||
|
||||
console = "0.15.5"
|
||||
dialoguer = "0.10.3"
|
||||
lazy_static = "1.4.0"
|
||||
os_str_bytes = "6.4.1"
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::utils::{
|
||||
|
@ -30,6 +31,125 @@ pub fn create(lua: &Lua) -> LuaResult<()> {
|
|||
eprint!("{s}");
|
||||
Ok(())
|
||||
})?
|
||||
.with_function("prompt", prompt)?
|
||||
.build_readonly()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn prompt_theme() -> ColorfulTheme {
|
||||
ColorfulTheme::default()
|
||||
}
|
||||
|
||||
fn prompt<'a>(
|
||||
lua: &'a Lua,
|
||||
(kind, message, options): (Option<String>, Option<String>, LuaValue<'a>),
|
||||
) -> LuaResult<LuaValue<'a>> {
|
||||
match kind.map(|k| k.trim().to_ascii_lowercase()).as_deref() {
|
||||
None | Some("text") => {
|
||||
let theme = prompt_theme();
|
||||
let mut prompt = Input::with_theme(&theme);
|
||||
if let Some(message) = message {
|
||||
prompt.with_prompt(message);
|
||||
};
|
||||
if let LuaValue::String(s) = options {
|
||||
let txt = String::from_lua(LuaValue::String(s), lua)?;
|
||||
prompt.with_initial_text(&txt);
|
||||
};
|
||||
let input: String = prompt.allow_empty(true).interact_text()?;
|
||||
Ok(LuaValue::String(lua.create_string(&input)?))
|
||||
}
|
||||
Some("confirm") => {
|
||||
if let Some(message) = message {
|
||||
let theme = prompt_theme();
|
||||
let mut prompt = Confirm::with_theme(&theme);
|
||||
if let LuaValue::Boolean(b) = options {
|
||||
prompt.default(b);
|
||||
};
|
||||
let result = prompt.with_prompt(&message).interact()?;
|
||||
Ok(LuaValue::Boolean(result))
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Argument #2 missing or nil".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
Some(s) if matches!(s, "select" | "multiselect") => {
|
||||
let options = match options {
|
||||
LuaValue::Table(t) => {
|
||||
let v: Vec<String> = Vec::from_lua(LuaValue::Table(t), lua)?;
|
||||
if v.len() < 2 {
|
||||
return Err(LuaError::RuntimeError(
|
||||
"Options table must contain at least 2 options".to_string(),
|
||||
));
|
||||
}
|
||||
v
|
||||
}
|
||||
LuaValue::Nil => {
|
||||
return Err(LuaError::RuntimeError(
|
||||
"Argument #3 missing or nil".to_string(),
|
||||
))
|
||||
}
|
||||
value => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Argument #3 must be a table, got '{}'",
|
||||
value.type_name()
|
||||
)))
|
||||
}
|
||||
};
|
||||
if let Some(message) = message {
|
||||
match s {
|
||||
"select" => {
|
||||
let chosen = Select::with_theme(&prompt_theme())
|
||||
.with_prompt(&message)
|
||||
.items(&options)
|
||||
.interact_opt()?;
|
||||
Ok(match chosen {
|
||||
Some(idx) => LuaValue::Number((idx + 1) as f64),
|
||||
None => LuaValue::Nil,
|
||||
})
|
||||
}
|
||||
"multiselect" => {
|
||||
let chosen = MultiSelect::with_theme(&prompt_theme())
|
||||
.with_prompt(&message)
|
||||
.items(&options)
|
||||
.interact_opt()?;
|
||||
Ok(match chosen {
|
||||
Some(indices) => indices
|
||||
.iter()
|
||||
.map(|idx| (*idx + 1) as f64)
|
||||
.collect::<Vec<_>>()
|
||||
.to_lua(lua)?,
|
||||
None => LuaValue::Nil,
|
||||
})
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
match s {
|
||||
"select" => {
|
||||
let chosen = Select::new().items(&options).interact_opt()?;
|
||||
Ok(match chosen {
|
||||
Some(idx) => LuaValue::Number((idx + 1) as f64),
|
||||
None => LuaValue::Nil,
|
||||
})
|
||||
}
|
||||
"multiselect" => {
|
||||
let chosen = MultiSelect::new().items(&options).interact_opt()?;
|
||||
Ok(match chosen {
|
||||
Some(indices) => indices
|
||||
.iter()
|
||||
.map(|idx| (*idx + 1) as f64)
|
||||
.collect::<Vec<_>>()
|
||||
.to_lua(lua)?,
|
||||
None => LuaValue::Nil,
|
||||
})
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(s) => Err(LuaError::RuntimeError(format!(
|
||||
"Invalid stdio prompt kind: '{s}'"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
|
37
tests/stdio/prompt.luau
Normal file
37
tests/stdio/prompt.luau
Normal file
|
@ -0,0 +1,37 @@
|
|||
-- NOTE: This test is intentionally not included in the
|
||||
-- automated tests suite since it requires user input
|
||||
|
||||
-- Text prompt
|
||||
|
||||
local text = stdio.prompt("text", "Type some text")
|
||||
assert(#text > 0, "Did not get any text")
|
||||
print(`Got text '{text}'\n`)
|
||||
|
||||
-- Confirmation prompt
|
||||
|
||||
local confirmed = stdio.prompt("confirm", "Please confirm", true)
|
||||
assert(confirmed == true, "Did not get true as result")
|
||||
print(if confirmed then "Confirmed\n" else "Did not confirm\n")
|
||||
|
||||
-- Selection prompt
|
||||
|
||||
local option = stdio.prompt(
|
||||
"select",
|
||||
"Please select the first option from the list",
|
||||
{ "one", "two", "three", "four" }
|
||||
)
|
||||
assert(option == 1, "Did not get the first option as result")
|
||||
print(if option then `Got option #{option}\n` else "Got no option\n")
|
||||
|
||||
-- Multi-selection prompt
|
||||
|
||||
local options = stdio.prompt(
|
||||
"multiselect",
|
||||
"Please select options two and four",
|
||||
{ "one", "two", "three", "four", "five" }
|
||||
)
|
||||
assert(
|
||||
options ~= nil and table.find(options, 2) and table.find(options, 4),
|
||||
"Did not get options 2 and 4 as result"
|
||||
)
|
||||
print(if options then `Got option(s) {stdio.format(options)}\n` else "Got no option\n")
|
Loading…
Reference in a new issue