diff --git a/src/cli.rs b/src/cli.rs index dce097d..b769288 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,8 +5,9 @@ use clap::{Args, Parser, Subcommand}; name = "ah", author = "Michał Czyż", version = "0.1.0", - about = "A declarative package manager for Arch Linux", - long_about = "Arch Helper is a declarative package management tool for Arch Linux. It leverages paru or other package managers for seamless integration.")] + about = "A declarative package manager for Arch Linux", + long_about = "Arch Helper is a declarative package management tool for Arch Linux. It leverages paru or other package managers for seamless integration." +)] pub struct Cli { #[command(subcommand)] pub command: Option, @@ -14,25 +15,25 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { - #[command(about = "Install packages")] + #[command(alias = "i", about = "Install packages")] Install(PackageList), - #[command(about = "Upgrade packages")] + #[command(alias = "u", about = "Upgrade packages")] Upgrade { #[arg(help = "Don't prompt for confirmation", default_value_t = false)] - noconfirm: bool + noconfirm: bool, }, - - #[command(about = "Synchronize packages")] + + #[command(alias = "s", about = "Synchronize packages")] Sync { #[arg(help = "Don't prompt for confirmation", default_value_t = false)] - noconfirm: bool + noconfirm: bool, }, - - #[command(about = "Remove packages")] + + #[command(alias = "r", about = "Remove packages")] Remove(PackageList), - #[command(about = "Find packages")] + #[command(alias = "f", about = "Find packages")] Find(Query), } @@ -46,4 +47,4 @@ pub struct PackageList { pub struct Query { #[arg(help = "Search term for finding packages")] pub query: Vec, -} \ No newline at end of file +} diff --git a/src/file.rs b/src/file.rs index e4c75e1..0a81c5d 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,6 +1,7 @@ use std::{ - fs::File, - io::{prelude::*, BufReader}, path::PathBuf, + fs::{File, OpenOptions}, + io::{prelude::*, BufReader}, + path::PathBuf, }; // pub fn read_config(path: &str) -> Vec { @@ -12,12 +13,34 @@ use std::{ // } pub fn read_packages(path: PathBuf) -> Vec { - let file = File::open(path).expect("Failed to open file"); - let buf = BufReader::new(file); + let file = File::open(path).expect("Failed to open file"); + let buf = BufReader::new(file); - buf.lines().map(|l| l.expect("Failed to read line")).collect() + buf.lines() + .map(|l| l.expect("Failed to read line")) + .collect() } -// pub fn write_packages(path: &str, content: &str) { -// todo!(); -// } \ No newline at end of file +pub fn append_package(path: PathBuf, package: &str) { + let mut file = OpenOptions::new() + .write(true) + .append(true) + .open(path) + .expect("Failed to open file"); + + if let Err(err) = writeln!(file, "{}", package) { + eprintln!("Couldn't write to file: {}", err); + } +} + +pub fn write_packages(path: PathBuf, content: &str) { + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(path) + .expect("Failed to open file"); + + if let Err(err) = writeln!(file, "{}", content) { + eprintln!("Couldn't write to file: {}", err); + } +} diff --git a/src/main.rs b/src/main.rs index 7fe59ad..5b3f822 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,8 @@ use cli::{PackageList, Query}; use colored::Colorize; mod cli; -mod packages; mod file; +mod packages; fn main() { let cli = cli::Cli::parse(); diff --git a/src/packages/find.rs b/src/packages/find.rs index 4fc213b..9a2e758 100644 --- a/src/packages/find.rs +++ b/src/packages/find.rs @@ -4,21 +4,25 @@ use std::process::Command; const PACKAGE_MANAGER: &str = "paru"; pub fn find(query: Vec) -> Result<(), Box> { - println!("{} {}", "::".bold().green(), "Looking for package...".bold()); + println!( + "{} {}", + "::".bold().green(), + "Looking for package...".bold() + ); - if query.is_empty() { - return Err("No query provided".into()); - } + if query.is_empty() { + return Err("No query provided".into()); + } - let output = Command::new(PACKAGE_MANAGER) - .arg("--color") - .arg("always") - .arg("-Ss") - .args(query) - .output() - .expect("Failed to execute command"); + let output = Command::new(PACKAGE_MANAGER) + .arg("--color") + .arg("always") + .arg("-Ss") + .args(query) + .output() + .expect("Failed to execute command"); - print!("{}", String::from_utf8_lossy(&output.stdout)); + print!("{}", String::from_utf8_lossy(&output.stdout)); - Ok(()) -} \ No newline at end of file + Ok(()) +} diff --git a/src/packages/install.rs b/src/packages/install.rs index 9ca4d52..baaf9e5 100644 --- a/src/packages/install.rs +++ b/src/packages/install.rs @@ -1,6 +1,62 @@ +use crate::{file, packages::get_package_path}; use colored::Colorize; +use std::io::Write; +use std::process::{Command, Stdio}; -pub fn install(_package: Vec) -> Result<(), Box> { - println!("{} {}", "::".bold().green(), "Installing packages...".bold()); - todo!(); -} \ No newline at end of file +const PACKAGE_MANAGER: &str = "paru"; + +pub fn install(new_packages: Vec) -> Result<(), Box> { + println!( + "{} {}", + "::".bold().green(), + "Installing packages...".bold() + ); + + let packages = file::read_packages(get_package_path()); + + let packages = packages + .into_iter() + .filter(|p| !p.contains("#") && !p.is_empty()) + .collect::>(); + + let mut child = Command::new(PACKAGE_MANAGER) + .arg("--color") + .arg("always") + .arg("-S") + .arg("--needed") + // .arg(noconfirm) + .arg("-") + .stdin(Stdio::piped()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to execute command"); + + if let Some(mut stdin) = child.stdin.take() { + for new_package in new_packages.clone() { + writeln!(stdin, "{}", new_package).unwrap(); + } + } + + let status = child.wait().expect("Failed to wait on child"); + + if !status.success() { + return Err("Failed to install packages".into()); + } + + println!("{} {}", "::".bold().green(), "Packages installed".bold()); + + println!( + "{} {}", + "::".bold().blue(), + "Updating package index...".bold() + ); + + for new_package in new_packages { + if !packages.contains(&new_package) { + file::append_package(get_package_path(), &new_package); + } + } + + Ok(()) +} diff --git a/src/packages/mod.rs b/src/packages/mod.rs index af0344a..feed7bc 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -1,34 +1,38 @@ use colored::Colorize; -use std::path::PathBuf; use std::io::{self, Write}; +use std::path::PathBuf; +pub mod find; +pub mod install; pub mod rebuild; +pub mod remove; pub mod sync; pub mod upgrade; -pub mod install; -pub mod remove; -pub mod find; +pub use find::find; +pub use install::install; pub use rebuild::rebuild; +pub use remove::remove; pub use sync::sync; pub use upgrade::upgrade; -pub use install::install; -pub use remove::remove; -pub use find::find; fn get_package_path() -> PathBuf { - let home_dir = std::env::var("HOME").unwrap(); - - PathBuf::from(home_dir).join("packages") + let home_dir = std::env::var("HOME").unwrap(); + + PathBuf::from(home_dir).join("packages") } fn ask_confirmation() -> Result { - print!("{} {}", "::".bold().blue(), "Do you want to continue? [Y/n] "); - io::stdout().flush()?; + print!( + "{} {}", + "::".bold().blue(), + "Do you want to continue? [Y/n] " + ); + io::stdout().flush()?; - let mut input = String::new(); - io::stdin().read_line(&mut input)?; + let mut input = String::new(); + io::stdin().read_line(&mut input)?; - let input = input.trim().to_lowercase(); - Ok(input.is_empty() || input == "y") -} \ No newline at end of file + let input = input.trim().to_lowercase(); + Ok(input.is_empty() || input == "y") +} diff --git a/src/packages/rebuild.rs b/src/packages/rebuild.rs index e26f1fc..ffc53ff 100644 --- a/src/packages/rebuild.rs +++ b/src/packages/rebuild.rs @@ -1,6 +1,6 @@ use colored::Colorize; -use std::process::{Command, Stdio}; use std::io::Write; +use std::process::{Command, Stdio}; use crate::file; use crate::packages::{ask_confirmation, get_package_path}; @@ -8,45 +8,58 @@ use crate::packages::{ask_confirmation, get_package_path}; const PACKAGE_MANAGER: &str = "paru"; pub fn rebuild(noconfirm: bool) -> Result<(), Box> { - println!("{} {}", "::".bold().green(), "Upgrading & syncing packages...".bold()); + println!( + "{} {}", + "::".bold().green(), + "Upgrading & syncing packages...".bold() + ); - if !ask_confirmation()? { - return Err("Operation aborted".into()); - } + if !ask_confirmation()? { + return Err("Operation aborted".into()); + } - let packages = file::read_packages(get_package_path()); + let packages = file::read_packages(get_package_path()); - let packages = packages.into_iter() - .filter(|p| !p.contains("#") && !p.is_empty()) - .collect::>(); + let packages = packages + .into_iter() + .filter(|p| !p.contains("#") && !p.is_empty()) + .collect::>(); - let noconfirm = if noconfirm { "--noconfirm" } else { "--confirm" }; + let noconfirm = if noconfirm { + "--noconfirm" + } else { + "--confirm" + }; - let mut child = Command::new(PACKAGE_MANAGER) - .arg("--color") - .arg("always") - .arg("-Syu") - .arg("--needed") - .arg(noconfirm) - .arg("-") - .stdin(Stdio::piped()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn() - .expect("Failed to execute command"); + let mut child = Command::new(PACKAGE_MANAGER) + .arg("--color") + .arg("always") + .arg("-Syu") + .arg("--needed") + .arg(noconfirm) + .arg("-") + .stdin(Stdio::piped()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to execute command"); - if let Some(mut stdin) = child.stdin.take() { - for package in packages { - writeln!(stdin, "{}", package).unwrap(); - } - } + if let Some(mut stdin) = child.stdin.take() { + for package in packages { + writeln!(stdin, "{}", package).unwrap(); + } + } - let status = child.wait().expect("Failed to wait on child"); + let status = child.wait().expect("Failed to wait on child"); - if !status.success() { - return Err("Failed to upgrade & sync packages".into()); - } + if !status.success() { + return Err("Failed to upgrade & sync packages".into()); + } - println!("{} {}", "::".bold().green(), "Packages upgraded & synced".bold()); - Ok(()) + println!( + "{} {}", + "::".bold().green(), + "Packages upgraded & synced".bold() + ); + Ok(()) } diff --git a/src/packages/remove.rs b/src/packages/remove.rs index 47deffc..f716668 100644 --- a/src/packages/remove.rs +++ b/src/packages/remove.rs @@ -1,6 +1,58 @@ +use crate::{file, packages::get_package_path}; use colored::Colorize; +use std::io::Write; +use std::process::{Command, Stdio}; -pub fn remove(_package: Vec) -> Result<(), Box> { - println!("{} {}", "::".bold().green(), "Removing packages...".bold()); - todo!(); -} \ No newline at end of file +const PACKAGE_MANAGER: &str = "paru"; + +pub fn remove(unwanted_packages: Vec) -> Result<(), Box> { + println!("{} {}", "::".bold().green(), "Removing packages...".bold()); + + let packages = file::read_packages(get_package_path()); + + let mut packages = packages + .into_iter() + .filter(|p| !p.contains("#") && !p.is_empty()) + .collect::>(); + + let mut child = Command::new(PACKAGE_MANAGER) + .arg("--color") + .arg("always") + .arg("-R") + // .arg("--needed") + // .arg(noconfirm) + .arg("-") + .stdin(Stdio::piped()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to execute command"); + + if let Some(mut stdin) = child.stdin.take() { + for unwanted_package in unwanted_packages.clone() { + writeln!(stdin, "{}", unwanted_package).unwrap(); + } + } + + let status = child.wait().expect("Failed to wait on child"); + + if !status.success() { + return Err("Failed to remove packages".into()); + } + + println!("{} {}", "::".bold().green(), "Packages removed".bold()); + + println!( + "{} {}", + "::".bold().blue(), + "Updating package index...".bold() + ); + + for unwanted_package in unwanted_packages { + packages.retain(|p| *p != unwanted_package); + } + + file::write_packages(get_package_path(), &packages.join("\n")); + + Ok(()) +} diff --git a/src/packages/sync.rs b/src/packages/sync.rs index a8e0255..68f8ecf 100644 --- a/src/packages/sync.rs +++ b/src/packages/sync.rs @@ -1,48 +1,57 @@ use colored::Colorize; -use std::process::{Command, Stdio}; use std::io::Write; +use std::process::{Command, Stdio}; use crate::file; -use crate::packages::get_package_path; +use crate::packages::{ask_confirmation, get_package_path}; const PACKAGE_MANAGER: &str = "paru"; pub fn sync(noconfirm: bool) -> Result<(), Box> { - println!("{} {}", "::".bold().green(), "Syncing packages...".bold()); + println!("{} {}", "::".bold().green(), "Syncing packages...".bold()); - let packages = file::read_packages(get_package_path()); + if !ask_confirmation()? { + return Err("Operation aborted".into()); + } - let packages = packages.into_iter() - .filter(|p| !p.contains("#") && !p.is_empty()) - .collect::>(); + let packages = file::read_packages(get_package_path()); - let noconfirm = if noconfirm { "--noconfirm" } else { "--confirm" }; + let packages = packages + .into_iter() + .filter(|p| !p.contains("#") && !p.is_empty()) + .collect::>(); - let mut child = Command::new(PACKAGE_MANAGER) - .arg("--color") - .arg("always") - .arg("-S") - .arg("--needed") - .arg(noconfirm) - .arg("-") - .stdin(Stdio::piped()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn() - .expect("Failed to execute command"); + let noconfirm = if noconfirm { + "--noconfirm" + } else { + "--confirm" + }; - if let Some(mut stdin) = child.stdin.take() { - for package in packages { - writeln!(stdin, "{}", package).unwrap(); - } - } + let mut child = Command::new(PACKAGE_MANAGER) + .arg("--color") + .arg("always") + .arg("-S") + .arg("--needed") + .arg(noconfirm) + .arg("-") + .stdin(Stdio::piped()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to execute command"); - let status = child.wait().expect("Failed to wait on child"); + if let Some(mut stdin) = child.stdin.take() { + for package in packages { + writeln!(stdin, "{}", package).unwrap(); + } + } - if !status.success() { - return Err("Failed to sync packages".into()); - } + let status = child.wait().expect("Failed to wait on child"); - println!("{} {}", "::".bold().green(), "Packages synced".bold()); - Ok(()) -} \ No newline at end of file + if !status.success() { + return Err("Failed to sync packages".into()); + } + + println!("{} {}", "::".bold().green(), "Packages synced".bold()); + Ok(()) +} diff --git a/src/packages/upgrade.rs b/src/packages/upgrade.rs index ff1d5c8..3241ab5 100644 --- a/src/packages/upgrade.rs +++ b/src/packages/upgrade.rs @@ -1,27 +1,37 @@ use colored::Colorize; use std::process::Command; +use crate::packages::ask_confirmation; + const PACKAGE_MANAGER: &str = "paru"; pub fn upgrade(noconfirm: bool) -> Result<(), Box> { - println!("{} {}", "::".bold().green(), "Upgrading packages...".bold()); + println!("{} {}", "::".bold().green(), "Upgrading packages...".bold()); - let noconfirm = if noconfirm { "--noconfirm" } else { "--confirm" }; + if !ask_confirmation()? { + return Err("Operation aborted".into()); + } - let mut child = Command::new(PACKAGE_MANAGER) - .arg("--color") - .arg("always") - .arg("-Syu") - .arg(noconfirm) - .spawn() - .expect("Failed to execute command"); + let noconfirm = if noconfirm { + "--noconfirm" + } else { + "--confirm" + }; - let status = child.wait().expect("Failed to wait on child"); + let mut child = Command::new(PACKAGE_MANAGER) + .arg("--color") + .arg("always") + .arg("-Syu") + .arg(noconfirm) + .spawn() + .expect("Failed to execute command"); - if !status.success() { - return Err("Failed to upgrade packages".into()); - } + let status = child.wait().expect("Failed to wait on child"); - println!("{} {}", "::".bold().green(), "Packages upgraded".bold()); - Ok(()) -} \ No newline at end of file + if !status.success() { + return Err("Failed to upgrade packages".into()); + } + + println!("{} {}", "::".bold().green(), "Packages upgraded".bold()); + Ok(()) +}