Files
pacdef_rust/crates/pacdef/src/review/mod.rs
T
ripytide 17e42ca435 Backend trait refactors
- Refactor the confusing/bodgy consts backend macro to use a single
  `BackendInfo` struct
- Combine `supports_as_dependency()` and the `switches_dependency` into a single `Option` type as that is a better representation as it is an optional backend feature.
- Add more types like `Packages`, `Sections` for uniformity.
- Added a crate-private prelude and switches all imports to use a
  prelude glob import (excluding functions because it's nice to only be
  importing types)
- Refactored Backend section name in logging by implementing Display for
  `AnyBackend` that just prints the `section`
2024-04-24 17:03:42 +01:00

178 lines
5.1 KiB
Rust

mod datastructures;
mod strategy;
use std::io::{stdin, stdout, Write};
use anyhow::Result;
use crate::prelude::*;
use crate::ui::{get_user_confirmation, read_single_char_from_terminal};
use self::datastructures::{ContinueWithReview, ReviewAction, ReviewIntention, ReviewsPerBackend};
use self::strategy::Strategy;
pub fn review(todo_per_backend: ToDoPerBackend, groups: &Groups) -> Result<()> {
let mut reviews = ReviewsPerBackend::new();
if todo_per_backend.nothing_to_do_for_all_backends() {
println!("nothing to do");
return Ok(());
}
'outer: for (backend, packages) in todo_per_backend {
let mut actions = vec![];
for package in packages {
println!("{}: {package}", backend.backend_info().section);
match get_action_for_package(package, groups, &mut actions, &backend)? {
ContinueWithReview::Yes => continue,
ContinueWithReview::No => return Ok(()),
ContinueWithReview::NoAndApply => {
reviews.push((backend, actions));
break 'outer;
}
}
}
reviews.push((backend, actions));
}
if reviews.nothing_to_do() {
println!("nothing to do");
return Ok(());
}
let strategies: Vec<Strategy> = reviews.into_strategies();
println!();
let mut iter = strategies.iter().peekable();
while let Some(strategy) = iter.next() {
strategy.show();
if iter.peek().is_some() {
println!();
}
}
println!();
if !get_user_confirmation()? {
return Ok(());
}
for strategy in strategies {
strategy.execute()?;
}
Ok(())
}
fn get_action_for_package(
package: Package,
groups: &Groups,
reviews: &mut Vec<ReviewAction>,
backend: &dyn Backend,
) -> Result<ContinueWithReview> {
loop {
match ask_user_action_for_package(backend.supports_as_dependency())? {
ReviewIntention::AsDependency => {
assert!(
backend.supports_as_dependency(),
"backend does not support dependencies"
);
reviews.push(ReviewAction::AsDependency(package));
break;
}
ReviewIntention::AssignGroup => {
if let Ok(Some(group)) = ask_group(groups) {
reviews.push(ReviewAction::AssignGroup(package, group.clone()));
break;
};
}
ReviewIntention::Delete => {
reviews.push(ReviewAction::Delete(package));
break;
}
ReviewIntention::Info => {
backend.show_package_info(&package)?;
}
ReviewIntention::Invalid => (),
ReviewIntention::Skip => break,
ReviewIntention::Quit => return Ok(ContinueWithReview::No),
ReviewIntention::Apply => return Ok(ContinueWithReview::NoAndApply),
}
}
Ok(ContinueWithReview::Yes)
}
/// Ask the user for the desired action, and return the associated
/// [`ReviewIntention`]. The query depends on the capabilities of the backend.
///
/// # Errors
///
/// This function will return an error if stdin or stdout cannot be accessed.
fn ask_user_action_for_package(supports_as_dependency: bool) -> Result<ReviewIntention> {
print_query(supports_as_dependency)?;
match read_single_char_from_terminal()?.to_ascii_lowercase() {
'a' if supports_as_dependency => Ok(ReviewIntention::AsDependency),
'd' => Ok(ReviewIntention::Delete),
'g' => Ok(ReviewIntention::AssignGroup),
'i' => Ok(ReviewIntention::Info),
'q' => Ok(ReviewIntention::Quit),
's' => Ok(ReviewIntention::Skip),
'p' => Ok(ReviewIntention::Apply),
_ => Ok(ReviewIntention::Invalid),
}
}
/// Print a space-terminated string that asks the user for the desired action.
/// The items of the string depend on whether the backend supports dependent
/// packages.
///
/// # Errors
///
/// This function will return an error if stdout cannot be flushed.
fn print_query(supports_as_dependency: bool) -> Result<()> {
let mut query = String::from("assign to (g)roup, (d)elete, (s)kip, (i)nfo, ");
if supports_as_dependency {
query.push_str("(a)s dependency, ");
}
query.push_str("a(p)ply, (q)uit? ");
print!("{query}");
stdout().lock().flush()?;
Ok(())
}
fn print_enumerated_groups(groups: &Groups) {
let number_digits = get_amount_of_digits_for_number(groups.len());
for (i, group) in groups.iter().enumerate() {
println!("{i:>number_digits$}: {}", group.name);
}
}
fn get_amount_of_digits_for_number(number: usize) -> usize {
number.to_string().len()
}
fn ask_group(groups: &Groups) -> Result<Option<&Group>> {
print_enumerated_groups(groups);
let mut buf = String::new();
stdin().read_line(&mut buf)?;
let reply = buf.trim();
let idx: usize = if let Ok(idx) = reply.parse() {
idx
} else {
return Ok(None);
};
if idx < groups.len() {
Ok(groups.iter().nth(idx))
} else {
Ok(None)
}
}