Proper Result and error structure for input prompts

This commit is contained in:
Xavier Moffett 2024-09-25 20:18:06 -04:00
parent 8844bafd64
commit 828da40579
Signed by: Sapphirus
GPG key ID: A6C061B2CEA1A7AC
7 changed files with 68 additions and 25 deletions

View file

@ -44,7 +44,7 @@ use crate::{
filesystem::{create_blank_state, create_hard_link},
transaction::{TransactionAggregator, TransactionFlags},
},
utils::unix_epoch_time,
utils::{prompt::PromptError, unix_epoch_time},
Error,
ErrorGeneric,
ErrorTrait,
@ -131,6 +131,10 @@ impl ErrorTrait for SyncError {
impl From<&Error> for SyncError {
fn from(error: &Error) -> SyncError {
if let Ok(PromptError::PromptInterrupted) = error.downcast::<PromptError>() {
return Self::SignalInterrupt;
}
Self::InternalError(error.kind().to_string())
}
}

View file

@ -21,7 +21,7 @@ use std::path::Path;
use alpm::{AnyQuestion, Question::*};
use crate::utils::prompt::prompt;
use crate::{utils::prompt::prompt, ErrorGeneric};
pub fn callback(question: AnyQuestion, _: &mut ()) {
match question.question() {
@ -30,8 +30,9 @@ pub fn callback(question: AnyQuestion, _: &mut ()) {
let pkg_b = x.conflict().package2().name();
let prompt_string = format!("Conflict between {pkg_a} and {pkg_b}; Remove {pkg_b}?");
if prompt("->", prompt_string, false) {
x.set_remove(true);
match prompt("->", prompt_string, false).generic() {
Ok(bool) => x.set_remove(bool),
Err(err) => err.error(),
}
}
Replace(x) => {
@ -39,8 +40,9 @@ pub fn callback(question: AnyQuestion, _: &mut ()) {
let new = x.newpkg().name();
let prompt_string = format!("Replace package {old} with {new}?");
if prompt("->", prompt_string, false) {
x.set_replace(true);
match prompt("->", prompt_string, false).generic() {
Ok(bool) => x.set_replace(bool),
Err(err) => err.error(),
}
}
Corrupted(mut x) => {
@ -49,8 +51,9 @@ pub fn callback(question: AnyQuestion, _: &mut ()) {
let reason = x.reason();
let prompt_string = format!("'{filename}': {reason}. Remove package?");
if prompt("::", prompt_string, true) {
x.set_remove(true);
match prompt("->", prompt_string, false).generic() {
Ok(bool) => x.set_remove(bool),
Err(err) => err.error(),
}
}
ImportKey(mut x) => {
@ -58,8 +61,9 @@ pub fn callback(question: AnyQuestion, _: &mut ()) {
let name = x.uid();
let prompt_string = format!("Import key {fingerprint}, \"{name}\" to keyring?");
if prompt("->", prompt_string, true) {
x.set_import(true);
match prompt("->", prompt_string, false).generic() {
Ok(bool) => x.set_import(bool),
Err(err) => err.error(),
}
}
_ => (),

View file

@ -436,7 +436,7 @@ impl<'a> TransactionHandle<'a> {
if !self.agent
&& !ignored.contains(pkg.name())
&& config.alpm().held().contains(&pkg.name())
&& !prompt("::", format!("Target package {}{}{} is held. Remove it?", *BOLD, pkg.name(), *RESET), false)
&& !prompt("::", format!("Target package {}{}{} is held. Remove it?", *BOLD, pkg.name(), *RESET), false)?
{
self.meta.held_pkgs.insert(pkg.name().into());
continue;
@ -466,7 +466,7 @@ impl<'a> TransactionHandle<'a> {
"::",
format!("Target package {}{}{} is ignored. Upgrade it?", *BOLD, pkg.name(), *RESET),
false,
)
)?
{
self.meta.ignored_pkgs.insert(pkg.name().into());
continue;

View file

@ -17,24 +17,51 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{
fmt::{Display, Formatter, Result as FmtResult},
io::ErrorKind::{Interrupted, NotConnected},
};
use dialoguer::{
console::{style, Style},
theme::ColorfulTheme,
Input,
};
use std::io::Error;
use crate::constants::{BAR_RED, BOLD, RESET};
use crate::{
constants::{BAR_RED, BOLD, RESET},
err,
impl_error,
Error,
ErrorGeneric,
ErrorTrait,
Result,
};
pub fn prompt(prefix: &str, prompt: impl Into<String>, yn_prompt: bool) -> bool {
if let Ok(value) = create_prompt(prompt.into(), prefix, yn_prompt) {
value.to_lowercase() == "y" || (yn_prompt && value.is_empty())
} else {
false
#[derive(Debug)]
pub enum PromptError {
PromptInterrupted,
PromptNotTerminal,
}
impl Display for PromptError {
fn fmt(&self, fmter: &mut Formatter<'_>) -> FmtResult {
match self {
Self::PromptInterrupted => write!(fmter, "Prompt was interrupted."),
Self::PromptNotTerminal => write!(fmter, "Input is not a terminal."),
}
}
}
fn create_prompt(message: String, prefix: &str, yn_prompt: bool) -> Result<String, Error> {
impl_error!(PromptError);
pub fn prompt(prefix: &str, prompt: impl Into<String>, yn_prompt: bool) -> Result<bool> {
let value = create_prompt(prompt.into(), prefix, yn_prompt)?;
Ok(value.to_lowercase() == "y" || (yn_prompt && value.is_empty()))
}
fn create_prompt(message: String, prefix: &str, yn_prompt: bool) -> Result<String> {
let prompt = match yn_prompt {
true => ("[Y/n]", style(prefix.into()).blue().bold()),
false => ("[y/N]", style(prefix.into()).red().bold()),
@ -50,11 +77,19 @@ fn create_prompt(message: String, prefix: &str, yn_prompt: bool) -> Result<Strin
values_style: Style::new(),
..ColorfulTheme::default()
};
let input: String = match Input::with_theme(&theme).with_prompt(message).allow_empty(true).interact_text() {
Ok(prompt) => prompt,
Err(error) => match error.kind() {
Interrupted => err!(PromptError::PromptInterrupted)?,
NotConnected => err!(PromptError::PromptNotTerminal)?,
_ => Err(error).generic()?,
},
};
return Input::with_theme(&theme).with_prompt(message).allow_empty(true).interact_text();
Ok(input)
}
pub fn prompt_targets(targets: &[&str], ins_prompt: &str, yn_prompt: bool) -> bool {
pub fn prompt_targets(targets: &[&str], ins_prompt: &str, yn_prompt: bool) -> Result<bool> {
eprintln!("{} {}Container{}{}\n", *BAR_RED, *BOLD, if targets.len() > 1 { "s" } else { "" }, *RESET);
for target in targets.iter() {

View file

@ -72,7 +72,7 @@ fn delete_containers<'a>(
if flags.contains(TransactionFlags::NO_CONFIRM) {
println!("{} {}{}...{}", *BAR_GREEN, *BOLD, &message, *RESET);
delete_roots(cache, lock, logger, delete, force)?;
} else if prompt_targets(delete, &message, false) {
} else if prompt_targets(delete, &message, false)? {
delete_roots(cache, lock, logger, delete, force)?;
}

View file

@ -278,7 +278,7 @@ fn process_kill(args: &mut Arguments) -> Result<()> {
let instances: Vec<String> = instances.iter().map(|a| format!("{} ({}{}{})", a.0, *DIM, a.1, *RESET)).collect();
let instances: Vec<&str> = instances.iter().map(|a| a.as_ref()).collect();
match no_confirm || prompt_targets(&instances, "Kill container processes?", false) {
match no_confirm || prompt_targets(&instances, "Kill container processes?", false)? {
true => kill_processes(&list, sigint),
false => Ok(()),
}

View file

@ -92,7 +92,7 @@ pub fn remove_containers(args: &mut Arguments) -> Result<()> {
let lock = Lock::new().lock()?;
if let (true, _) | (_, true) = (no_confirm, prompt_targets(&instances, "Delete containers?", false)) {
if let (true, _) | (_, true) = (no_confirm, prompt_targets(&instances, "Delete containers?", false)?) {
if let Err(err) = delete_roots(&cache, &lock, &mut logger, &instances, force) {
eprintln!("{}", ErrorType::Error(&err));
}