aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar BanceDev 2026-02-27 00:41:20 -0500
committerGravatar BanceDev 2026-02-27 00:41:20 -0500
commitf19dd10caff1d6393bffb11189c7467e268a33df (patch)
tree06e4d9c9b7aeb7771aa0ad189816851094cee54d /src
parentadd version flag (diff)
add the clean subcommand
Diffstat (limited to 'src')
-rw-r--r--src/action.rs70
-rw-r--r--src/config.rs30
2 files changed, 88 insertions, 12 deletions
diff --git a/src/action.rs b/src/action.rs
index fe405a3..a9c2563 100644
--- a/src/action.rs
+++ b/src/action.rs
@@ -2,7 +2,7 @@ use crate::config::{self, ConfigCommand, TEMP_CONFIG_PATH, create_config};
use crate::util::{dir_size, get_editor, open_in_editor, yn_prompt};
use git2::Repository;
use std::fs;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
const BASE_REPO_PATH: &str = "/var/db/forge";
const BASE_CONFIG_PATH: &str = "/etc/forge/packages";
@@ -71,7 +71,7 @@ impl Action {
Action::Remove { packages } => remove(packages),
Action::List => list(),
Action::Search { term } => Ok(search(term)),
- Action::Clean { packages } => Ok(clean(packages)),
+ Action::Clean { packages } => clean(packages),
Action::Show { package } => Ok(show(package)),
Action::Version => Ok(version()),
}
@@ -192,8 +192,7 @@ fn remove(packages: Vec<String>) -> Result<(), String> {
}
fn list() -> Result<(), String> {
- let config_path = Path::new(BASE_CONFIG_PATH);
- for entry in fs::read_dir(config_path)
+ for entry in fs::read_dir(BASE_CONFIG_PATH)
.map_err(|e| format!("failed to iterate package directory: {}", e))?
{
let entry = entry.map_err(|e| e.to_string())?;
@@ -211,10 +210,67 @@ fn search(term: String) {
println!("searching: {}", term);
}
-fn clean(packages: Vec<String>) {
- for (_, p) in packages.iter().enumerate() {
- println!("cleaning: {}", p);
+fn clean(packages: Vec<String>) -> Result<(), String> {
+ if !nix::unistd::geteuid().is_root() {
+ return Err("clean must be run as root".to_string());
+ }
+
+ let package_paths: Vec<(String, PathBuf, PathBuf)> = if packages.is_empty() {
+ fs::read_dir(BASE_CONFIG_PATH)
+ .map_err(|e| format!("failed to iterate package directory: {}", e))?
+ .map(|p| {
+ let entry = p.map_err(|e| e.to_string())?;
+ let path = entry.path();
+
+ let pkgname = path
+ .file_stem()
+ .ok_or_else(|| format!("invalid filename: {:?}", path))?
+ .to_string_lossy()
+ .into_owned();
+
+ let path = PathBuf::from(BASE_REPO_PATH).join(&pkgname);
+ let cfg_path = PathBuf::from(BASE_CONFIG_PATH).join(format!("{}.toml", &pkgname));
+
+ if !path.exists() || !cfg_path.exists() {
+ Err(format!("no installed package: {}", pkgname))
+ } else {
+ Ok((pkgname, path, cfg_path))
+ }
+ })
+ .collect::<Result<_, _>>()?
+ } else {
+ packages
+ .into_iter()
+ .map(|p| {
+ 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, cfg_path))
+ }
+ })
+ .collect::<Result<_, _>>()?
+ };
+
+ println!(
+ "Packages to clean ({}): {}\n",
+ package_paths.len(),
+ package_paths
+ .iter()
+ .map(|(p, _, _)| p.as_str())
+ .collect::<Vec<_>>()
+ .join(", ")
+ );
+
+ if yn_prompt("Proceed with cleanup?") {
+ for (name, path, cfg_path) in package_paths {
+ config::run_config_command(&cfg_path, &path, ConfigCommand::Clean)?;
+ println!("Cleaned {}", name);
+ }
}
+
+ Ok(())
}
fn show(package: String) {
diff --git a/src/config.rs b/src/config.rs
index ecada2a..8e3437c 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -9,15 +9,26 @@ pub enum ConfigCommand {
Build,
Install,
Uninstall,
+ PostInstall,
+ PostUninstall,
+ Clean,
}
#[derive(Deserialize)]
pub struct Config {
pub update: Option<String>,
+ pub dependencies: Option<Vec<String>>,
+ pub hooks: Option<Hooks>,
+}
+
+#[derive(Deserialize)]
+pub struct Hooks {
pub build: Option<String>,
pub install: Option<String>,
pub uninstall: Option<String>,
- pub dependencies: Option<Vec<String>>,
+ pub post_install: Option<String>,
+ pub post_uninstall: Option<String>,
+ pub clean: Option<String>,
}
impl Config {
@@ -48,10 +59,15 @@ pub fn create_config(package: &str) -> Result<(), String> {
let template = format!(
r#"# {package} configuration
update = "tagged" # no | live | tagged
+dependencies = []
+
+[hooks]
build = "make"
install = "make install"
uninstall = "make uninstall"
-dependencies = []
+post_install = ""
+post_uninstall = ""
+clean = "make clean"
"#
);
@@ -66,11 +82,15 @@ pub fn run_config_command(
command: ConfigCommand,
) -> Result<(), String> {
let config = Config::new(config_path).ok_or("config not found".to_string())?;
+ let hooks = config.hooks.ok_or("no hooks section".to_string())?;
let cmd = match command {
- ConfigCommand::Build => config.build,
- ConfigCommand::Install => config.install,
- ConfigCommand::Uninstall => config.uninstall,
+ ConfigCommand::Build => hooks.build,
+ ConfigCommand::Install => hooks.install,
+ ConfigCommand::Uninstall => hooks.uninstall,
+ ConfigCommand::PostInstall => hooks.post_install,
+ ConfigCommand::PostUninstall => hooks.post_uninstall,
+ ConfigCommand::Clean => hooks.clean,
};
if let Some(c) = cmd {