diff options
| author | 2026-02-26 00:29:43 -0500 | |
|---|---|---|
| committer | 2026-02-26 00:29:43 -0500 | |
| commit | 477f40bf969c6de2955cdaaaa571c7b7e9838f06 (patch) | |
| tree | e1eea562765cf10adee9801fd214929e22b7cdbd /src | |
| parent | begin work on add and remove (diff) | |
updates to add, remove and list
Diffstat (limited to 'src')
| -rw-r--r-- | src/action.rs | 94 | ||||
| -rw-r--r-- | src/main.rs | 9 | ||||
| -rw-r--r-- | src/util.rs | 56 |
3 files changed, 135 insertions, 24 deletions
diff --git a/src/action.rs b/src/action.rs index 63e8676..cd3b32b 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,7 +1,10 @@ -use crate::util::{self, yn_prompt}; +use crate::util::{TEMP_CONFIG_PATH, create_config, dir_size, get_editor, is_root, open_in_editor, yn_prompt}; use git2::Repository; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; + +const BASE_REPO_PATH: &str = "/var/db/forge"; +const BASE_CONFIG_PATH: &str = "/etc/forge/packages"; pub enum Action { Add { url: String }, @@ -63,7 +66,7 @@ impl Action { Action::Upgrade { packages } => Ok(upgrade(packages)), Action::Autoremove => Ok(autoremove()), Action::Remove { packages } => remove(packages), - Action::List => Ok(list()), + Action::List => list(), Action::Search { term } => Ok(search(term)), Action::Clean { packages } => Ok(clean(packages)), Action::Show { package } => Ok(show(package)), @@ -72,18 +75,47 @@ impl Action { } fn add(url: &str) -> Result<(), String> { - let base_path = "/var/lib/forge"; + if !is_root() { + return Err("add must be run as root".to_string()); + } + 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))?; + let config_name = format!("{repo_name}.toml"); + println!( - "new package initialized at: {}", + "Creating config: {}", + config_name + ); + create_config(repo_name)?; + + let editor = get_editor(); + let config_temp = format!("{}/{}", TEMP_CONFIG_PATH, config_name); + open_in_editor(&editor, &config_temp)?; + + let clone_path = PathBuf::from(BASE_REPO_PATH).join(repo_name); + let repo = Repository::clone(url, &clone_path).map_err(|e| { + format!("failed to clone {}: {}", repo_name, e) + })?; + + let mut config_path = PathBuf::from(BASE_CONFIG_PATH); + if !config_path.exists() { + fs::create_dir_all(&config_path).map_err(|e| { + format!("failed to create config directory: {}", e) + })?; + } + + config_path.push(config_name); + + fs::rename(config_temp, config_path).map_err(|e| format!("failed to place config in system directory: {}", e))?; + + println!( + "New package initialized at: {}", repo.path().to_str().unwrap() ); + Ok(()) } @@ -102,33 +134,37 @@ fn autoremove() { } fn remove(packages: Vec<String>) -> Result<(), String> { - let base_path = "/var/lib/forge"; - println!("checking dependencies...\n"); - let package_paths: Vec<(String, PathBuf)> = packages + if !is_root() { + return Err("remove must be run as root".to_string()); + } + + println!("Checking dependencies...\n"); + let package_paths: Vec<(String, PathBuf, PathBuf)> = packages .into_iter() .map(|p| { - let path = PathBuf::from(base_path).join(&p); - if !path.exists() { + let path = PathBuf::from(BASE_REPO_PATH).join(&p); + let cfg_path = PathBuf::from(BASE_CONFIG_PATH).join(format!("{}.toml", p)); + if !path.exists() || !cfg_path.exists() { Err(format!("no installed package: {}", p)) } else { - Ok((p, path)) + Ok((p, path, cfg_path)) } }) - .collect::<Result<_, _>>()?; // propagates the first error + .collect::<Result<_, _>>()?; println!( "Packages to remove ({}): {}\n", package_paths.len(), package_paths .iter() - .map(|(p, _)| p.as_str()) + .map(|(p, _, _)| p.as_str()) .collect::<Vec<_>>() .join(", ") ); let total_size: u64 = package_paths .iter() - .map(|(_, path)| util::dir_size(path).unwrap_or(0)) + .map(|(_, path, _)| dir_size(path).unwrap_or(0)) .sum(); println!( @@ -137,8 +173,13 @@ fn remove(packages: Vec<String>) -> Result<(), String> { ); 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))?; + for (name, path, cfg_path) in package_paths { + fs::remove_dir_all(&path).map_err(|e| { + format!("failed to remove {}: {}", name, e) + })?; + fs::remove_file(&cfg_path).map_err(|e| { + format!("failed to remove {}: {}", name, e) + })?; println!("Removed {}", name); } } @@ -146,8 +187,19 @@ fn remove(packages: Vec<String>) -> Result<(), String> { Ok(()) } -fn list() { - println!("listing"); +fn list() -> Result<(), String> { + let config_path = Path::new(BASE_CONFIG_PATH); + for entry in fs::read_dir(config_path) + .map_err(|e| format!("failed to iterate package directory: {}", e))? { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + if path.is_file() { + if let Some(stem) = path.file_stem() { + println!("{}", stem.to_string_lossy()); + } + } + } + Ok(()) } fn search(term: String) { diff --git a/src/main.rs b/src/main.rs index ab6f558..ff71155 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ use action::Action; -use std::env; +use std::{env, fs}; + +use crate::util::TEMP_CONFIG_PATH; mod action; mod util; -fn main() -> std::io::Result<()> { +fn main() { let args: Vec<String> = env::args().collect(); match Action::parse(&args) { @@ -15,5 +17,6 @@ fn main() -> std::io::Result<()> { } Err(e) => eprintln!("forge: {}", e), } - Ok(()) + // eat the error because end user doesn't care about cleanup + let _ = fs::remove_dir_all(TEMP_CONFIG_PATH); } diff --git a/src/util.rs b/src/util.rs index c3d573c..ae114d0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,41 @@ +use std::env; use std::fs; use std::io; use std::io::Write; use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use libc; + +pub const TEMP_CONFIG_PATH: &str = "/var/lib/forge/.tmp"; + +pub fn create_config(package: &str) -> Result<(), String> { + let filename = format!("{package}.toml"); + let mut path = PathBuf::from(TEMP_CONFIG_PATH); + + if !path.exists() { + fs::create_dir_all(&path).map_err(|e| { + format!("failed to create temp config directory: {}", e) + })?; + } + + path.push(filename); + + let template = format!( + r#"# {package} configuration +update = "tagged" # no | live | tagged +build = "make" +dependencies = [] +install = "make install" + "# + ); + + fs::write(path, template).map_err(|e| { + format!("failed to create config: {}", e) + })?; + + Ok(()) +} pub fn dir_size(path: &Path) -> std::io::Result<u64> { let mut size = 0; @@ -19,6 +53,28 @@ pub fn dir_size(path: &Path) -> std::io::Result<u64> { Ok(size) } +pub fn get_editor() -> String { + env::var("VISUAL") + .or_else(|_| env::var("EDITOR")) + .unwrap_or_else(|_| "nano".to_string()) +} + +pub fn is_root() -> bool { + unsafe { libc::geteuid() == 0} +} + +pub fn open_in_editor(editor: &str, file: &str) -> Result<(), String> { + let status = Command::new(editor).arg(file).status().map_err(|e| { + format!("failed to execute editor: {}", e) + })?; + + if !status.success() { + return Err(format!("editor exited with non-zero status: {}", status)); + } + + Ok(()) +} + pub fn yn_prompt(prompt: &str) -> bool { print!("{} [y/n]: ", prompt); io::stdout().flush().unwrap(); |
