mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-21 04:58:42 +00:00
Merge pull request #171 from NobodyXu/fix/confirm
This commit is contained in:
commit
50183a38c5
3 changed files with 119 additions and 30 deletions
|
@ -1,10 +1,9 @@
|
||||||
use std::{
|
use std::{
|
||||||
io::{stderr, stdin, Write},
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use cargo_toml::Manifest;
|
use cargo_toml::Manifest;
|
||||||
use log::{debug, info};
|
use log::debug;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tinytemplate::TinyTemplate;
|
use tinytemplate::TinyTemplate;
|
||||||
|
@ -18,6 +17,9 @@ pub use async_extracter::extract_archive_stream;
|
||||||
mod auto_abort_join_handle;
|
mod auto_abort_join_handle;
|
||||||
pub use auto_abort_join_handle::AutoAbortJoinHandle;
|
pub use auto_abort_join_handle::AutoAbortJoinHandle;
|
||||||
|
|
||||||
|
mod ui_thread;
|
||||||
|
pub use ui_thread::UIThread;
|
||||||
|
|
||||||
mod extracter;
|
mod extracter;
|
||||||
mod readable_rx;
|
mod readable_rx;
|
||||||
|
|
||||||
|
@ -129,23 +131,6 @@ pub fn get_install_path<P: AsRef<Path>>(install_path: Option<P>) -> Option<PathB
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm() -> Result<(), BinstallError> {
|
|
||||||
loop {
|
|
||||||
info!("Do you wish to continue? yes/[no]");
|
|
||||||
eprint!("? ");
|
|
||||||
stderr().flush().ok();
|
|
||||||
|
|
||||||
let mut input = String::new();
|
|
||||||
stdin().read_line(&mut input).unwrap();
|
|
||||||
|
|
||||||
match input.as_str().trim() {
|
|
||||||
"yes" | "y" | "YES" | "Y" => break Ok(()),
|
|
||||||
"no" | "n" | "NO" | "N" | "" => break Err(BinstallError::UserAbort),
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Template: Serialize {
|
pub trait Template: Serialize {
|
||||||
fn render(&self, template: &str) -> Result<String, BinstallError>
|
fn render(&self, template: &str) -> Result<String, BinstallError>
|
||||||
where
|
where
|
||||||
|
|
97
src/helpers/ui_thread.rs
Normal file
97
src/helpers/ui_thread.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use std::io::{self, BufRead, Write};
|
||||||
|
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
|
use crate::BinstallError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct UIThreadInner {
|
||||||
|
/// Request for confirmation
|
||||||
|
request_tx: mpsc::Sender<()>,
|
||||||
|
|
||||||
|
/// Confirmation
|
||||||
|
confirm_rx: mpsc::Receiver<Result<(), BinstallError>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UIThreadInner {
|
||||||
|
fn new() -> Self {
|
||||||
|
let (request_tx, mut request_rx) = mpsc::channel(1);
|
||||||
|
let (confirm_tx, confirm_rx) = mpsc::channel(10);
|
||||||
|
|
||||||
|
spawn_blocking(move || {
|
||||||
|
// This task should be the only one able to
|
||||||
|
// access stdin
|
||||||
|
let mut stdin = io::stdin().lock();
|
||||||
|
let mut input = String::with_capacity(16);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if request_rx.blocking_recv().is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock stdout so that nobody can interfere
|
||||||
|
// with confirmation.
|
||||||
|
let mut stdout = io::stdout().lock();
|
||||||
|
|
||||||
|
let res = loop {
|
||||||
|
writeln!(&mut stdout, "Do you wish to continue? yes/[no]").unwrap();
|
||||||
|
write!(&mut stdout, "? ").unwrap();
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
|
||||||
|
input.clear();
|
||||||
|
stdin.read_line(&mut input).unwrap();
|
||||||
|
|
||||||
|
match input.as_str().trim() {
|
||||||
|
"yes" | "y" | "YES" | "Y" => break Ok(()),
|
||||||
|
"no" | "n" | "NO" | "N" | "" => break Err(BinstallError::UserAbort),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
confirm_tx
|
||||||
|
.blocking_send(res)
|
||||||
|
.expect("entry exits when confirming request");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
request_tx,
|
||||||
|
confirm_rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn confirm(&mut self) -> Result<(), BinstallError> {
|
||||||
|
self.request_tx
|
||||||
|
.send(())
|
||||||
|
.await
|
||||||
|
.map_err(|_| BinstallError::UserAbort)?;
|
||||||
|
|
||||||
|
self.confirm_rx
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.unwrap_or(Err(BinstallError::UserAbort))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UIThread(Option<UIThreadInner>);
|
||||||
|
|
||||||
|
impl UIThread {
|
||||||
|
/// * `enable` - `true` to enable confirmation, `false` to disable it.
|
||||||
|
pub fn new(enable: bool) -> Self {
|
||||||
|
Self(if enable {
|
||||||
|
Some(UIThreadInner::new())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn confirm(&mut self) -> Result<(), BinstallError> {
|
||||||
|
if let Some(inner) = self.0.as_mut() {
|
||||||
|
inner.confirm().await
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/main.rs
29
src/main.rs
|
@ -191,6 +191,8 @@ async fn entry() -> Result<()> {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let mut uithread = UIThread::new(!opts.no_confirm);
|
||||||
|
|
||||||
// Compute install directory
|
// Compute install directory
|
||||||
let install_path = get_install_path(opts.install_path.as_deref()).ok_or_else(|| {
|
let install_path = get_install_path(opts.install_path.as_deref()).ok_or_else(|| {
|
||||||
error!("No viable install path found of specified, try `--install-path`");
|
error!("No viable install path found of specified, try `--install-path`");
|
||||||
|
@ -228,8 +230,8 @@ async fn entry() -> Result<()> {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if !opts.no_confirm && !opts.dry_run {
|
if !opts.dry_run {
|
||||||
confirm()?;
|
uithread.confirm().await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,6 +305,7 @@ async fn entry() -> Result<()> {
|
||||||
opts,
|
opts,
|
||||||
package,
|
package,
|
||||||
temp_dir,
|
temp_dir,
|
||||||
|
&mut uithread,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -317,7 +320,7 @@ async fn entry() -> Result<()> {
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| miette!("No viable targets found, try with `--targets`"))?;
|
.ok_or_else(|| miette!("No viable targets found, try with `--targets`"))?;
|
||||||
|
|
||||||
install_from_source(opts, package, target).await
|
install_from_source(opts, package, target, &mut uithread).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +334,7 @@ async fn install_from_package(
|
||||||
opts: Options,
|
opts: Options,
|
||||||
package: Package<Meta>,
|
package: Package<Meta>,
|
||||||
temp_dir: TempDir,
|
temp_dir: TempDir,
|
||||||
|
uithread: &mut UIThread,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Prompt user for third-party source
|
// Prompt user for third-party source
|
||||||
if fetcher.is_third_party() {
|
if fetcher.is_third_party() {
|
||||||
|
@ -338,8 +342,8 @@ async fn install_from_package(
|
||||||
"The package will be downloaded from third-party source {}",
|
"The package will be downloaded from third-party source {}",
|
||||||
fetcher.source_name()
|
fetcher.source_name()
|
||||||
);
|
);
|
||||||
if !opts.no_confirm && !opts.dry_run {
|
if !opts.dry_run {
|
||||||
confirm()?;
|
uithread.confirm().await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!(
|
info!(
|
||||||
|
@ -429,9 +433,7 @@ async fn install_from_package(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.no_confirm {
|
uithread.confirm().await?;
|
||||||
confirm()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Installing binaries...");
|
info!("Installing binaries...");
|
||||||
for file in &bin_files {
|
for file in &bin_files {
|
||||||
|
@ -456,11 +458,16 @@ async fn install_from_package(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn install_from_source(opts: Options, package: Package<Meta>, target: &str) -> Result<()> {
|
async fn install_from_source(
|
||||||
|
opts: Options,
|
||||||
|
package: Package<Meta>,
|
||||||
|
target: &str,
|
||||||
|
uithread: &mut UIThread,
|
||||||
|
) -> Result<()> {
|
||||||
// Prompt user for source install
|
// Prompt user for source install
|
||||||
warn!("The package will be installed from source (with cargo)",);
|
warn!("The package will be installed from source (with cargo)",);
|
||||||
if !opts.no_confirm && !opts.dry_run {
|
if !opts.dry_run {
|
||||||
confirm()?;
|
uithread.confirm().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.dry_run {
|
if opts.dry_run {
|
||||||
|
|
Loading…
Add table
Reference in a new issue