Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(exec): impl async prompts #609

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::{
fmt::{self, Debug},
process::{ExitCode, Termination},
string::FromUtf8Error,
};

use thiserror::Error;
Expand All @@ -29,7 +30,7 @@ pub enum Error {
ConfigError(#[from] figment::Error),

/// A [`Cmd`](crate::exec::Cmd) failed to finish.
#[error("Failed to get exit code of subprocess: {0}")]
#[error("Failed to finish subprocess execution: {0}")]
CmdJoinError(JoinError),

/// A [`Cmd`](crate::exec::Cmd) failed to spawn.
Expand Down Expand Up @@ -57,7 +58,7 @@ pub enum Error {

/// Error while converting a [`Vec<u8>`] to a [`String`].
#[error(transparent)]
FromUtf8Error(#[from] std::string::FromUtf8Error),
FromUtf8Error(#[from] FromUtf8Error),

/// Error while rendering a dialog.
#[error(transparent)]
Expand All @@ -67,12 +68,16 @@ pub enum Error {
#[error(transparent)]
IoError(#[from] io::Error),

/// A generic [`JoinError`].
#[error(transparent)]
JoinError(#[from] JoinError),

/// A [`Pm`](crate::pm::Pm) operation is not implemented.
#[error("Operation `{op}` is unimplemented for `{pm}`")]
#[allow(missing_docs)]
OperationUnimplementedError { op: String, pm: String },

/// Miscellaneous other error.
/// An error from a non-specified category.
#[error("{0}")]
OtherError(String),
}
Expand Down
59 changes: 32 additions & 27 deletions src/exec.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
//! APIs for spawning subprocesses and handling their results.

use std::{
process::Stdio,
sync::atomic::{AtomicBool, Ordering},
};
use std::process::Stdio;

use bytes::{Bytes, BytesMut};
use dialoguer::FuzzySelect;
use futures::prelude::*;
use indoc::indoc;
use itertools::{chain, Itertools};
use once_cell::sync::Lazy;
use regex::{RegexSet, RegexSetBuilder};
use tap::prelude::*;
use tokio::{
io::{self, AsyncRead, AsyncWrite},
process::Command as Exec,
sync::Mutex,
task::JoinHandle,
};
#[allow(clippy::wildcard_imports)]
Expand Down Expand Up @@ -318,32 +317,38 @@ impl Cmd {
#[doc = docs_errors_exec!()]
async fn exec_prompt(self, mute: bool) -> Result<Output> {
/// If the user has skipped all the prompts with `yes`.
static ALL: AtomicBool = AtomicBool::new(false);
///
/// The [`Mutex`] is required to mutually exclude the prompt.
static ALL: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));

// The answer obtained from the prompt.
// The only Atomic* we're dealing with is `ALL`, so `Ordering::Relaxed` is fine.
// See: <https://marabos.nl/atomics/memory-ordering.html#relaxed>
let proceed = ALL.load(Ordering::Relaxed) || {
println_quoted(&*prompt::PENDING, &self);
let answer = tokio::task::block_in_place(move || {
prompt(
"Proceed",
"with the previous command?",
&["Yes", "All", "No"],
)
})?;
match answer {
// The default answer is `Yes`.
0 => true,
// You can also say `All` to answer `Yes` to all the other questions that follow.
1 => {
ALL.store(true, Ordering::Relaxed);
true
let proceed = {
let mut all = ALL.lock().await;
*all || {
println_quoted(&*prompt::PENDING, &self);
let answer = tokio::task::spawn_blocking(move || {
prompt(
"Proceed",
"with the previous command?",
&["Yes", "All", "No"],
)
})
.await??;
match answer {
// The default answer is `Yes`.
0 => true,
// You can also say `All` to answer `Yes` to all the other questions that
// follow.
1 => {
*all = true;
true
}
// Or you can say `No`.
2 => false,
// TODO: This might need some changes.
// ! I didn't put a `None` option because you can just Ctrl-C it if you want.
_ => unreachable!(),
}
// Or you can say `No`.
2 => false,
// ! I didn't put a `None` option because you can just Ctrl-C it if you want.
_ => unreachable!(),
}
};
if !proceed {
Expand Down
Loading