From 2bdf100ed627d4d6443d58fb3304dee534143c07 Mon Sep 17 00:00:00 2001 From: pyro Date: Wed, 20 May 2026 17:31:42 -0500 Subject: [PATCH] added distrobox logic, and protomotion logic --- src/funcs.rs | 16 +++++- src/lib.rs | 153 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 146 insertions(+), 23 deletions(-) diff --git a/src/funcs.rs b/src/funcs.rs index 61d3c88..ce86d6e 100644 --- a/src/funcs.rs +++ b/src/funcs.rs @@ -35,6 +35,11 @@ pub fn run_tui( let mut terminal = Terminal::new(backend)?; let (event_tx, event_rx) = channel::(); let input_tx = event_tx.clone(); + let mut project_list_state = ListState::default(); + if !state.projects.is_empty() { + project_list_state.select(Some(0)); + state.selected_project = 0; + } std::thread::spawn(move || { loop { if event::poll(Duration::from_millis(100)).unwrap_or(false) { @@ -169,6 +174,9 @@ pub fn run_tui( state.output.push("Error: USAGE -> np ".into()); } } + "current_project" | "cp" => { + let _ = state.execute_command(command, None); + } command_name => { if state.module_loader.commands.contains_key(command_name) { state.output.push(format!( @@ -221,7 +229,9 @@ pub fn run_tui( { if let Some(selected) = project_list_state.selected() { if selected > 0 { - project_list_state.select(Some(selected - 1)); + let new_index = selected - 1; + project_list_state.select(Some(new_index)); + state.selected_project = new_index; } } } @@ -232,7 +242,9 @@ pub fn run_tui( { if let Some(selected) = project_list_state.selected() { if selected + 1 < state.projects.len() { - project_list_state.select(Some(selected + 1)); + let new_index = selected + 1; + project_list_state.select(Some(new_index)); + state.selected_project = new_index; } } } diff --git a/src/lib.rs b/src/lib.rs index a341010..53c6f8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ +use fs_extra::dir::{CopyOptions, copy}; use ratatui::crossterm::event; use rhai::{AST, Dynamic, Engine, Scope}; use std::collections::HashMap; use std::error::Error; -use std::fs::{File, create_dir_all, read_dir, read_to_string}; +use std::fs::{File, create_dir_all, read_dir, read_to_string, remove_dir_all}; use std::io::{self, Write}; use std::path::PathBuf; +use std::process::Command; use std::sync::mpsc::Receiver; use std::sync::{Arc, mpsc::Sender, mpsc::channel}; @@ -22,7 +24,7 @@ pub struct AppState { pub log: Vec, pub output: Vec, pub selected_server: Option, - pub selected_project: Option, + pub selected_project: usize, pub curent_intput: String, pub module_loader: ModuleLoader, pub output_scroll: u16, @@ -47,7 +49,7 @@ impl AppState { log: Vec::new(), output: Vec::new(), selected_server: None, - selected_project: None, + selected_project: 0, curent_intput: String::new(), module_loader: ModuleLoader::new(), output_scroll: 0, @@ -145,6 +147,48 @@ impl AppState { )))?; } } + "current_project" | "cp" => { + let mut out_vec = Vec::new(); + out_vec.push(format!( + "org: {}", + &self.projects[self.selected_project].org_name + )); + out_vec.push(format!( + "name: {}", + &self.projects[self.selected_project].name + )); + out_vec.push(format!( + "files: {}", + &self.projects[self.selected_project].files.display() + )); + out_vec.push(format!( + "notes: {}", + &self.projects[self.selected_project].notes.display() + )); + if let Some(db) = self.projects[self.selected_project].db.clone() { + out_vec.push(format!("distrobox: {}", db.name)); + } + if self.projects[self.selected_project].current { + out_vec.push(format!("status: current")); + } else { + out_vec.push(format!("status: upcoming")); + } + for line in out_vec { + let _ = self.main_tx.send(ToolMessage::Output(line)); + } + } + "promote_project" | "pp" => { + match self.promote_project() { + Ok(out) => { + let _ = self.main_tx.send(ToolMessage::Output(out)); + } + Err(e) => { + let _ = self + .main_tx + .send(ToolMessage::Output(format!("error promoting project! {e}"))); + } + }; + } _ => { let ast = self .module_loader @@ -164,9 +208,7 @@ impl AppState { for arg_requirement in &cmd_meta.args { match arg_requirement.as_str() { "project" => { - if let Some(ref proj) = self.selected_project { - scope.push("project", proj.clone()); - } + scope.push("project", self.projects[self.selected_project].clone()); } "projects" => { let mut arr = rhai::Array::new(); @@ -176,16 +218,10 @@ impl AppState { scope.push("projects", arr); } "host" => { - if let Some(host) = - self.selected_project.as_ref().and_then(|p| p.hosts.first()) - { - scope.push("host", host.clone()); - } + scope.push("host", "todo"); } "hosts" => { - if let Some(ref proj) = self.selected_project { - scope.push("hosts", proj.hosts.clone()); - } + scope.push("hosts", self.projects[self.selected_project].clone()); } "config" => { let mut map = rhai::Map::new(); @@ -248,19 +284,30 @@ impl AppState { pub fn new_project(&mut self, org: String, name: String) -> Result<(), Box> { let template_box = self.config.get("template_box").unwrap(); + let tools_dir = self.config.get("tools").unwrap(); let project_files = PathBuf::from(self.config.get("upcoming_files").unwrap()) .join(format!("{}/{}", org, name)); let project_notes = PathBuf::from(self.config.get("upcoming_notes").unwrap()) .join(format!("{}/{}", org, name)); create_dir_all(&project_files)?; create_dir_all(&project_notes)?; + let db = DistroBox { + name: format!("{}-{}-{}", template_box, org, name), + volumes: vec![ + project_files.display().to_string(), + project_notes.display().to_string(), + tools_dir.clone(), + ], + created: false, + template: template_box.clone(), + }; let mut new_project = Project::new(); let mut project_conf_folder = self.config_file.clone(); project_conf_folder.pop(); project_conf_folder.push(format!("projects/{}-{}", org, name)); new_project.config_folder(project_conf_folder); - new_project.box_name(format!("{}-{}-{}", template_box, org, name)); new_project.name = name; + new_project.db = Some(db); new_project.org_name(org); new_project.files(project_files); new_project.notes(project_notes); @@ -268,6 +315,44 @@ impl AppState { self.projects.push(new_project); return Ok(()); } + + pub fn promote_project(&mut self) -> Result> { + let mut return_string = String::from("Project promoted successfully!"); + let mut new_project = self.projects[self.selected_project].clone(); + let new_files_path = PathBuf::from(self.config.get("current_files").unwrap()) + .join(format!("{}/{}", new_project.org_name, new_project.name)); + let new_notes_path = PathBuf::from(self.config.get("current_notes").unwrap()) + .join(format!("{}/{}", new_project.org_name, new_project.name)); + let mut options = CopyOptions::new(); + options.overwrite = true; + copy(new_project.files.clone(), new_files_path.clone(), &options)?; + copy(new_project.notes.clone(), new_notes_path.clone(), &options)?; + let cleanup_res = Command::new("rm") + .arg("-rf") + .arg(new_project.files.display().to_string()) + .arg(new_project.notes.display().to_string()) + .status(); + if cleanup_res.is_err() { + return_string = format!( + "Error deleting {} and {}, please clean up manually. Otherwise Project promotion succeeded!", + new_files_path.display(), + new_notes_path.display() + ); + } + new_project.files(new_files_path.clone()); + new_project.notes(new_notes_path.clone()); + if let Some(mut db) = new_project.db { + db.volumes = vec![ + new_files_path.display().to_string(), + new_notes_path.display().to_string(), + self.config.get("tools").unwrap().to_string(), + ]; + db.create()?; + new_project.db = Some(db); + } + self.projects[self.selected_project] = new_project; + return Ok(return_string); + } } #[derive(Clone)] @@ -285,7 +370,7 @@ pub struct Project { pub files: PathBuf, pub hosts: Vec, pub current: bool, - pub boxname: String, + pub db: Option, } impl Project { @@ -298,7 +383,7 @@ impl Project { files: PathBuf::new(), hosts: Vec::new(), current: false, - boxname: String::new(), + db: None, } } @@ -326,10 +411,6 @@ impl Project { self.config_folder = path; } - pub fn box_name(&mut self, boxname: String) { - self.boxname = boxname; - } - pub fn load_config(&mut self) -> Result<(), Box> { let config_contents = read_to_string(&self.config_folder)?; for line in config_contents.lines() { @@ -656,3 +737,33 @@ enum AppEvent { Worker(ToolMessage), Mouse(event::MouseEvent), } + +#[derive(Debug, Clone)] +pub struct DistroBox { + pub name: String, + pub volumes: Vec, + pub created: bool, + pub template: String, +} + +impl DistroBox { + pub fn create(&mut self) -> Result<(), Box> { + let mut create_command = Command::new("distrobox"); + create_command + .arg("create") + .arg("--root") + .arg("--clone") + .arg(self.template.clone()) + .arg("--name") + .arg(self.name.clone()); + for volume in &self.volumes { + let folder_name = volume.split("/").last().unwrap(); + create_command + .arg("--volume") + .arg(format!("{}:/{}:rw", volume, folder_name)); + } + create_command.status()?; + self.created = true; + return Ok(()); + } +}