remove async
This commit is contained in:
parent
4ef903934b
commit
6dd7007033
18
.github/workflows/async-std.yml
vendored
18
.github/workflows/async-std.yml
vendored
|
@ -1,18 +0,0 @@
|
|||
name: Async std
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run clippy
|
||||
run: cargo clippy --verbose --features=async-std --bins --examples
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --verbose --features=async-std
|
18
.github/workflows/smol.yml
vendored
18
.github/workflows/smol.yml
vendored
|
@ -1,18 +0,0 @@
|
|||
name: Smol
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run clippy
|
||||
run: cargo clippy --verbose --features=smol --bins --examples
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --verbose --features=smol
|
18
.github/workflows/tokio.yml
vendored
18
.github/workflows/tokio.yml
vendored
|
@ -1,18 +0,0 @@
|
|||
name: Tokio
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run clippy
|
||||
run: cargo clippy --verbose --features=tokio --bins --examples
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --verbose --features=tokio
|
25
Cargo.toml
25
Cargo.toml
|
@ -18,36 +18,13 @@ ui = { package = "inquisition-ui", path = "./inquisition-ui" }
|
|||
macros = { package = "inquisition-macros", path = "./inquisition-macros" }
|
||||
|
||||
ahash = { version = "0.7", optional = true }
|
||||
async-trait = { version = "0.1", optional = true }
|
||||
|
||||
# The following dependencies are renamed in the form `<dep-name>-dep` to allow
|
||||
# a feature name with the same name to be present. This is necessary since
|
||||
# these features are required to enable other dependencies
|
||||
smol-dep = { package = "smol", version = "1.2", optional = true }
|
||||
tokio-dep = { package = "tokio", version = "1.5", optional = true, features = ["fs", "process", "io-util"] }
|
||||
async-std-dep = { package = "async-std", version = "1.9", optional = true, features = ["unstable"] }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0.42"
|
||||
csscolorparser = "0.4" # examples/input.rs
|
||||
regex = "1.5" # examples/templates/pizza.rs
|
||||
regex = "1.5" # examples/prompt_module.rs
|
||||
|
||||
[features]
|
||||
default = ["crossterm", "ahash"]
|
||||
crossterm = ["ui/crossterm"]
|
||||
termion = ["ui/termion"]
|
||||
tokio = ["tokio-dep", "async-trait", "ui/tokio"]
|
||||
smol = ["smol-dep", "async-trait", "ui/smol"]
|
||||
async-std = ["async-std-dep", "async-trait", "ui/async-std"]
|
||||
|
||||
[[example]]
|
||||
name = "async-std"
|
||||
required-features = ["async-std"]
|
||||
|
||||
[[example]]
|
||||
name = "smol"
|
||||
required-features = ["smol"]
|
||||
|
||||
[[example]]
|
||||
name = "tokio"
|
||||
required-features = ["tokio"]
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
WIP rust clone of [Inquire.js](https://github.com/SBoudrias/Inquirer.js)
|
||||
|
||||
Minimum supported rust version (as per [cargo-msrv](https://crates.io/crates/cargo-msrv))
|
||||
is `1.46`
|
||||
is `1.43`
|
||||
|
|
|
@ -7,8 +7,7 @@ questions.
|
|||
|
||||
## Required features
|
||||
|
||||
Most of the examples are feature agnostic, except for `tokio`, `async-std`,
|
||||
and `smol` which require enabling the feature of the same name.
|
||||
All of the examples work with all features.
|
||||
|
||||
## Run examples
|
||||
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
include!("templates/pizza.rs");
|
||||
|
||||
fn main() -> inquisition::Result<()> {
|
||||
// It has to be called `async_std_dep` in this example due to implementation reasons.
|
||||
// When used outside this crate, just `async_std` will work.
|
||||
async_std::task::block_on(
|
||||
async {
|
||||
// There is no special async prompt, PromptModule itself can run both
|
||||
// synchronously and asynchronously
|
||||
let mut module = inquisition::PromptModule::new(pizza_questions());
|
||||
|
||||
// you can also prompt a single question, and get a mutable reference to its
|
||||
// answer
|
||||
if module.prompt_async().await?.unwrap().as_bool().unwrap() {
|
||||
println!("Delivery is guaranteed to be under 40 minutes");
|
||||
}
|
||||
|
||||
println!("{:#?}", module.prompt_all_async().await?);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
|
@ -1,12 +1,101 @@
|
|||
include!("templates/pizza.rs");
|
||||
use inquisition::{Choice, DefaultSeparator, Question, Separator};
|
||||
|
||||
fn main() -> inquisition::Result<()> {
|
||||
let phone_validator = regex::RegexBuilder::new(r"^([01]{1})?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})\s?((?:#|ext\.?\s?|x\.?\s?){1}(?:\d+)?)?$")
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let answers = inquisition::Answers::default();
|
||||
|
||||
// the prompt module can also be created with the `inquisition::prompt_module` macro
|
||||
let mut module = inquisition::PromptModule::new(pizza_questions())
|
||||
// you can use answers from before
|
||||
.with_answers(answers);
|
||||
let mut module = inquisition::PromptModule::new(vec![
|
||||
Question::confirm("to_be_delivered")
|
||||
.message("Is this for delivery?")
|
||||
.default(false)
|
||||
.build(),
|
||||
Question::input("phone")
|
||||
.message("What's your phone number?")
|
||||
.validate(move |value, _| {
|
||||
if phone_validator.is_match(value) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Please enter a valid phone number".into())
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
Question::select("size")
|
||||
.message("What size do you need?")
|
||||
.choice("Large")
|
||||
.choice("Medium")
|
||||
.choice("Small")
|
||||
.build(),
|
||||
Question::int("quantity")
|
||||
.message("How many do you need?")
|
||||
.validate(|ans, _| {
|
||||
if ans > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("You need to order at least one pizza".into())
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
Question::confirm("custom_toppings")
|
||||
.message("Do you want to customize the toppings?")
|
||||
.default(false)
|
||||
.build(),
|
||||
Question::expand("toppings")
|
||||
.message("What about the toppings?")
|
||||
.when(|answers: &inquisition::Answers| {
|
||||
!answers["custom_toppings"].as_bool().unwrap()
|
||||
})
|
||||
.choice('p', "Pepperoni and cheese")
|
||||
.choice('a', "All dressed")
|
||||
.choice('w', "Hawaiian")
|
||||
.build(),
|
||||
Question::checkbox("toppings")
|
||||
.message("Select toppings")
|
||||
.when(|answers: &inquisition::Answers| {
|
||||
answers["custom_toppings"].as_bool().unwrap()
|
||||
})
|
||||
.separator(" = The Meats = ")
|
||||
.choices(vec!["Pepperoni", "Ham", "Ground Meat", "Bacon"])
|
||||
.separator(" = The Cheeses = ")
|
||||
.choice_with_default("Mozzarella", true)
|
||||
.choice("Cheddar")
|
||||
.choices(vec![
|
||||
Choice("Parmesan".into()),
|
||||
Separator(" = The usual = ".into()),
|
||||
"Mushroom".into(),
|
||||
"Tomato".into(),
|
||||
Separator(" = The extras = ".into()),
|
||||
"Pineapple".into(),
|
||||
"Olives".into(),
|
||||
"Extra cheese".into(),
|
||||
DefaultSeparator,
|
||||
])
|
||||
.build(),
|
||||
Question::raw_select("beverage")
|
||||
.message("You also get a free 2L beverage")
|
||||
.choice("Pepsi")
|
||||
.choice("7up")
|
||||
.choice("Coke")
|
||||
.build(),
|
||||
Question::input("comments")
|
||||
.message("Any comments on your purchase experience?")
|
||||
.default("Nope, all good!")
|
||||
.build(),
|
||||
Question::select("prize")
|
||||
.message("For leaving a comment, you get a freebie")
|
||||
.choices(vec!["cake", "fries"])
|
||||
.when(|answers: &inquisition::Answers| {
|
||||
return answers["comments"].as_string().unwrap()
|
||||
!= "Nope, all good!";
|
||||
})
|
||||
.build(),
|
||||
])
|
||||
// you can use answers from before
|
||||
.with_answers(answers);
|
||||
|
||||
// you can also prompt a single question, and get a mutable reference to its answer
|
||||
if module.prompt()?.unwrap().as_bool().unwrap() {
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
include!("templates/pizza.rs");
|
||||
|
||||
fn main() -> inquisition::Result<()> {
|
||||
// It has to be called `smol_dep` in this example due to implementation reasons.
|
||||
// When used outside this crate, just `smol` will work.
|
||||
smol_dep::block_on(
|
||||
async {
|
||||
// There is no special async prompt, PromptModule itself can run both
|
||||
// synchronously and asynchronously
|
||||
let mut module = inquisition::PromptModule::new(pizza_questions());
|
||||
|
||||
// you can also prompt a single question, and get a mutable reference to its
|
||||
// answer
|
||||
if module.prompt_async().await?.unwrap().as_bool().unwrap() {
|
||||
println!("Delivery is guaranteed to be under 40 minutes");
|
||||
}
|
||||
|
||||
println!("{:#?}", module.prompt_all_async().await?);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
use inquisition::{Choice, DefaultSeparator, Question, Separator};
|
||||
|
||||
fn pizza_questions() -> Vec<Question<'static, 'static, 'static, 'static, 'static>> {
|
||||
let phone_validator = regex::RegexBuilder::new(r"^([01]{1})?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})\s?((?:#|ext\.?\s?|x\.?\s?){1}(?:\d+)?)?$")
|
||||
.case_insensitive(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
vec![
|
||||
Question::confirm("to_be_delivered")
|
||||
.message("Is this for delivery?")
|
||||
.default(false)
|
||||
.build(),
|
||||
Question::input("phone")
|
||||
.message("What's your phone number?")
|
||||
.validate(move |value, _| {
|
||||
if phone_validator.is_match(value) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Please enter a valid phone number".into())
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
Question::select("size")
|
||||
.message("What size do you need?")
|
||||
.choice("Large")
|
||||
.choice("Medium")
|
||||
.choice("Small")
|
||||
.build(),
|
||||
Question::int("quantity")
|
||||
.message("How many do you need?")
|
||||
.validate(|ans, _| {
|
||||
if ans > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("You need to order at least one pizza".into())
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
Question::confirm("custom_toppings")
|
||||
.message("Do you want to customize the toppings?")
|
||||
.default(false)
|
||||
.build(),
|
||||
Question::expand("toppings")
|
||||
.message("What about the toppings?")
|
||||
.when(|answers: &inquisition::Answers| {
|
||||
!answers["custom_toppings"].as_bool().unwrap()
|
||||
})
|
||||
.choice('p', "Pepperoni and cheese")
|
||||
.choice('a', "All dressed")
|
||||
.choice('w', "Hawaiian")
|
||||
.build(),
|
||||
Question::checkbox("toppings")
|
||||
.message("Select toppings")
|
||||
.when(|answers: &inquisition::Answers| {
|
||||
answers["custom_toppings"].as_bool().unwrap()
|
||||
})
|
||||
.separator(" = The Meats = ")
|
||||
.choices(vec!["Pepperoni", "Ham", "Ground Meat", "Bacon"])
|
||||
.separator(" = The Cheeses = ")
|
||||
.choice_with_default("Mozzarella", true)
|
||||
.choice("Cheddar")
|
||||
.choices(vec![
|
||||
Choice("Parmesan".into()),
|
||||
Separator(" = The usual = ".into()),
|
||||
"Mushroom".into(),
|
||||
"Tomato".into(),
|
||||
Separator(" = The extras = ".into()),
|
||||
"Pineapple".into(),
|
||||
"Olives".into(),
|
||||
"Extra cheese".into(),
|
||||
DefaultSeparator,
|
||||
])
|
||||
.build(),
|
||||
Question::raw_select("beverage")
|
||||
.message("You also get a free 2L beverage")
|
||||
.choice("Pepsi")
|
||||
.choice("7up")
|
||||
.choice("Coke")
|
||||
.build(),
|
||||
Question::input("comments")
|
||||
.message("Any comments on your purchase experience?")
|
||||
.default("Nope, all good!")
|
||||
.build(),
|
||||
Question::select("prize")
|
||||
.message("For leaving a comment, you get a freebie")
|
||||
.choices(vec!["cake", "fries"])
|
||||
.when(|answers: &inquisition::Answers| {
|
||||
return answers["comments"].as_string().unwrap()
|
||||
!= "Nope, all good!";
|
||||
})
|
||||
.build(),
|
||||
]
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
include!("templates/pizza.rs");
|
||||
|
||||
// It has to be called `tokio_dep` in this example due to implementation reasons. When
|
||||
// used outside this crate, just `tokio` will work.
|
||||
#[tokio_dep::main]
|
||||
async fn main() -> inquisition::Result<()> {
|
||||
// There is no special async prompt, PromptModule itself can run both synchronously
|
||||
// and asynchronously
|
||||
let mut module = inquisition::PromptModule::new(pizza_questions());
|
||||
|
||||
// you can also prompt a single question, and get a mutable reference to its answer
|
||||
if module.prompt_async().await?.unwrap().as_bool().unwrap() {
|
||||
println!("Delivery is guaranteed to be under 40 minutes");
|
||||
}
|
||||
|
||||
println!("{:#?}", module.prompt_all_async().await?);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -141,9 +141,6 @@ pub(crate) struct QuestionOpts {
|
|||
pub(crate) validate: Option<syn::Expr>,
|
||||
pub(crate) filter: Option<syn::Expr>,
|
||||
pub(crate) transform: Option<syn::Expr>,
|
||||
pub(crate) validate_async: Option<syn::Expr>,
|
||||
pub(crate) filter_async: Option<syn::Expr>,
|
||||
pub(crate) transform_async: Option<syn::Expr>,
|
||||
|
||||
pub(crate) choices: Option<Choices>,
|
||||
pub(crate) page_size: Option<syn::Expr>,
|
||||
|
@ -167,9 +164,6 @@ impl Default for QuestionOpts {
|
|||
validate: None,
|
||||
filter: None,
|
||||
transform: None,
|
||||
validate_async: None,
|
||||
filter_async: None,
|
||||
transform_async: None,
|
||||
|
||||
choices: None,
|
||||
page_size: None,
|
||||
|
@ -194,14 +188,11 @@ fn check_disallowed(
|
|||
(ident == "default" &&
|
||||
!allowed.contains(BuilderMethods::DEFAULT)) ||
|
||||
|
||||
((ident == "transform_async" ||
|
||||
ident == "transform") &&
|
||||
((ident == "transform") &&
|
||||
!allowed.contains(BuilderMethods::TRANSFORM)) ||
|
||||
|
||||
((ident == "validate_async" ||
|
||||
ident == "validate" ||
|
||||
ident == "filter" ||
|
||||
ident == "filter_async") &&
|
||||
((ident == "validate" ||
|
||||
ident == "filter") &&
|
||||
!allowed.contains(BuilderMethods::VAL_FIL)) ||
|
||||
|
||||
((ident == "choices" ||
|
||||
|
@ -247,94 +238,64 @@ impl Parse for Question {
|
|||
let allowed_methods = kind.get_builder_methods();
|
||||
|
||||
while !content.is_empty() {
|
||||
if content.peek(Token![async]) {
|
||||
let asynct = content.parse::<Token![async]>()?;
|
||||
let ident = content.parse::<syn::Ident>()?;
|
||||
let ident = content.parse::<syn::Ident>()?;
|
||||
|
||||
content.parse::<Token![:]>()?;
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
let full_ident_str = format!("{}_async", ident);
|
||||
let full_ident = syn::Ident::new(
|
||||
&full_ident_str,
|
||||
asynct
|
||||
.span
|
||||
.join(ident.span())
|
||||
.unwrap_or_else(|| ident.span()),
|
||||
);
|
||||
// it is not an issue if ident doesn't correspond to valid option
|
||||
// since check_allowed only checks if valid idents are disallowed
|
||||
check_disallowed(&ident, kind, allowed_methods)?;
|
||||
|
||||
check_disallowed(&full_ident, kind, allowed_methods)?;
|
||||
if ident == "validate" {
|
||||
insert_non_dup(full_ident, &mut opts.validate_async, &content)?;
|
||||
} else if ident == "filter" {
|
||||
insert_non_dup(full_ident, &mut opts.filter_async, &content)?;
|
||||
} else if ident == "transform" {
|
||||
insert_non_dup(full_ident, &mut opts.transform_async, &content)?;
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
ident.span(),
|
||||
format!("unknown question option `{}`", full_ident_str),
|
||||
));
|
||||
}
|
||||
// default options which are always there
|
||||
if ident == "name" {
|
||||
insert_non_dup(ident, &mut name, &content)?;
|
||||
} else if ident == "message" {
|
||||
insert_non_dup(ident, &mut opts.message, &content)?;
|
||||
} else if ident == "when" {
|
||||
insert_non_dup(ident, &mut opts.when, &content)?;
|
||||
} else if ident == "ask_if_answered" {
|
||||
insert_non_dup(ident, &mut opts.ask_if_answered, &content)?;
|
||||
} else {
|
||||
let ident = content.parse::<syn::Ident>()?;
|
||||
|
||||
content.parse::<Token![:]>()?;
|
||||
|
||||
// the rest may or may not be there, so must be checked
|
||||
// it is not an issue if ident doesn't correspond to valid option
|
||||
// since check_allowed only checks if valid idents are disallowed
|
||||
check_disallowed(&ident, kind, allowed_methods)?;
|
||||
|
||||
// default options which are always there
|
||||
if ident == "name" {
|
||||
insert_non_dup(ident, &mut name, &content)?;
|
||||
} else if ident == "message" {
|
||||
insert_non_dup(ident, &mut opts.message, &content)?;
|
||||
} else if ident == "when" {
|
||||
insert_non_dup(ident, &mut opts.when, &content)?;
|
||||
} else if ident == "ask_if_answered" {
|
||||
insert_non_dup(ident, &mut opts.ask_if_answered, &content)?;
|
||||
if ident == "default" {
|
||||
insert_non_dup(ident, &mut opts.default, &content)?;
|
||||
} else if ident == "validate" {
|
||||
insert_non_dup(ident, &mut opts.validate, &content)?;
|
||||
} else if ident == "filter" {
|
||||
insert_non_dup(ident, &mut opts.filter, &content)?;
|
||||
} else if ident == "transform" {
|
||||
insert_non_dup(ident, &mut opts.transform, &content)?;
|
||||
} else if ident == "choices" {
|
||||
let parser = match kind {
|
||||
QuestionKind::Checkbox => Choices::parse_checkbox_choice,
|
||||
_ => Choices::parse_choice,
|
||||
};
|
||||
|
||||
insert_non_dup_parse(
|
||||
ident,
|
||||
&mut opts.choices,
|
||||
&content,
|
||||
parser,
|
||||
)?;
|
||||
} else if ident == "page_size" {
|
||||
insert_non_dup(ident, &mut opts.page_size, &content)?;
|
||||
} else if ident == "should_loop" {
|
||||
insert_non_dup(ident, &mut opts.should_loop, &content)?;
|
||||
} else if ident == "mask" {
|
||||
insert_non_dup(ident, &mut opts.mask, &content)?;
|
||||
} else if ident == "extension" {
|
||||
insert_non_dup(ident, &mut opts.extension, &content)?;
|
||||
} else if ident == "plugin" {
|
||||
insert_non_dup(ident, &mut opts.plugin, &content)?;
|
||||
} else {
|
||||
// the rest may or may not be there, so must be checked
|
||||
// it is not an issue if ident doesn't correspond to valid option
|
||||
// since check_allowed only checks if valid idents are disallowed
|
||||
check_disallowed(&ident, kind, allowed_methods)?;
|
||||
|
||||
if ident == "default" {
|
||||
insert_non_dup(ident, &mut opts.default, &content)?;
|
||||
} else if ident == "validate" {
|
||||
insert_non_dup(ident, &mut opts.validate, &content)?;
|
||||
} else if ident == "filter" {
|
||||
insert_non_dup(ident, &mut opts.filter, &content)?;
|
||||
} else if ident == "transform" {
|
||||
insert_non_dup(ident, &mut opts.transform, &content)?;
|
||||
} else if ident == "choices" {
|
||||
let parser = match kind {
|
||||
QuestionKind::Checkbox => Choices::parse_checkbox_choice,
|
||||
_ => Choices::parse_choice,
|
||||
};
|
||||
|
||||
insert_non_dup_parse(
|
||||
ident,
|
||||
&mut opts.choices,
|
||||
&content,
|
||||
parser,
|
||||
)?;
|
||||
} else if ident == "page_size" {
|
||||
insert_non_dup(ident, &mut opts.page_size, &content)?;
|
||||
} else if ident == "should_loop" {
|
||||
insert_non_dup(ident, &mut opts.should_loop, &content)?;
|
||||
} else if ident == "mask" {
|
||||
insert_non_dup(ident, &mut opts.mask, &content)?;
|
||||
} else if ident == "extension" {
|
||||
insert_non_dup(ident, &mut opts.extension, &content)?;
|
||||
} else if ident == "plugin" {
|
||||
insert_non_dup(ident, &mut opts.plugin, &content)?;
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
ident.span(),
|
||||
format!("unknown question option `{}`", ident),
|
||||
));
|
||||
}
|
||||
return Err(syn::Error::new(
|
||||
ident.span(),
|
||||
format!("unknown question option `{}`", ident),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -413,29 +374,14 @@ impl quote::ToTokens for Question {
|
|||
tokens
|
||||
.extend(quote_spanned! { validate.span() => .validate(#validate) });
|
||||
}
|
||||
if let Some(ref validate_async) = self.opts.validate_async {
|
||||
tokens.extend(quote_spanned! {
|
||||
validate_async.span() => .validate_async(#validate_async)
|
||||
});
|
||||
}
|
||||
if let Some(ref filter) = self.opts.filter {
|
||||
tokens.extend(quote_spanned! { filter.span() => .filter(#filter) });
|
||||
}
|
||||
if let Some(ref filter_async) = self.opts.filter_async {
|
||||
tokens.extend(quote_spanned! {
|
||||
filter_async.span() => .filter_async(#filter_async)
|
||||
});
|
||||
}
|
||||
if let Some(ref transform) = self.opts.transform {
|
||||
tokens.extend(
|
||||
quote_spanned! { transform.span() => .transform(#transform) },
|
||||
);
|
||||
}
|
||||
if let Some(ref transform_async) = self.opts.transform_async {
|
||||
tokens.extend(quote_spanned! {
|
||||
transform_async.span() => .transform_async(#transform_async)
|
||||
});
|
||||
}
|
||||
if let Some(ref choices) = self.opts.choices {
|
||||
tokens.extend(match self.kind {
|
||||
QuestionKind::Checkbox => {
|
||||
|
|
|
@ -9,21 +9,5 @@ bitflags = "1.2"
|
|||
textwrap = "0.13"
|
||||
unicode-segmentation = "1.7"
|
||||
|
||||
async-trait = { version = "0.1", optional = true }
|
||||
futures = { version = "0.3", optional = true }
|
||||
|
||||
crossterm = { version = "0.19", optional = true }
|
||||
termion = { version = "1.5", optional = true }
|
||||
|
||||
# The following dependencies are renamed in the form `<dep-name>-dep` to allow
|
||||
# a feature name with the same name to be present. This is necessary since
|
||||
# these features are required to enable other dependencies
|
||||
smol-dep = { package = "smol", version = "1.2", optional = true }
|
||||
tokio-dep = { package = "tokio", version = "1.5", optional = true, features = ["fs", "rt"] }
|
||||
async-std-dep = { package = "async-std", version = "1.9", optional = true, features = ["unstable"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
tokio = ["tokio-dep", "async-trait", "futures"]
|
||||
smol = ["smol-dep", "async-trait", "futures"]
|
||||
async-std = ["async-std-dep", "async-trait", "futures"]
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
|
||||
use super::{Input, Prompt, Validation};
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
error,
|
||||
events::{AsyncEvents, KeyCode, KeyModifiers},
|
||||
};
|
||||
|
||||
/// This trait should be implemented by all 'root' widgets.
|
||||
///
|
||||
/// It provides the functionality specifically required only by the main controlling widget. For the
|
||||
/// trait required for general rendering to terminal, see [`Widget`].
|
||||
#[async_trait]
|
||||
pub trait AsyncPrompt: Prompt {
|
||||
/// Try to validate the prompt state is valid synchronously without blocking. If it can't be
|
||||
/// done without blocking, return `None` and [`validate_async`](AsyncPrompt::validate_async)
|
||||
/// will be called instead. It is called whenever the use presses the enter key.
|
||||
fn try_validate_sync(
|
||||
&mut self,
|
||||
) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Determine whether the prompt state is ready to be submitted. It is called whenever the use
|
||||
/// presses the enter key.
|
||||
#[allow(unused_variables)]
|
||||
async fn validate_async(&mut self) -> Result<Validation, Self::ValidateErr> {
|
||||
Ok(Validation::Finish)
|
||||
}
|
||||
|
||||
/// The value to return from [`Input::run_async`]. This will only be called once validation returns
|
||||
/// [`Validation::Finish`];
|
||||
async fn finish_async(self) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<P: AsyncPrompt + Send, B: Backend + Unpin> Input<P, B> {
|
||||
#[inline]
|
||||
async fn finish_async(
|
||||
mut self,
|
||||
pressed_enter: bool,
|
||||
prompt_len: u16,
|
||||
) -> error::Result<P::Output> {
|
||||
self.clear(prompt_len)?;
|
||||
self.backend.reset()?;
|
||||
|
||||
if pressed_enter {
|
||||
Ok(self.prompt.finish_async().await)
|
||||
} else {
|
||||
Ok(self.prompt.finish_default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the ui on the given writer. It will return when the user presses `Enter` or `Escape`
|
||||
/// based on the [`AsyncPrompt`] implementation.
|
||||
pub async fn run_async(
|
||||
mut self,
|
||||
events: &mut AsyncEvents,
|
||||
) -> error::Result<P::Output> {
|
||||
let prompt_len = self.init()?;
|
||||
|
||||
loop {
|
||||
let e = events.next().await.unwrap()?;
|
||||
|
||||
let key_handled = match e.code {
|
||||
KeyCode::Char('c')
|
||||
if e.modifiers.contains(KeyModifiers::CONTROL) =>
|
||||
{
|
||||
self.exit()?;
|
||||
return Err(error::ErrorKind::Interrupted);
|
||||
}
|
||||
KeyCode::Null => {
|
||||
self.exit()?;
|
||||
return Err(error::ErrorKind::Eof);
|
||||
}
|
||||
KeyCode::Esc if self.prompt.has_default() => {
|
||||
return self.finish_async(false, prompt_len).await;
|
||||
}
|
||||
|
||||
KeyCode::Enter => {
|
||||
let result = match self.prompt.try_validate_sync() {
|
||||
Some(res) => res,
|
||||
None => self.prompt.validate_async().await,
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(Validation::Finish) => {
|
||||
return self.finish_async(true, prompt_len).await;
|
||||
}
|
||||
Ok(Validation::Continue) => true,
|
||||
Err(e) => {
|
||||
self.print_error(e)?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => self.prompt.handle_key(e),
|
||||
};
|
||||
|
||||
if key_handled {
|
||||
self.size = self.backend.size()?;
|
||||
|
||||
self.render()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
#[cfg(feature = "async-std")]
|
||||
use async_std_dep::task::spawn_blocking;
|
||||
#[cfg(feature = "smol")]
|
||||
use smol_dep::unblock as spawn_blocking;
|
||||
#[cfg(feature = "tokio")]
|
||||
use tokio_dep::task::spawn_blocking;
|
||||
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{mpsc, Arc},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::{task::AtomicWaker, Stream};
|
||||
|
||||
use crate::{error, events};
|
||||
|
||||
type Receiver = mpsc::Receiver<error::Result<events::KeyEvent>>;
|
||||
|
||||
pub struct AsyncEvents {
|
||||
events: Receiver,
|
||||
waker: Arc<AtomicWaker>,
|
||||
}
|
||||
|
||||
impl AsyncEvents {
|
||||
pub async fn new() -> error::Result<Self> {
|
||||
let res = spawn_blocking(|| {
|
||||
let (tx, rx) = mpsc::sync_channel(16);
|
||||
let waker = Arc::new(AtomicWaker::new());
|
||||
let events = AsyncEvents {
|
||||
events: rx,
|
||||
waker: Arc::clone(&waker),
|
||||
};
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let events = super::Events::new();
|
||||
|
||||
for event in events {
|
||||
if tx.send(event).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
waker.wake();
|
||||
}
|
||||
});
|
||||
|
||||
events
|
||||
})
|
||||
.await;
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
return res.map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"failed to spawn event thread",
|
||||
)
|
||||
.into()
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "tokio"))]
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for AsyncEvents {
|
||||
type Item = error::Result<events::KeyEvent>;
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
match self.events.try_recv() {
|
||||
Ok(e) => Poll::Ready(Some(e)),
|
||||
Err(mpsc::TryRecvError::Empty) => {
|
||||
self.waker.register(cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
Err(mpsc::TryRecvError::Disconnected) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,6 @@ use std::io::{stdin, Stdin};
|
|||
|
||||
use crate::error;
|
||||
|
||||
crate::cfg_async! {
|
||||
mod async_events;
|
||||
pub use async_events::AsyncEvents;
|
||||
}
|
||||
|
||||
#[cfg(feature = "crossterm")]
|
||||
mod crossterm;
|
||||
#[cfg(feature = "termion")]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! A widget based cli ui rendering library
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub use sync_input::{Input, Prompt};
|
||||
pub use input::{Input, Prompt};
|
||||
pub use widget::Widget;
|
||||
|
||||
/// In build widgets
|
||||
|
@ -20,18 +20,13 @@ pub mod widgets {
|
|||
}
|
||||
}
|
||||
|
||||
cfg_async! {
|
||||
pub use async_input::AsyncPrompt;
|
||||
mod async_input;
|
||||
}
|
||||
|
||||
pub mod backend;
|
||||
mod char_input;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
mod input;
|
||||
mod list;
|
||||
mod string_input;
|
||||
mod sync_input;
|
||||
mod text;
|
||||
mod widget;
|
||||
|
||||
|
@ -194,14 +189,3 @@ impl<B: backend::Backend> DerefMut for TerminalState<B> {
|
|||
&mut self.backend
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! cfg_async {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(any(feature = "tokio", feature = "async-std", feature = "smol"))]
|
||||
$item
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
|
72
src/lib.rs
72
src/lib.rs
|
@ -23,9 +23,6 @@ pub mod plugin {
|
|||
backend::{Attributes, Backend, Color, Styled, Stylize},
|
||||
events::Events,
|
||||
};
|
||||
crate::cfg_async! {
|
||||
pub use ui::events::AsyncEvents;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -95,48 +92,6 @@ where
|
|||
Ok(self.answers)
|
||||
}
|
||||
|
||||
cfg_async! {
|
||||
async fn prompt_impl_async<B: backend::Backend>(
|
||||
&mut self,
|
||||
stdout: &mut B,
|
||||
events: &mut events::AsyncEvents,
|
||||
) -> error::Result<Option<&mut Answer>> {
|
||||
while let Some(question) = self.questions.next() {
|
||||
if let Some((name, answer)) = question.ask_async(&self.answers, stdout, events).await? {
|
||||
return Ok(Some(self.answers.insert(name, answer)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn prompt_async(&mut self) -> error::Result<Option<&mut Answer>> {
|
||||
if atty::isnt(atty::Stream::Stdout) {
|
||||
return Err(error::ErrorKind::NotATty);
|
||||
}
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = backend::get_backend(stdout.lock())?;
|
||||
|
||||
self.prompt_impl_async(&mut stdout, &mut events::AsyncEvents::new().await?).await
|
||||
}
|
||||
|
||||
pub async fn prompt_all_async(mut self) -> error::Result<Answers> {
|
||||
self.answers.reserve(self.questions.size_hint().0);
|
||||
|
||||
if atty::isnt(atty::Stream::Stdout) {
|
||||
return Err(error::ErrorKind::NotATty);
|
||||
}
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = backend::get_backend(stdout.lock())?;
|
||||
|
||||
let mut events = events::AsyncEvents::new().await?;
|
||||
|
||||
while self.prompt_impl_async(&mut stdout, &mut events).await?.is_some() {}
|
||||
|
||||
Ok(self.answers)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_answers(self) -> Answers {
|
||||
self.answers
|
||||
}
|
||||
|
@ -155,30 +110,3 @@ pub fn prompt_one<'m, 'w, 'f, 'v, 't, I: Into<Question<'m, 'w, 'f, 'v, 't>>>(
|
|||
let ans = prompt(std::iter::once(question.into()))?;
|
||||
Ok(ans.into_iter().next().unwrap().1)
|
||||
}
|
||||
|
||||
cfg_async! {
|
||||
pub async fn prompt_async<'m, 'w, 'f, 'v, 't, Q>(questions: Q) -> error::Result<Answers>
|
||||
where
|
||||
Q: IntoIterator<Item = Question<'m, 'w, 'f, 'v, 't>>,
|
||||
{
|
||||
PromptModule::new(questions).prompt_all_async().await
|
||||
}
|
||||
|
||||
pub async fn prompt_one_async<'m, 'w, 'f, 'v, 't, I: Into<Question<'m, 'w, 'f, 'v, 't>>>(
|
||||
question: I,
|
||||
) -> error::Result<Answer> {
|
||||
let ans = prompt_async(std::iter::once(question.into())).await?;
|
||||
Ok(ans.into_iter().next().unwrap().1)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! cfg_async {
|
||||
($($item:item)*) => {
|
||||
$(
|
||||
#[cfg(any(feature = "tokio", feature = "async-std", feature = "smol"))]
|
||||
$item
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
|
|
@ -83,44 +83,6 @@ impl Prompt for CheckboxPrompt<'_, '_, '_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for CheckboxPrompt<'_, '_, '_, '_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
let Checkbox {
|
||||
mut selected,
|
||||
choices,
|
||||
filter,
|
||||
..
|
||||
} = self.picker.finish();
|
||||
|
||||
selected = match filter {
|
||||
Filter::Async(filter) => filter(selected, self.answers).await,
|
||||
Filter::Sync(filter) => filter(selected, self.answers),
|
||||
Filter::None => selected,
|
||||
};
|
||||
|
||||
create_list_items(selected, choices)
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
match self.picker.list.validate {
|
||||
Validate::Sync(ref validate) => {
|
||||
Some(validate(&self.picker.list.selected, self.answers).map(|_| Validation::Finish))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_async(&mut self) -> Result<Validation, Self::ValidateErr> {
|
||||
if let Validate::Async(ref validate) = self.picker.list.validate {
|
||||
validate(&self.picker.list.selected, self.answers).await?;
|
||||
}
|
||||
Ok(Validation::Finish)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for CheckboxPrompt<'_, '_, '_, '_> {
|
||||
fn render<B: Backend>(
|
||||
&mut self,
|
||||
|
@ -261,48 +223,6 @@ impl Checkbox<'_, '_, '_> {
|
|||
|
||||
Ok(Answer::ListItems(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let transform = self.transform.take();
|
||||
|
||||
let ans = ui::Input::new(
|
||||
CheckboxPrompt {
|
||||
message,
|
||||
picker: widgets::ListPicker::new(self),
|
||||
answers,
|
||||
},
|
||||
b,
|
||||
)
|
||||
.hide_cursor()
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(&ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(&ans, answers, b)?,
|
||||
_ => {
|
||||
b.set_fg(Color::Cyan)?;
|
||||
print_comma_separated(
|
||||
ans.iter().map(|item| item.name.lines().next().unwrap()),
|
||||
b,
|
||||
)?;
|
||||
b.set_fg(Color::Reset)?;
|
||||
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Answer::ListItems(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CheckboxBuilder<'m, 'w, 'f, 'v, 't> {
|
||||
|
|
|
@ -89,19 +89,6 @@ impl Prompt for ConfirmPrompt<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for ConfirmPrompt<'_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
self.finish()
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
Some(self.validate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Confirm<'_> {
|
||||
pub(crate) fn ask<B: Backend>(
|
||||
mut self,
|
||||
|
@ -134,39 +121,6 @@ impl Confirm<'_> {
|
|||
|
||||
Ok(Answer::Bool(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let transform = self.transform.take();
|
||||
|
||||
let ans = ui::Input::new(ConfirmPrompt {
|
||||
confirm: self,
|
||||
message,
|
||||
input: widgets::CharInput::new(only_yn),
|
||||
}, b)
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(ans, answers, b)?,
|
||||
_ => {
|
||||
let ans = if ans { "Yes" } else { "No" };
|
||||
b.write_styled(ans.cyan())?;
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Answer::Bool(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConfirmBuilder<'m, 'w, 't> {
|
||||
|
|
|
@ -8,25 +8,6 @@ use std::{
|
|||
|
||||
use tempfile::TempPath;
|
||||
|
||||
#[cfg(feature = "async-std")]
|
||||
use async_std_dep::{
|
||||
fs::File as AsyncFile,
|
||||
io::prelude::{ReadExt, SeekExt, WriteExt},
|
||||
process::Command as AsyncCommand,
|
||||
};
|
||||
#[cfg(feature = "smol")]
|
||||
use smol_dep::{
|
||||
fs::File as AsyncFile,
|
||||
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
|
||||
process::Command as AsyncCommand,
|
||||
};
|
||||
#[cfg(feature = "tokio")]
|
||||
use tokio_dep::{
|
||||
fs::File as AsyncFile,
|
||||
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
|
||||
process::Command as AsyncCommand,
|
||||
};
|
||||
|
||||
use ui::{
|
||||
backend::{Backend, Stylize},
|
||||
error, Validation, Widget,
|
||||
|
@ -137,88 +118,6 @@ impl ui::Prompt for EditorPrompt<'_, '_, '_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
struct EditorPromptAsync<'f, 'v, 't, 'a> {
|
||||
file: AsyncFile,
|
||||
path: TempPath,
|
||||
ans: String,
|
||||
message: String,
|
||||
editor: Editor<'f, 'v, 't>,
|
||||
answers: &'a Answers,
|
||||
}
|
||||
|
||||
impl Widget for EditorPromptAsync<'_, '_, '_, '_> {
|
||||
fn render<B: Backend>(&mut self, _: ui::Layout, _: &mut B) -> error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn height(&mut self, _: ui::Layout) -> u16 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl ui::Prompt for EditorPromptAsync<'_, '_, '_, '_> {
|
||||
type ValidateErr = io::Error;
|
||||
type Output = String;
|
||||
|
||||
fn prompt(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
|
||||
fn hint(&self) -> Option<&str> {
|
||||
Some("Press <enter> to launch your preferred editor.")
|
||||
}
|
||||
|
||||
fn finish(self) -> Self::Output {
|
||||
unimplemented!("EditorPromptAsync should only be called through async api");
|
||||
}
|
||||
|
||||
fn has_default(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for EditorPromptAsync<'_, '_, '_, '_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
match self.editor.filter {
|
||||
Filter::Async(filter) => filter(self.ans, self.answers).await,
|
||||
Filter::Sync(filter) => filter(self.ans, self.answers),
|
||||
Filter::None => self.ans,
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_async(&mut self) -> Result<Validation, Self::ValidateErr> {
|
||||
if !AsyncCommand::new(&self.editor.editor)
|
||||
.arg(&self.path)
|
||||
.status()
|
||||
.await?
|
||||
.success()
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Could not open editor",
|
||||
));
|
||||
}
|
||||
|
||||
self.ans.clear();
|
||||
self.file.read_to_string(&mut self.ans).await?;
|
||||
self.file.seek(SeekFrom::Start(0)).await?;
|
||||
|
||||
match self.editor.validate {
|
||||
Validate::Async(ref validate) => validate(&self.ans, self.answers).await
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?,
|
||||
Validate::Sync(ref validate) => validate(&self.ans, self.answers)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?,
|
||||
Validate::None => {}
|
||||
|
||||
}
|
||||
|
||||
Ok(Validation::Finish)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor<'_, '_, '_> {
|
||||
pub(crate) fn ask<B: Backend>(
|
||||
mut self,
|
||||
|
@ -269,56 +168,6 @@ impl Editor<'_, '_, '_> {
|
|||
|
||||
Ok(Answer::String(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let mut builder = tempfile::Builder::new();
|
||||
|
||||
if let Some(ref extension) = self.extension {
|
||||
builder.suffix(extension);
|
||||
}
|
||||
|
||||
let (file, path) = builder.tempfile()?.into_parts();
|
||||
let mut file = AsyncFile::from(file);
|
||||
|
||||
if let Some(ref default) = self.default {
|
||||
file.write_all(default.as_bytes()).await?;
|
||||
file.seek(SeekFrom::Start(0)).await?;
|
||||
file.flush().await?;
|
||||
}
|
||||
|
||||
let transform = self.transform.take();
|
||||
|
||||
let ans = ui::Input::new(EditorPromptAsync {
|
||||
message,
|
||||
editor: self,
|
||||
file,
|
||||
path,
|
||||
ans: String::new(),
|
||||
answers,
|
||||
}, b)
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(&ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(&ans, answers, b)?,
|
||||
Transform::None => {
|
||||
b.write_styled("Received".dark_grey())?;
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(Answer::String(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorBuilder<'m, 'w, 'f, 'v, 't> {
|
||||
|
|
|
@ -117,19 +117,6 @@ impl<F: Fn(char) -> Option<char>> Prompt for ExpandPrompt<'_, F> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl<F: Fn(char) -> Option<char> + Send + Sync> ui::AsyncPrompt for ExpandPrompt<'_, F> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
self.finish()
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
Some(self.validate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ANSWER_PROMPT: &[u8] = b" Answer: ";
|
||||
|
||||
impl<F: Fn(char) -> Option<char>> ui::Widget for ExpandPrompt<'_, F> {
|
||||
|
@ -357,48 +344,6 @@ impl Expand<'_> {
|
|||
|
||||
Ok(Answer::ExpandItem(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let (choices, hint) = self.get_choices_and_hint();
|
||||
let transform = self.transform.take();
|
||||
|
||||
let ans = ui::Input::new(ExpandPrompt {
|
||||
message,
|
||||
input: widgets::CharInput::new(|c| {
|
||||
let c = c.to_ascii_lowercase();
|
||||
if choices.contains(c) {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
list: widgets::ListPicker::new(self),
|
||||
hint,
|
||||
expanded: false,
|
||||
}, b)
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(&ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(&ans, answers, b)?,
|
||||
_ => {
|
||||
b.write_styled(ans.name.lines().next().unwrap().cyan())?;
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Answer::ExpandItem(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExpandBuilder<'m, 'w, 't> {
|
||||
|
|
|
@ -93,50 +93,6 @@ impl Prompt for InputPrompt<'_, '_, '_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for InputPrompt<'_, '_, '_, '_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
let hint = self.input_opts.default;
|
||||
let ans = self
|
||||
.input
|
||||
.finish()
|
||||
.unwrap_or_else(|| remove_brackets(hint.unwrap()));
|
||||
|
||||
match self.input_opts.filter {
|
||||
Filter::Sync(filter) => filter(ans, self.answers),
|
||||
Filter::Async(filter) => filter(ans, self.answers).await,
|
||||
Filter::None => ans,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
if !self.input.has_value() {
|
||||
if self.has_default() {
|
||||
return Some(Ok(Validation::Finish));
|
||||
} else {
|
||||
return Some(Err("Please enter a string".into()));
|
||||
}
|
||||
}
|
||||
|
||||
match self.input_opts.validate {
|
||||
Validate::Sync(ref validate) => {
|
||||
Some(validate(self.input.value(), self.answers).map(|_| Validation::Finish))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_async(&mut self) -> Result<Validation, Self::ValidateErr> {
|
||||
if let Validate::Async(ref validate) = self.input_opts.validate {
|
||||
validate(self.input.value(), self.answers).await?;
|
||||
}
|
||||
|
||||
Ok(Validation::Finish)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Input<'_, '_, '_> {
|
||||
pub(crate) fn ask<B: Backend>(
|
||||
mut self,
|
||||
|
@ -174,44 +130,6 @@ impl Input<'_, '_, '_> {
|
|||
|
||||
Ok(Answer::String(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
if let Some(ref mut default) = self.default {
|
||||
default.insert(0, '(');
|
||||
default.push(')');
|
||||
}
|
||||
|
||||
let transform = self.transform.take();
|
||||
|
||||
let ans = ui::Input::new(InputPrompt {
|
||||
message,
|
||||
input_opts: self,
|
||||
input: widgets::StringInput::default(),
|
||||
answers,
|
||||
}, b)
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(&ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(&ans, answers, b)?,
|
||||
_ => {
|
||||
b.write_styled(ans.as_str().cyan())?;
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Answer::String(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputBuilder<'m, 'w, 'f, 'v, 't> {
|
||||
|
|
|
@ -30,7 +30,7 @@ use options::Options;
|
|||
pub use plugin::Plugin;
|
||||
use ui::{backend::Backend, error};
|
||||
|
||||
use std::{fmt, future::Future, pin::Pin};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Question<'m, 'w, 'f, 'v, 't> {
|
||||
|
@ -172,53 +172,11 @@ impl Question<'_, '_, '_, '_, '_> {
|
|||
|
||||
Ok(Some((name, res)))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Option<(String, Answer)>> {
|
||||
if (!self.opts.ask_if_answered && answers.contains_key(&self.opts.name))
|
||||
|| !self.opts.when.get(answers)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let name = self.opts.name;
|
||||
let message = self
|
||||
.opts
|
||||
.message
|
||||
.map(|message| message.get(answers))
|
||||
.unwrap_or_else(|| name.clone() + ":");
|
||||
|
||||
let res = match self.kind {
|
||||
QuestionKind::Input(i) => i.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Int(i) => i.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Float(f) => f.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Confirm(c) => c.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Select(l) => l.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::RawSelect(r) => r.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Expand(e) => e.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Checkbox(c) => c.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Password(p) => p.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Editor(e) => e.ask_async(message, answers, b, events).await?,
|
||||
QuestionKind::Plugin(ref mut o) => o.ask_async(message, answers, b, events).await?,
|
||||
};
|
||||
|
||||
Ok(Some((name, res)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type BoxFuture<'a, T> =
|
||||
Pin<Box<dyn Future<Output = T> + Send + Sync + 'a>>;
|
||||
|
||||
macro_rules! handler {
|
||||
($name:ident, $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
|
||||
pub(crate) enum $name<'a, T> {
|
||||
Async(Box<dyn $fn_trait( $($type),* ) -> BoxFuture<'a, $return> + Send + Sync + 'a>),
|
||||
Sync(Box<dyn $fn_trait( $($type),* ) -> $return + Send + Sync + 'a>),
|
||||
None,
|
||||
}
|
||||
|
@ -239,7 +197,6 @@ macro_rules! handler {
|
|||
impl<T: fmt::Debug> fmt::Debug for $name<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Async(_) => f.write_str("Async(_)"),
|
||||
Self::Sync(_) => f.write_str("Sync(_)"),
|
||||
Self::None => f.write_str("None"),
|
||||
}
|
||||
|
@ -250,7 +207,6 @@ macro_rules! handler {
|
|||
// The type signature of the function must only contain &T
|
||||
($name:ident, ?Sized $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
|
||||
pub(crate) enum $name<'a, T: ?Sized> {
|
||||
Async(Box<dyn $fn_trait( $($type),* ) -> BoxFuture<'a, $return> + Send + Sync + 'a>),
|
||||
Sync(Box<dyn $fn_trait( $($type),* ) -> $return + Send + Sync + 'a>),
|
||||
None,
|
||||
}
|
||||
|
@ -271,7 +227,6 @@ macro_rules! handler {
|
|||
impl<T: fmt::Debug + ?Sized> fmt::Debug for $name<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Async(_) => f.write_str("Async(_)"),
|
||||
Self::Sync(_) => f.write_str("Sync(_)"),
|
||||
Self::None => f.write_str("None"),
|
||||
}
|
||||
|
@ -303,15 +258,6 @@ macro_rules! impl_filter_builder {
|
|||
let $filter = crate::question::Filter::Sync(Box::new(filter));
|
||||
$body
|
||||
}
|
||||
|
||||
pub fn filter_async<'a, F>(self, filter: F) -> $ty<$($pre_lifetime),*, 'a, $($post_lifetime),*>
|
||||
where
|
||||
F: FnOnce($t, &crate::Answers) -> std::pin::Pin<Box<dyn std::future::Future<Output = $t> + Send + Sync + 'a>> + Send + Sync + 'a,
|
||||
{
|
||||
let $self = self;
|
||||
let $filter = crate::question::Filter::Async(Box::new(filter));
|
||||
$body
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -330,15 +276,6 @@ macro_rules! impl_validate_builder {
|
|||
let $validate = crate::question::Validate::Sync(Box::new(validate));
|
||||
$body
|
||||
}
|
||||
|
||||
pub fn validate_async<'a, F>(self, validate: F) -> $ty<$($pre_lifetime),*, 'a, $($post_lifetime),*>
|
||||
where
|
||||
F: Fn(&$t, &crate::Answers) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), String>> + Send + Sync + 'a>> + Send + Sync + 'a,
|
||||
{
|
||||
let $self = self;
|
||||
let $validate = crate::question::Validate::Async(Box::new(validate));
|
||||
$body
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -353,15 +290,6 @@ macro_rules! impl_validate_builder {
|
|||
let $validate = crate::question::ValidateByVal::Sync(Box::new(validate));
|
||||
$body
|
||||
}
|
||||
|
||||
pub fn validate_async<'a, F>(self, validate: F) -> $ty<$($pre_lifetime),*, 'a, $($post_lifetime),*>
|
||||
where
|
||||
F: Fn($t, &crate::Answers) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), String>> + Send + Sync + 'a>> + Send + Sync + 'a,
|
||||
{
|
||||
let $self = self;
|
||||
let $validate = crate::question::ValidateByVal::Async(Box::new(validate));
|
||||
$body
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -380,15 +308,6 @@ macro_rules! impl_transform_builder {
|
|||
let $transform = crate::question::Transform::Sync(Box::new(transform));
|
||||
$body
|
||||
}
|
||||
|
||||
pub fn transform_async<'a, F>(self, transform: F) -> $ty<$($pre_lifetime),*, 'a, $($post_lifetime),*>
|
||||
where
|
||||
F: FnOnce(&$t, &crate::Answers, &mut dyn Backend) -> std::pin::Pin<Box<dyn std::future::Future<Output = ui::error::Result<()>> + Send + Sync + 'a>> + Send + Sync + 'a,
|
||||
{
|
||||
let $self = self;
|
||||
let $transform = crate::question::Transform::Async(Box::new(transform));
|
||||
$body
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -403,15 +322,6 @@ macro_rules! impl_transform_builder {
|
|||
let $transform = crate::question::TransformByVal::Sync(Box::new(transform));
|
||||
$body
|
||||
}
|
||||
|
||||
pub fn transform_async<'a, F>(self, transform: F) -> $ty<$($pre_lifetime),*, 'a, $($post_lifetime),*>
|
||||
where
|
||||
F: FnOnce($t, &crate::Answers, &mut dyn Backend) -> std::pin::Pin<Box<dyn std::future::Future<Output = ui::error::Result<()>> + Send + Sync + 'a>> + Send + Sync + 'a,
|
||||
{
|
||||
let $self = self;
|
||||
let $transform = crate::question::TransformByVal::Async(Box::new(transform));
|
||||
$body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,10 @@ macro_rules! impl_number_prompt {
|
|||
|
||||
impl $prompt_name<'_, '_, '_, '_> {
|
||||
fn parse(&self) -> Result<$inner_ty, String> {
|
||||
self.input.value().parse::<$inner_ty>().map_err(|e| e.to_string())
|
||||
self.input
|
||||
.value()
|
||||
.parse::<$inner_ty>()
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,51 +151,6 @@ macro_rules! impl_number_prompt {
|
|||
self.number.default.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for $prompt_name<'_, '_, '_, '_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
if self.input.value().is_empty() && self.has_default() {
|
||||
return self.number.default.unwrap();
|
||||
}
|
||||
|
||||
let n = self.parse().unwrap();
|
||||
match self.number.filter {
|
||||
Filter::Async(filter) => filter(n, self.answers).await,
|
||||
Filter::Sync(filter) => filter(n, self.answers),
|
||||
Filter::None => n,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
if self.input.value().is_empty() && self.has_default() {
|
||||
return Some(Ok(Validation::Finish));
|
||||
}
|
||||
|
||||
let n = match self.parse() {
|
||||
Ok(n) => n,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
|
||||
|
||||
match self.number.validate {
|
||||
Validate::Sync(ref validate) => {
|
||||
Some(validate(n, self.answers).map(|_| Validation::Finish))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_async(&mut self) -> Result<Validation, Self::ValidateErr> {
|
||||
if let Validate::Async(ref validate) = self.number.validate {
|
||||
validate(self.parse().unwrap(), self.answers).await?;
|
||||
}
|
||||
|
||||
Ok(Validation::Finish)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -230,36 +188,6 @@ macro_rules! impl_ask {
|
|||
|
||||
Ok(Answer::$t(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let transform = self.transform.take();
|
||||
|
||||
let ans = ui::Input::new($prompt_name {
|
||||
hint: self.default.map(|default| format!("({})", default)),
|
||||
input: widgets::StringInput::new(Self::filter_map_char),
|
||||
number: self,
|
||||
message,
|
||||
answers,
|
||||
}, b)
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(ans, answers, b)?,
|
||||
_ => Self::write(ans, b)?,
|
||||
}
|
||||
|
||||
Ok(Answer::$t(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -62,38 +62,6 @@ impl ui::Prompt for PasswordPrompt<'_, '_, '_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for PasswordPrompt<'_, '_, '_, '_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
let ans = self.input.finish().unwrap_or_else(String::new);
|
||||
|
||||
match self.password.filter {
|
||||
Filter::Async(filter) => filter(ans, self.answers).await,
|
||||
Filter::Sync(filter) => filter(ans, self.answers),
|
||||
Filter::None => ans,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
match self.password.validate {
|
||||
Validate::Sync(ref validate) => {
|
||||
Some(validate(self.input.value(), self.answers).map(|_| Validation::Finish))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_async(&mut self) -> Result<Validation, Self::ValidateErr> {
|
||||
if let Validate::Async(ref validate) = self.password.validate {
|
||||
validate(self.input.value(), self.answers).await?;
|
||||
}
|
||||
|
||||
Ok(Validation::Finish)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for PasswordPrompt<'_, '_, '_, '_> {
|
||||
fn render<B: Backend>(
|
||||
&mut self,
|
||||
|
@ -148,39 +116,6 @@ impl Password<'_, '_, '_> {
|
|||
|
||||
Ok(Answer::String(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let transform = self.transform.take();
|
||||
|
||||
let ans = ui::Input::new(PasswordPrompt {
|
||||
message,
|
||||
input: widgets::StringInput::default().password(self.mask),
|
||||
password: self,
|
||||
answers,
|
||||
}, b)
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(&ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(&ans, answers, b)?,
|
||||
_ => {
|
||||
b.write_styled("[hidden]".dark_grey())?;
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Answer::String(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PasswordBuilder<'m, 'w, 'f, 'v, 't> {
|
||||
|
|
|
@ -11,16 +11,6 @@ pub trait Plugin: std::fmt::Debug {
|
|||
stdout: &mut dyn Backend,
|
||||
events: &mut events::Events,
|
||||
) -> error::Result<Answer>;
|
||||
|
||||
crate::cfg_async! {
|
||||
fn ask_async<'future>(
|
||||
&mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
stdout: &mut dyn Backend,
|
||||
events: &mut events::AsyncEvents,
|
||||
) -> crate::question::BoxFuture<'future, error::Result<Answer>>;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PluginBuilder<'m, 'w, 'p> {
|
||||
|
|
|
@ -74,19 +74,6 @@ impl Prompt for RawSelectPrompt<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for RawSelectPrompt<'_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
self.finish()
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<Validation, Self::ValidateErr>> {
|
||||
Some(self.validate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ANSWER_PROMPT: &[u8] = b" Answer: ";
|
||||
|
||||
impl Widget for RawSelectPrompt<'_> {
|
||||
|
@ -245,49 +232,6 @@ impl RawSelect<'_> {
|
|||
|
||||
Ok(Answer::ListItem(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let transform = self.transform.take();
|
||||
|
||||
let mut list = widgets::ListPicker::new(self);
|
||||
if let Some(default) = list.list.choices.default() {
|
||||
list.set_at(default);
|
||||
}
|
||||
|
||||
let ans = ui::Input::new(RawSelectPrompt {
|
||||
input: widgets::StringInput::new(|c| {
|
||||
if c.is_digit(10) {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
list,
|
||||
message,
|
||||
}, b)
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(&ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(&ans, answers, b)?,
|
||||
_ => {
|
||||
b.write_styled(ans.name.lines().next().unwrap().cyan())?;
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Answer::ListItem(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawSelectBuilder<'m, 'w, 't> {
|
||||
|
|
|
@ -62,19 +62,6 @@ impl Prompt for SelectPrompt<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
#[async_trait::async_trait]
|
||||
impl ui::AsyncPrompt for SelectPrompt<'_> {
|
||||
async fn finish_async(self) -> Self::Output {
|
||||
self.finish()
|
||||
}
|
||||
|
||||
fn try_validate_sync(&mut self) -> Option<Result<ui::Validation, Self::ValidateErr>> {
|
||||
Some(self.validate())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for SelectPrompt<'_> {
|
||||
fn render<B: Backend>(
|
||||
&mut self,
|
||||
|
@ -169,38 +156,6 @@ impl Select<'_> {
|
|||
|
||||
Ok(Answer::ListItem(ans))
|
||||
}
|
||||
|
||||
crate::cfg_async! {
|
||||
pub(crate) async fn ask_async<B: Backend>(
|
||||
mut self,
|
||||
message: String,
|
||||
answers: &Answers,
|
||||
b: &mut B,
|
||||
events: &mut ui::events::AsyncEvents,
|
||||
) -> error::Result<Answer> {
|
||||
let transform = self.transform.take();
|
||||
let mut picker = widgets::ListPicker::new(self);
|
||||
if let Some(default) = picker.list.choices.default() {
|
||||
picker.set_at(default);
|
||||
}
|
||||
let ans = ui::Input::new(SelectPrompt { message, picker }, b)
|
||||
.hide_cursor()
|
||||
.run_async(events)
|
||||
.await?;
|
||||
|
||||
match transform {
|
||||
Transform::Async(transform) => transform(&ans, answers, b).await?,
|
||||
Transform::Sync(transform) => transform(&ans, answers, b)?,
|
||||
_ => {
|
||||
b.write_styled(ans.name.lines().next().unwrap().cyan())?;
|
||||
b.write_all(b"\n")?;
|
||||
b.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Answer::ListItem(ans))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SelectBuilder<'m, 'w, 't> {
|
||||
|
|
|
@ -33,9 +33,6 @@ fn duplicate() {
|
|||
t.compile_fail("validate");
|
||||
t.compile_fail("filter");
|
||||
t.compile_fail("transform");
|
||||
t.compile_fail("validate_async");
|
||||
t.compile_fail("filter_async");
|
||||
t.compile_fail("transform_async");
|
||||
t.compile_fail("choices");
|
||||
t.compile_fail("page_size");
|
||||
t.compile_fail("should_loop");
|
||||
|
@ -49,8 +46,6 @@ fn unknown() {
|
|||
let t = Runner::new("unknown");
|
||||
t.compile_fail("kind");
|
||||
t.compile_fail("option");
|
||||
t.compile_fail("async-unknown");
|
||||
t.compile_fail("async-option");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -78,9 +73,7 @@ fn confirm() {
|
|||
|
||||
t.pass("valid");
|
||||
t.compile_fail("filter");
|
||||
t.compile_fail("filter_async");
|
||||
t.compile_fail("validate");
|
||||
t.compile_fail("validate_async");
|
||||
t.compile_fail("choices");
|
||||
t.compile_fail("should_loop");
|
||||
t.compile_fail("page_size");
|
||||
|
@ -107,9 +100,7 @@ fn expand() {
|
|||
|
||||
t.pass("valid");
|
||||
t.compile_fail("filter");
|
||||
t.compile_fail("filter_async");
|
||||
t.compile_fail("validate");
|
||||
t.compile_fail("validate_async");
|
||||
t.compile_fail("mask");
|
||||
t.compile_fail("extension");
|
||||
t.compile_fail("plugin");
|
||||
|
@ -160,9 +151,7 @@ fn select() {
|
|||
|
||||
t.pass("valid");
|
||||
t.compile_fail("filter");
|
||||
t.compile_fail("filter_async");
|
||||
t.compile_fail("validate");
|
||||
t.compile_fail("validate_async");
|
||||
t.compile_fail("mask");
|
||||
t.compile_fail("extension");
|
||||
t.compile_fail("plugin");
|
||||
|
@ -188,11 +177,8 @@ fn plugin() {
|
|||
t.pass("valid");
|
||||
t.compile_fail("default");
|
||||
t.compile_fail("transform");
|
||||
t.compile_fail("transform_async");
|
||||
t.compile_fail("filter");
|
||||
t.compile_fail("filter_async");
|
||||
t.compile_fail("validate");
|
||||
t.compile_fail("validate_async");
|
||||
t.compile_fail("choices");
|
||||
t.compile_fail("should_loop");
|
||||
t.compile_fail("page_size");
|
||||
|
@ -206,9 +192,7 @@ fn raw_select() {
|
|||
|
||||
t.pass("valid");
|
||||
t.compile_fail("filter");
|
||||
t.compile_fail("filter_async");
|
||||
t.compile_fail("validate");
|
||||
t.compile_fail("validate_async");
|
||||
t.compile_fail("mask");
|
||||
t.compile_fail("extension");
|
||||
t.compile_fail("plugin");
|
||||
|
|
|
@ -6,11 +6,8 @@ fn main() {
|
|||
Checkbox {
|
||||
name: "name",
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
validate: |_, _| Ok(()),
|
||||
async validate: |_, _| Box::pin(async { Ok(()) }),
|
||||
filter: |t, _| t,
|
||||
async filter: |t, _| Box::pin(async move { t }),
|
||||
choices: [
|
||||
sep,
|
||||
sep "separator",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Confirm { async filter: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `filter_async` does not exist for kind `confirm`
|
||||
--> $DIR/filter_async.rs:2:53
|
||||
|
|
||||
2 | let q = inquisition::questions![Confirm { async filter: todo!() }];
|
||||
| ^^^^^^
|
|
@ -3,6 +3,5 @@ fn main() {
|
|||
name: "name",
|
||||
default: true,
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Confirm { async validate: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `validate_async` does not exist for kind `confirm`
|
||||
--> $DIR/validate_async.rs:2:53
|
||||
|
|
||||
2 | let q = inquisition::questions![Confirm { async validate: todo!() }];
|
||||
| ^^^^^^^^
|
|
@ -1,6 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Input {
|
||||
async filter: todo!(),
|
||||
async filter: todo!(),
|
||||
}];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: duplicate option `filter_async`
|
||||
--> $DIR/filter_async.rs:4:15
|
||||
|
|
||||
4 | async filter: todo!(),
|
||||
| ^^^^^^
|
|
@ -1,6 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Input {
|
||||
async transform: todo!(),
|
||||
async transform: todo!(),
|
||||
}];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: duplicate option `transform_async`
|
||||
--> $DIR/transform_async.rs:4:15
|
||||
|
|
||||
4 | async transform: todo!(),
|
||||
| ^^^^^^^^^
|
|
@ -1,6 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Input {
|
||||
async validate: todo!(),
|
||||
async validate: todo!(),
|
||||
}];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: duplicate option `validate_async`
|
||||
--> $DIR/validate_async.rs:4:15
|
||||
|
|
||||
4 | async validate: todo!(),
|
||||
| ^^^^^^^^
|
|
@ -1,15 +1,10 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
Editor {
|
||||
name: "name",
|
||||
default: "hello world",
|
||||
extension: ".rs",
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
validate: |_, _| Ok(()),
|
||||
async validate: |_, _| Box::pin(async { Ok(()) }),
|
||||
filter: |t, _| t,
|
||||
async filter: |t, _| Box::pin(async move { t }),
|
||||
}
|
||||
];
|
||||
inquisition::questions![Editor {
|
||||
name: "name",
|
||||
default: "hello world",
|
||||
extension: ".rs",
|
||||
transform: |_, _, _| Ok(()),
|
||||
validate: |_, _| Ok(()),
|
||||
filter: |t, _| t,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Expand { async filter: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `filter_async` does not exist for kind `expand`
|
||||
--> $DIR/filter_async.rs:2:52
|
||||
|
|
||||
2 | let q = inquisition::questions![Expand { async filter: todo!() }];
|
||||
| ^^^^^^
|
|
@ -1,13 +1,10 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
Expand {
|
||||
name: "name",
|
||||
default: 'c',
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
choices: [('c', "choice")],
|
||||
page_size: 0,
|
||||
should_loop: true,
|
||||
}
|
||||
];
|
||||
inquisition::questions![Expand {
|
||||
name: "name",
|
||||
default: 'c',
|
||||
transform: |_, _, _| Ok(()),
|
||||
choices: [('c', "choice")],
|
||||
page_size: 0,
|
||||
should_loop: true,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Expand { async validate: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `validate_async` does not exist for kind `expand`
|
||||
--> $DIR/validate_async.rs:2:52
|
||||
|
|
||||
2 | let q = inquisition::questions![Expand { async validate: todo!() }];
|
||||
| ^^^^^^^^
|
|
@ -1,14 +1,9 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
Float {
|
||||
name: "name",
|
||||
default: 0.0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
validate: |_, _| Ok(()),
|
||||
async validate: |_, _| Box::pin(async { Ok(()) }),
|
||||
filter: |t, _| t,
|
||||
async filter: |t, _| Box::pin(async move { t }),
|
||||
}
|
||||
];
|
||||
inquisition::questions![Float {
|
||||
name: "name",
|
||||
default: 0.0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
validate: |_, _| Ok(()),
|
||||
filter: |t, _| t,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
Input {
|
||||
name: "name",
|
||||
default: "hello world",
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
validate: |_, _| Ok(()),
|
||||
async validate: |_, _| Box::pin(async { Ok(()) }),
|
||||
filter: |t, _| t,
|
||||
async filter: |t, _| Box::pin(async move { t }),
|
||||
}
|
||||
];
|
||||
inquisition::questions![Input {
|
||||
name: "name",
|
||||
default: "hello world",
|
||||
transform: |_, _, _| Ok(()),
|
||||
validate: |_, _| Ok(()),
|
||||
filter: |t, _| t,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
Int {
|
||||
name: "name",
|
||||
default: 0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
validate: |_, _| Ok(()),
|
||||
async validate: |_, _| Box::pin(async { Ok(()) }),
|
||||
filter: |t, _| t,
|
||||
async filter: |t, _| Box::pin(async move { t }),
|
||||
}
|
||||
];
|
||||
inquisition::questions![Int {
|
||||
name: "name",
|
||||
default: 0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
validate: |_, _| Ok(()),
|
||||
filter: |t, _| t,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
Password {
|
||||
name: "name",
|
||||
mask: '*',
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
validate: |_, _| Ok(()),
|
||||
async validate: |_, _| Box::pin(async { Ok(()) }),
|
||||
filter: |t, _| t,
|
||||
async filter: |t, _| Box::pin(async move { t }),
|
||||
}
|
||||
];
|
||||
inquisition::questions![Password {
|
||||
name: "name",
|
||||
mask: '*',
|
||||
transform: |_, _, _| Ok(()),
|
||||
validate: |_, _| Ok(()),
|
||||
filter: |t, _| t,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Plugin { async filter: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `filter_async` does not exist for kind `plugin`
|
||||
--> $DIR/filter_async.rs:2:52
|
||||
|
|
||||
2 | let q = inquisition::questions![Plugin { async filter: todo!() }];
|
||||
| ^^^^^^
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Plugin { async transform: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `transform_async` does not exist for kind `plugin`
|
||||
--> $DIR/transform_async.rs:2:52
|
||||
|
|
||||
2 | let q = inquisition::questions![Plugin { async transform: todo!() }];
|
||||
| ^^^^^^^^^
|
|
@ -13,24 +13,6 @@ impl inquisition::question::Plugin for TestPlugin {
|
|||
) -> inquisition::Result<Answer> {
|
||||
Ok(Answer::Int(0))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "tokio", feature = "async-std", feature = "smol"))]
|
||||
fn ask_async<'future>(
|
||||
&mut self,
|
||||
_message: String,
|
||||
_answers: &Answers,
|
||||
_stdout: &mut dyn Backend,
|
||||
_events: &mut inquisition::plugin::AsyncEvents,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<Output = inquisition::Result<Answer>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'future,
|
||||
>,
|
||||
> {
|
||||
Box::pin(async { Ok(Answer::Int(0)) })
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Plugin { async validate: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `validate_async` does not exist for kind `plugin`
|
||||
--> $DIR/validate_async.rs:2:52
|
||||
|
|
||||
2 | let q = inquisition::questions![Plugin { async validate: todo!() }];
|
||||
| ^^^^^^^^
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![RawSelect { async filter: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `filter_async` does not exist for kind `raw_select`
|
||||
--> $DIR/filter_async.rs:2:55
|
||||
|
|
||||
2 | let q = inquisition::questions![RawSelect { async filter: todo!() }];
|
||||
| ^^^^^^
|
|
@ -1,13 +1,10 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
RawSelect {
|
||||
name: "name",
|
||||
default: 0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
choices: ["choice"],
|
||||
page_size: 0,
|
||||
should_loop: true,
|
||||
}
|
||||
];
|
||||
inquisition::questions![RawSelect {
|
||||
name: "name",
|
||||
default: 0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
choices: ["choice"],
|
||||
page_size: 0,
|
||||
should_loop: true,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![RawSelect { async validate: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `validate_async` does not exist for kind `raw_select`
|
||||
--> $DIR/validate_async.rs:2:55
|
||||
|
|
||||
2 | let q = inquisition::questions![RawSelect { async validate: todo!() }];
|
||||
| ^^^^^^^^
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Select { async filter: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `filter_async` does not exist for kind `select`
|
||||
--> $DIR/filter_async.rs:2:52
|
||||
|
|
||||
2 | let q = inquisition::questions![Select { async filter: todo!() }];
|
||||
| ^^^^^^
|
|
@ -1,13 +1,10 @@
|
|||
fn main() {
|
||||
inquisition::questions! [
|
||||
Select {
|
||||
name: "name",
|
||||
default: 0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
async transform: |_, _, _| Box::pin(async { Ok(()) }),
|
||||
choices: ["choice"],
|
||||
page_size: 0,
|
||||
should_loop: true,
|
||||
}
|
||||
];
|
||||
inquisition::questions![Select {
|
||||
name: "name",
|
||||
default: 0,
|
||||
transform: |_, _, _| Ok(()),
|
||||
choices: ["choice"],
|
||||
page_size: 0,
|
||||
should_loop: true,
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Select { async validate: todo!() }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: option `validate_async` does not exist for kind `select`
|
||||
--> $DIR/validate_async.rs:2:52
|
||||
|
|
||||
2 | let q = inquisition::questions![Select { async validate: todo!() }];
|
||||
| ^^^^^^^^
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Input { async name: 0 }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: unknown question option `name_async`
|
||||
--> $DIR/async-option.rs:2:51
|
||||
|
|
||||
2 | let q = inquisition::questions![Input { async name: 0 }];
|
||||
| ^^^^
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
let q = inquisition::questions![Input { async unknown: 0 }];
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
error: unknown question option `unknown_async`
|
||||
--> $DIR/async-unknown.rs:2:51
|
||||
|
|
||||
2 | let q = inquisition::questions![Input { async unknown: 0 }];
|
||||
| ^^^^^^^
|
Loading…
Reference in New Issue
Block a user