context: add context
All checks were successful
CI / build (push) Successful in 1m50s

pkh context allows to manage contexts (local, ssh) and run contextualized commands (deb)

in other words, this allows building binary packages over ssh
This commit is contained in:
2025-12-15 20:48:44 +01:00
parent ad98d9c1ab
commit 1d65d1ce31
9 changed files with 789 additions and 12 deletions

117
src/context/manager.rs Normal file
View File

@@ -0,0 +1,117 @@
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::PathBuf;
use super::api::Context;
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Config {
pub current_context: Option<String>,
pub contexts: HashMap<String, Context>,
}
pub struct ContextManager {
config_path: PathBuf,
config: Config,
}
impl ContextManager {
pub fn new() -> io::Result<Self> {
let proj_dirs = ProjectDirs::from("com", "pkh", "pkh").ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"Could not determine config directory",
)
})?;
let config_dir = proj_dirs.config_dir();
fs::create_dir_all(config_dir)?;
let config_path = config_dir.join("contexts.json");
let config = if config_path.exists() {
let content = fs::read_to_string(&config_path)?;
serde_json::from_str(&content)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
} else {
let mut cfg = Config::default();
cfg.contexts.insert("local".to_string(), Context::Local);
cfg.current_context = Some("local".to_string());
cfg
};
Ok(Self {
config_path,
config,
})
}
pub fn with_path(path: PathBuf) -> Self {
Self {
config_path: path,
config: Config::default(),
}
}
pub fn save(&self) -> io::Result<()> {
let content = serde_json::to_string_pretty(&self.config)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
fs::write(&self.config_path, content)?;
Ok(())
}
pub fn list_contexts(&self) -> Vec<String> {
self.config.contexts.keys().cloned().collect()
}
pub fn get_context(&self, name: &str) -> Option<&Context> {
self.config.contexts.get(name)
}
pub fn add_context(&mut self, name: &str, context: Context) -> io::Result<()> {
self.config.contexts.insert(name.to_string(), context);
self.save()
}
pub fn remove_context(&mut self, name: &str) -> io::Result<()> {
if self.config.contexts.remove(name).is_some() {
if self.config.current_context.as_deref() == Some(name) {
self.config.current_context = Some("local".to_string());
if !self.config.contexts.contains_key("local") {
self.config
.contexts
.insert("local".to_string(), Context::Local);
}
}
self.save()?;
}
Ok(())
}
pub fn set_current(&mut self, name: &str) -> io::Result<()> {
if self.config.contexts.contains_key(name) {
self.config.current_context = Some(name.to_string());
self.save()?;
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Context '{}' not found", name),
))
}
}
pub fn current(&self) -> Context {
self.config
.current_context
.as_deref()
.and_then(|name| self.config.contexts.get(name))
.cloned()
.unwrap_or(Context::Local)
}
pub fn current_name(&self) -> Option<String> {
self.config.current_context.clone()
}
}