remove async

This commit is contained in:
Lutetium-Vanadium 2021-05-25 17:22:44 +05:30
parent 4ef903934b
commit 6dd7007033
74 changed files with 215 additions and 1676 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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`

View File

@ -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

View File

@ -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(())
},
)
}

View File

@ -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() {

View File

@ -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(())
},
)
}

View File

@ -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(),
]
}

View File

@ -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(())
}

View File

@ -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 => {

View File

@ -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"]

View File

@ -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()?;
}
}
}
}

View File

@ -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!(),
}
}
}

View File

@ -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")]

View File

@ -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
)*
};
}

View File

@ -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
)*
};
}

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -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
}
}
}
}

View File

@ -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))
}
}
}
};
}

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -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> {

View File

@ -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");

View File

@ -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",

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Confirm { async filter: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^

View File

@ -3,6 +3,5 @@ fn main() {
name: "name",
default: true,
transform: |_, _, _| Ok(()),
async transform: |_, _, _| Box::pin(async { Ok(()) }),
}];
}

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Confirm { async validate: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^^^

View File

@ -1,6 +0,0 @@
fn main() {
let q = inquisition::questions![Input {
async filter: todo!(),
async filter: todo!(),
}];
}

View File

@ -1,5 +0,0 @@
error: duplicate option `filter_async`
--> $DIR/filter_async.rs:4:15
|
4 | async filter: todo!(),
| ^^^^^^

View File

@ -1,6 +0,0 @@
fn main() {
let q = inquisition::questions![Input {
async transform: todo!(),
async transform: todo!(),
}];
}

View File

@ -1,5 +0,0 @@
error: duplicate option `transform_async`
--> $DIR/transform_async.rs:4:15
|
4 | async transform: todo!(),
| ^^^^^^^^^

View File

@ -1,6 +0,0 @@
fn main() {
let q = inquisition::questions![Input {
async validate: todo!(),
async validate: todo!(),
}];
}

View File

@ -1,5 +0,0 @@
error: duplicate option `validate_async`
--> $DIR/validate_async.rs:4:15
|
4 | async validate: todo!(),
| ^^^^^^^^

View File

@ -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,
}];
}

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Expand { async filter: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^

View File

@ -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,
}];
}

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Expand { async validate: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^^^

View File

@ -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,
}];
}

View File

@ -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,
}];
}

View File

@ -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,
}];
}

View File

@ -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,
}];
}

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Plugin { async filter: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Plugin { async transform: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^^^^

View File

@ -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() {

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Plugin { async validate: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^^^

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![RawSelect { async filter: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^

View File

@ -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,
}];
}

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![RawSelect { async validate: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^^^

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Select { async filter: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^

View File

@ -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,
}];
}

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Select { async validate: todo!() }];
}

View File

@ -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!() }];
| ^^^^^^^^

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Input { async name: 0 }];
}

View File

@ -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 }];
| ^^^^

View File

@ -1,3 +0,0 @@
fn main() {
let q = inquisition::questions![Input { async unknown: 0 }];
}

View File

@ -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 }];
| ^^^^^^^