From 2b702a8b91588873a663ecd9af249e1dc1a47160 Mon Sep 17 00:00:00 2001 From: BanceDev Date: Wed, 25 Feb 2026 20:44:04 -0500 Subject: begin work on add and remove --- src/action.rs | 105 ++++++++++++++++++++++++++++++++++++++++++---------------- src/main.rs | 12 +++++-- src/util.rs | 37 +++++++++++++++++++++ 3 files changed, 122 insertions(+), 32 deletions(-) create mode 100644 src/util.rs (limited to 'src') diff --git a/src/action.rs b/src/action.rs index 55e5685..63e8676 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,5 +1,10 @@ +use crate::util::{self, yn_prompt}; +use git2::Repository; +use std::fs; +use std::path::PathBuf; + pub enum Action { - Add { repo: String }, + Add { url: String }, Update, Upgrade { packages: Vec }, Autoremove, @@ -12,16 +17,12 @@ pub enum Action { impl Action { pub fn parse(args: &[String]) -> Result { - let cmd = args.get(1) - .ok_or("no command provided")? - .as_str(); + let cmd = args.get(1).ok_or("no command provided")?.as_str(); match cmd { "add" => { - let repo = args.get(2) - .ok_or("add requires ")? - .clone(); - Ok(Action::Add { repo }) + let url = args.get(2).ok_or("add requires ")?.clone(); + Ok(Action::Add { url }) } "update" => Ok(Action::Update), "upgrade" => { @@ -40,9 +41,7 @@ impl Action { } "list" => Ok(Action::List), "search" => { - let term = args.get(2) - .ok_or("search requires ")? - .clone(); + let term = args.get(2).ok_or("search requires ")?.clone(); Ok(Action::Search { term }) } "clean" => { @@ -50,32 +49,42 @@ impl Action { Ok(Action::Clean { packages }) } "show" => { - let package = args.get(2) - .ok_or("show requires ")? - .clone(); + let package = args.get(2).ok_or("show requires ")?.clone(); Ok(Action::Show { package }) } _ => Err(format!("unknown command {}", cmd)), } } - pub fn execute(self) { + pub fn execute(self) -> Result<(), String> { match self { - Action::Add { repo } => add(repo), - Action::Update => update(), - Action::Upgrade { packages } => upgrade(packages), - Action::Autoremove => autoremove(), + Action::Add { url } => add(url.as_str()), + Action::Update => Ok(update()), + Action::Upgrade { packages } => Ok(upgrade(packages)), + Action::Autoremove => Ok(autoremove()), Action::Remove { packages } => remove(packages), - Action::List => list(), - Action::Search { term } => search(term), - Action::Clean { packages } => clean(packages), - Action::Show { package } => show(package), + Action::List => Ok(list()), + Action::Search { term } => Ok(search(term)), + Action::Clean { packages } => Ok(clean(packages)), + Action::Show { package } => Ok(show(package)), } } } -fn add(repo: String) { - println!("adding {}", repo); +fn add(url: &str) -> Result<(), String> { + let base_path = "/var/lib/forge"; + let repo_name = { + let last_segment = url.rsplit('/').next().unwrap_or(url); + last_segment.strip_suffix(".git").unwrap_or(last_segment) + }; + let clone_path = PathBuf::from(base_path).join(repo_name); + let repo = Repository::clone(url, &clone_path) + .map_err(|e| format!("failed to clone {}: {}", repo_name, e))?; + println!( + "new package initialized at: {}", + repo.path().to_str().unwrap() + ); + Ok(()) } fn update() { @@ -92,10 +101,49 @@ fn autoremove() { println!("autoremoving"); } -fn remove(packages: Vec) { - for (_, p) in packages.iter().enumerate() { - println!("removing: {}", p); +fn remove(packages: Vec) -> Result<(), String> { + let base_path = "/var/lib/forge"; + println!("checking dependencies...\n"); + let package_paths: Vec<(String, PathBuf)> = packages + .into_iter() + .map(|p| { + let path = PathBuf::from(base_path).join(&p); + if !path.exists() { + Err(format!("no installed package: {}", p)) + } else { + Ok((p, path)) + } + }) + .collect::>()?; // propagates the first error + + println!( + "Packages to remove ({}): {}\n", + package_paths.len(), + package_paths + .iter() + .map(|(p, _)| p.as_str()) + .collect::>() + .join(", ") + ); + + let total_size: u64 = package_paths + .iter() + .map(|(_, path)| util::dir_size(path).unwrap_or(0)) + .sum(); + + println!( + "Total remove size: {:.2} MB\n", + total_size as f64 / (1024.0 * 1024.0) + ); + + if yn_prompt("Proceed with removal?") { + for (name, path) in package_paths { + fs::remove_dir_all(&path).map_err(|e| format!("failed to remove {}: {}", name, e))?; + println!("Removed {}", name); + } } + + Ok(()) } fn list() { @@ -115,4 +163,3 @@ fn clean(packages: Vec) { fn show(package: String) { println!("showing {}", package); } - diff --git a/src/main.rs b/src/main.rs index e9bb749..ab6f558 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,19 @@ -use std::env; use action::Action; +use std::env; mod action; +mod util; -fn main() { +fn main() -> std::io::Result<()> { let args: Vec = env::args().collect(); match Action::parse(&args) { - Ok(action) => action.execute(), + Ok(action) => { + if let Err(e) = action.execute() { + eprintln!("forge: {}", e); + } + } Err(e) => eprintln!("forge: {}", e), } + Ok(()) } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..c3d573c --- /dev/null +++ b/src/util.rs @@ -0,0 +1,37 @@ +use std::fs; +use std::io; +use std::io::Write; +use std::path::Path; + +pub fn dir_size(path: &Path) -> std::io::Result { + let mut size = 0; + if path.is_dir() { + for entry in fs::read_dir(path)? { + let entry = entry?; + let metadata = entry.metadata()?; + if metadata.is_file() { + size += metadata.len(); + } else if metadata.is_dir() { + size += dir_size(&entry.path())?; + } + } + } + Ok(size) +} + +pub fn yn_prompt(prompt: &str) -> bool { + print!("{} [y/n]: ", prompt); + io::stdout().flush().unwrap(); + + // Read input from user + let mut input = String::new(); + io::stdin().read_line(&mut input).unwrap(); + + // Normalize input + let input = input.trim().to_lowercase(); + + match input.as_str() { + "y" | "yes" | "" => true, + _ => false, + } +} -- cgit v1.2.3-59-g8ed1b