aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar BanceDev 2026-02-26 00:29:43 -0500
committerGravatar BanceDev 2026-02-26 00:29:43 -0500
commit477f40bf969c6de2955cdaaaa571c7b7e9838f06 (patch)
treee1eea562765cf10adee9801fd214929e22b7cdbd /src
parentbegin work on add and remove (diff)
updates to add, remove and list
Diffstat (limited to 'src')
-rw-r--r--src/action.rs94
-rw-r--r--src/main.rs9
-rw-r--r--src/util.rs56
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();