add:add command + add existing
This commit is contained in:
55
src/services/config_service.rs
Normal file
55
src/services/config_service.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
config::{
|
||||
defaults::{config_dir, config_file, default_bin_dir, scripts_dir},
|
||||
types::Config,
|
||||
},
|
||||
error::AppError,
|
||||
};
|
||||
|
||||
pub struct ConfigService {
|
||||
pub config_dir: PathBuf,
|
||||
pub scripts_dir: PathBuf,
|
||||
pub config_path: PathBuf,
|
||||
}
|
||||
|
||||
impl ConfigService {
|
||||
pub fn new() -> Result<Self, AppError> {
|
||||
Ok(Self {
|
||||
config_dir: config_dir()?,
|
||||
scripts_dir: scripts_dir()?,
|
||||
config_path: config_file()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates all required directories and a default `config.toml` if they do
|
||||
/// not already exist. Safe to call on every startup.
|
||||
pub fn ensure_initialized(&self) -> Result<(), AppError> {
|
||||
fs::create_dir_all(&self.config_dir)?;
|
||||
fs::create_dir_all(&self.scripts_dir)?;
|
||||
|
||||
if !self.config_path.exists() {
|
||||
let default_config = Config {
|
||||
bin_dir: default_bin_dir()?,
|
||||
default_shell: "bash".to_string(),
|
||||
};
|
||||
let contents = toml::to_string_pretty(&default_config)?;
|
||||
fs::write(&self.config_path, contents)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(&self) -> Result<Config, AppError> {
|
||||
let contents = fs::read_to_string(&self.config_path)?;
|
||||
let config = toml::from_str(&contents)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn save(&self, config: &Config) -> Result<(), AppError> {
|
||||
let contents = toml::to_string_pretty(config)?;
|
||||
fs::write(&self.config_path, contents)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
55
src/services/path_service.rs
Normal file
55
src/services/path_service.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Result of checking whether a name collides with an existing executable on `$PATH`.
|
||||
#[derive(Debug)]
|
||||
pub enum CollisionResult {
|
||||
/// No conflict found — safe to use this name.
|
||||
Clear,
|
||||
/// A file with this name already exists at the given path.
|
||||
/// The caller decides whether to warn and proceed or re-prompt.
|
||||
Collision { path: PathBuf },
|
||||
}
|
||||
|
||||
pub struct PathService {
|
||||
/// Our own bin_dir — shims we've written here are excluded from collision results
|
||||
/// so we don't report our own scripts as conflicts.
|
||||
bin_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl PathService {
|
||||
pub fn new(bin_dir: PathBuf) -> Self {
|
||||
Self { bin_dir }
|
||||
}
|
||||
|
||||
/// Walks every directory in `$PATH` looking for an executable named `name`.
|
||||
/// Skips our own `bin_dir` so previously registered shims don't self-collide.
|
||||
pub fn check_collision(&self, name: &str) -> CollisionResult {
|
||||
let path_var = env::var("PATH").unwrap_or_default();
|
||||
|
||||
for dir in env::split_paths(&path_var) {
|
||||
// Skip our own bin_dir — our shims live here
|
||||
if dir == self.bin_dir {
|
||||
continue;
|
||||
}
|
||||
|
||||
let candidate = dir.join(name);
|
||||
if is_executable(&candidate) {
|
||||
return CollisionResult::Collision { path: candidate };
|
||||
}
|
||||
}
|
||||
|
||||
CollisionResult::Clear
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the path exists, is a file, and has at least one executable bit set.
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
path.metadata()
|
||||
.map(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
53
src/services/registry_service.rs
Normal file
53
src/services/registry_service.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
config::{defaults::registry_file, types::{Registry, ScriptEntry}},
|
||||
error::AppError,
|
||||
};
|
||||
|
||||
pub struct RegistryService {
|
||||
pub registry_path: PathBuf,
|
||||
}
|
||||
|
||||
impl RegistryService {
|
||||
pub fn new() -> Result<Self, AppError> {
|
||||
Ok(Self {
|
||||
registry_path: registry_file()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an empty `registry.toml` if it does not already exist.
|
||||
pub fn ensure_initialized(&self) -> Result<(), AppError> {
|
||||
if !self.registry_path.exists() {
|
||||
let empty = Registry::default();
|
||||
let contents = toml::to_string_pretty(&empty)?;
|
||||
fs::write(&self.registry_path, contents)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(&self) -> Result<Registry, AppError> {
|
||||
let contents = fs::read_to_string(&self.registry_path)?;
|
||||
let registry = toml::from_str(&contents)?;
|
||||
Ok(registry)
|
||||
}
|
||||
|
||||
pub fn save(&self, registry: &Registry) -> Result<(), AppError> {
|
||||
let contents = toml::to_string_pretty(registry)?;
|
||||
fs::write(&self.registry_path, contents)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Appends a new entry and persists the registry.
|
||||
pub fn add(&self, entry: ScriptEntry) -> Result<(), AppError> {
|
||||
let mut registry = self.load()?;
|
||||
registry.scripts.push(entry);
|
||||
self.save(®istry)
|
||||
}
|
||||
|
||||
/// Returns true if a script with the given name is already registered.
|
||||
pub fn name_exists(&self, name: &str) -> Result<bool, AppError> {
|
||||
let registry = self.load()?;
|
||||
Ok(registry.scripts.iter().any(|s| s.name == name))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user