From b956782249794542e8629b1fa4818d50cf0c3b1b Mon Sep 17 00:00:00 2001 From: steven-omaha <35634100+steven-omaha@users.noreply.github.com> Date: Sat, 14 Jan 2023 19:32:03 +0100 Subject: [PATCH] start adding review --- Cargo.lock | 10 +++ Cargo.toml | 1 + src/action.rs | 1 + src/args.rs | 1 + src/backend/backend_trait.rs | 4 + src/backend/todo_per_backend.rs | 4 + src/core.rs | 5 +- src/lib.rs | 1 + src/review.rs | 142 ++++++++++++++++++++++++++++++++ 9 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/review.rs diff --git a/Cargo.lock b/Cargo.lock index c944807..e276ac5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_yaml", + "termios", ] [[package]] @@ -295,6 +296,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + [[package]] name = "textwrap" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 2d07d34..c3f8729 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ serde_yaml = "*" serde_derive = "*" serde = "*" pacdef_macro = {path = "pacdef_macro/"} +termios = "*" [profile.release] lto = "off" diff --git a/src/action.rs b/src/action.rs index 26b15a1..f3d4d26 100644 --- a/src/action.rs +++ b/src/action.rs @@ -8,6 +8,7 @@ pub(crate) enum Actions { Import, New, Remove, + Review, Search, Show, Sync, diff --git a/src/args.rs b/src/args.rs index 445a02e..c9a7e25 100644 --- a/src/args.rs +++ b/src/args.rs @@ -44,6 +44,7 @@ fn get_arg_parser() -> Command<'static> { .arg_required_else_help(true) .arg(Arg::new("groups").multiple_values(true)), ) + .subcommand(Command::new(REVIEW).about("review unmanaged packages")) .subcommand( Command::new(SEARCH) .about("search for packages which match a provided string literal or regex") diff --git a/src/backend/backend_trait.rs b/src/backend/backend_trait.rs index aad45ca..6376d92 100644 --- a/src/backend/backend_trait.rs +++ b/src/backend/backend_trait.rs @@ -68,6 +68,10 @@ pub(crate) trait Backend { fn add_packages(&mut self, packages: HashSet); + fn show_package_info(&self, package: &Package) -> Result<()> { + todo!() + } + fn get_unmanaged_packages_sorted(&self) -> Result> { let installed = self .get_explicitly_installed_packages() diff --git a/src/backend/todo_per_backend.rs b/src/backend/todo_per_backend.rs index 0108581..c2e26d7 100644 --- a/src/backend/todo_per_backend.rs +++ b/src/backend/todo_per_backend.rs @@ -16,6 +16,10 @@ impl ToDoPerBackend { self.0.push(item); } + pub(crate) fn into_iter(self) -> impl Iterator, Vec)> { + self.0.into_iter() + } + pub(crate) fn iter(&self) -> impl Iterator, Vec)> { self.0.iter() } diff --git a/src/core.rs b/src/core.rs index 21a838b..360fa12 100644 --- a/src/core.rs +++ b/src/core.rs @@ -12,6 +12,7 @@ use crate::backend::{Backend, Backends, ToDoPerBackend}; use crate::cmd::run_edit_command; use crate::env::get_single_var; use crate::path::get_pacdef_group_dir; +use crate::review; use crate::search; use crate::ui::get_user_confirmation; use crate::Config; @@ -43,6 +44,8 @@ impl Pacdef { Some((IMPORT, args)) => self.import_groups(args).context("importing groups"), Some((NEW, args)) => self.new_groups(args).context("creating new group files"), Some((REMOVE, args)) => self.remove_groups(args).context("removing groups"), + Some((REVIEW, _)) => review::review(self.get_unmanaged_packages(), self.groups) + .context("removing groups"), Some((SHOW, args)) => self.show_group_content(args).context("showing groups"), Some((SEARCH, args)) => { search::search_packages(args, &self.groups).context("searching packages") @@ -141,7 +144,7 @@ impl Pacdef { unmanaged_per_backend.show(None); } - fn get_unmanaged_packages(self) -> ToDoPerBackend { + fn get_unmanaged_packages(&self) -> ToDoPerBackend { let mut result = ToDoPerBackend::new(); for backend in Backends::iter() { diff --git a/src/lib.rs b/src/lib.rs index f3cffe1..d5ba8f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ mod core; mod env; mod grouping; mod path; +mod review; mod search; mod ui; diff --git a/src/review.rs b/src/review.rs new file mode 100644 index 0000000..73f86b8 --- /dev/null +++ b/src/review.rs @@ -0,0 +1,142 @@ +use std::collections::HashSet; +use std::io::{self, stdin, Read}; +use std::rc::Rc; + +use anyhow::{bail, Result}; +use termios::*; + +use crate::backend::{Backend, Backends, ToDoPerBackend}; +use crate::grouping::{Group, Package, Section}; + +#[derive(Debug)] +enum ReviewAction { + AsDependency, + AssignGroupBackend, + Delete, + Info, + Invalid, + Skip, + Quit, +} + +struct Reviews<'a> { + pub delete: Vec<(Rc>, Package)>, + pub assign: Vec<(Rc>, Package, &'a Group, &'a Section)>, +} + +impl Reviews { + fn new() -> Self { + Self { + delete: vec![], + assign: vec![], + } + } +} + +pub(crate) fn review(todo_per_backend: ToDoPerBackend, groups: HashSet) -> Result<()> { + let mut reviews = Reviews::new(); + let mut groups: Vec<_> = groups.into_iter().collect(); + groups.sort_unstable(); + + if todo_per_backend.nothing_to_do_for_all_backends() { + println!("nothing to do"); + return Ok(()); + } + + for (backend, packages) in todo_per_backend.into_iter() { + let backend = Rc::new(backend); + for package in packages { + println!("{}: {package}", backend.get_section()); + 'inner: loop { + match ask_user_action_for_package()? { + ReviewAction::AsDependency => todo!(), + ReviewAction::AssignGroupBackend => { + if let Some((group, section)) = ask_user_group_section(&package, &groups)? { + reviews + .assign + .push((backend.clone(), package, &group, §ion)); + break 'inner; + } + } + ReviewAction::Delete => { + reviews.delete.push((backend.clone(), package)); + break 'inner; + } + ReviewAction::Info => backend.show_package_info(&package)?, + ReviewAction::Invalid => (), + ReviewAction::Skip => break 'inner, + ReviewAction::Quit => bail!("user wants to quit"), + } + } + } + } + + todo!() +} + +fn ask_user_group_section<'a>( + package: &'a Package, + groups: &'a [Group], +) -> Result> { + if let Some(group) = ask_group(groups)? { + if let Some(section) = ask_section(section)? { + return Ok(Some((group, section))); + } + } +} + +fn ask_user_action_for_package() -> Result { + print!("assign to (g)roup, (d)elete, (s)kip, (i)nfo, (a)s dependency, (q)uit? "); + match read_single_char_from_terminal()? { + 'a' => Ok(ReviewAction::AsDependency), + 'd' => Ok(ReviewAction::Delete), + 'g' => Ok(ReviewAction::AssignGroupBackend), + 'i' => Ok(ReviewAction::Info), + 'q' => Ok(ReviewAction::Quit), + 's' => Ok(ReviewAction::Skip), + _ => Ok(ReviewAction::Invalid), + } +} + +fn read_single_char_from_terminal() -> Result { + let fd = 0; // 0 is the file descriptor for stdin + let termios = Termios::from_fd(fd)?; + let mut new_termios = termios; + new_termios.c_lflag &= !(ICANON | ECHO); + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + tcsetattr(fd, TCSANOW, &new_termios).unwrap(); + + let mut input = [0u8; 1]; + io::stdin().read_exact(&mut input[..]).unwrap(); + let result = input[0] as char; + println!("{result}"); + + tcsetattr(fd, TCSANOW, &termios).unwrap(); // restore previous settings + Ok(result) +} + +fn print_enumerated_groups(groups: &[Group]) { + for (i, group) in groups.iter().enumerate() { + println!("{i}: {}", group.name); + } +} + +fn ask_group(groups: &[Group]) -> Result> { + 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])) + } else { + Ok(None) + } +}