restructure project into a virtual manifest
- remove redundant readme - de-duplicate Cargo.toml information using workspace inheritance - rename "pacdef_core" to "pacdef" - move "crates/main/main.rs" to "crates/pacdef/src/main.rs"
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::{Group, Package};
|
||||
|
||||
use super::strategy::Strategy;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(super) enum ReviewAction {
|
||||
AsDependency(Package),
|
||||
Delete(Package),
|
||||
AssignGroup(Package, Rc<Group>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum ReviewIntention {
|
||||
AsDependency,
|
||||
AssignGroup,
|
||||
Delete,
|
||||
Info,
|
||||
Invalid,
|
||||
Skip,
|
||||
Quit,
|
||||
Apply,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ReviewsPerBackend {
|
||||
items: Vec<(Box<dyn Backend>, Vec<ReviewAction>)>,
|
||||
}
|
||||
|
||||
impl ReviewsPerBackend {
|
||||
pub(super) fn new() -> Self {
|
||||
Self { items: vec![] }
|
||||
}
|
||||
|
||||
pub(super) fn nothing_to_do(&self) -> bool {
|
||||
self.items.iter().all(|(_, vec)| vec.is_empty())
|
||||
}
|
||||
|
||||
pub(super) fn push(&mut self, value: (Box<dyn Backend>, Vec<ReviewAction>)) {
|
||||
self.items.push(value);
|
||||
}
|
||||
|
||||
/// Convert the reviews per backend to a vector of [`Strategy`], where one `Strategy` contains
|
||||
/// all actions that must be executed for a [`Backend`].
|
||||
///
|
||||
/// If there are no actions for a `Backend`, then that `Backend` is removed from the return
|
||||
/// value.
|
||||
pub(super) fn into_strategies(self) -> Vec<Strategy> {
|
||||
let mut result = vec![];
|
||||
|
||||
for (backend, actions) in self {
|
||||
let mut to_delete = vec![];
|
||||
let mut assign_group = vec![];
|
||||
let mut as_dependency = vec![];
|
||||
|
||||
extract_actions(
|
||||
actions,
|
||||
&mut to_delete,
|
||||
&mut assign_group,
|
||||
&mut as_dependency,
|
||||
);
|
||||
|
||||
result.push(Strategy::new(
|
||||
backend,
|
||||
to_delete,
|
||||
as_dependency,
|
||||
assign_group,
|
||||
));
|
||||
}
|
||||
|
||||
result.retain(|s| !s.nothing_to_do());
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for ReviewsPerBackend {
|
||||
type Item = (Box<dyn Backend>, Vec<ReviewAction>);
|
||||
|
||||
type IntoIter = std::vec::IntoIter<(Box<dyn Backend>, Vec<ReviewAction>)>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.items.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) enum ContinueWithReview {
|
||||
Yes,
|
||||
No,
|
||||
NoAndApply,
|
||||
}
|
||||
|
||||
fn extract_actions(
|
||||
actions: Vec<ReviewAction>,
|
||||
to_delete: &mut Vec<Package>,
|
||||
assign_group: &mut Vec<(Package, Rc<Group>)>,
|
||||
as_dependency: &mut Vec<Package>,
|
||||
) {
|
||||
for action in actions {
|
||||
match action {
|
||||
ReviewAction::Delete(package) => to_delete.push(package),
|
||||
ReviewAction::AssignGroup(package, group) => assign_group.push((package, group)),
|
||||
ReviewAction::AsDependency(package) => as_dependency.push(package),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
mod datastructures;
|
||||
mod strategy;
|
||||
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::backend::{Backend, ToDoPerBackend};
|
||||
use crate::ui::{get_user_confirmation, read_single_char_from_terminal};
|
||||
use crate::{Group, Package};
|
||||
|
||||
use self::datastructures::{ContinueWithReview, ReviewAction, ReviewIntention, ReviewsPerBackend};
|
||||
use self::strategy::Strategy;
|
||||
|
||||
pub fn review(
|
||||
todo_per_backend: ToDoPerBackend,
|
||||
groups: impl IntoIterator<Item = Group>,
|
||||
) -> Result<()> {
|
||||
let mut reviews = ReviewsPerBackend::new();
|
||||
let mut groups: Vec<Rc<Group>> = groups.into_iter().map(Rc::new).collect();
|
||||
|
||||
groups.sort_unstable();
|
||||
|
||||
if todo_per_backend.nothing_to_do_for_all_backends() {
|
||||
println!("nothing to do");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
'outer: for (backend, packages) in todo_per_backend.into_iter() {
|
||||
let mut actions = vec![];
|
||||
for package in packages {
|
||||
println!("{}: {package}", backend.get_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: &[Rc<Group>],
|
||||
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));
|
||||
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: &[Rc<Group>]) {
|
||||
let number_digits = get_amount_of_digits_for_number(groups.len());
|
||||
|
||||
for (i, group) in groups.iter().enumerate() {
|
||||
println!("{i:>number_digits$}: {}", group.name);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::as_conversions)] // this cannot introduce errors for any reasonably sized numbers.
|
||||
fn get_amount_of_digits_for_number(number: usize) -> usize {
|
||||
(number as f64).log10().trunc() as usize + 1
|
||||
}
|
||||
|
||||
fn ask_group(groups: &[Rc<Group>]) -> Result<Option<Rc<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(Some(groups[idx].clone()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::{Group, Package};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Strategy {
|
||||
backend: Box<dyn Backend>,
|
||||
delete: Vec<Package>,
|
||||
as_dependency: Vec<Package>,
|
||||
assign_group: Vec<(Package, Rc<Group>)>,
|
||||
}
|
||||
|
||||
impl Strategy {
|
||||
pub(super) fn new(
|
||||
backend: Box<dyn Backend>,
|
||||
delete: Vec<Package>,
|
||||
as_dependency: Vec<Package>,
|
||||
assign_group: Vec<(Package, Rc<Group>)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
delete,
|
||||
as_dependency,
|
||||
assign_group,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn execute(self) -> Result<()> {
|
||||
if !self.delete.is_empty() {
|
||||
self.backend.remove_packages(&self.delete, false)?;
|
||||
}
|
||||
|
||||
if !self.as_dependency.is_empty() {
|
||||
self.backend.make_dependency(&self.as_dependency)?;
|
||||
}
|
||||
|
||||
if !self.assign_group.is_empty() {
|
||||
self.backend.assign_group(self.assign_group)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn show(&self) {
|
||||
if self.nothing_to_do() {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("[{}]", self.backend.get_section());
|
||||
|
||||
if !self.delete.is_empty() {
|
||||
println!("delete:");
|
||||
for p in &self.delete {
|
||||
println!(" {p}");
|
||||
}
|
||||
}
|
||||
|
||||
if !self.as_dependency.is_empty() {
|
||||
println!("as dependency:");
|
||||
for p in &self.as_dependency {
|
||||
println!(" {p}");
|
||||
}
|
||||
}
|
||||
|
||||
if !self.assign_group.is_empty() {
|
||||
println!("assign groups:");
|
||||
for (p, g) in &self.assign_group {
|
||||
println!(" {p} -> {}", g.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn nothing_to_do(&self) -> bool {
|
||||
self.delete.is_empty() && self.as_dependency.is_empty() && self.assign_group.is_empty()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user