From e73c978d95d8ef2e6ea9b9e1073e63824349c816 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:55:59 -0700 Subject: [PATCH] feat: Update tabs.rs to use tab data --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/floating_text.rs | 8 +- src/main.rs | 2 +- src/running_command.rs | 6 +- src/state.rs | 13 +-- src/tabs.rs | 208 +++++++++++++++++++---------------------- 7 files changed, 108 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad35a5f5..cb426968 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -672,9 +672,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index a5d4df3e..dd7c34cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ ratatui = "0.27.0" tui-term = "0.1.12" include_dir = "0.7.4" tempdir = "0.3.7" -serde_json = "1.0.122" serde = { version = "1.0.205", features = ["derive"] } +serde_json = "1.0.124" [[bin]] name = "linutil" diff --git a/src/floating_text.rs b/src/floating_text.rs index 467ec18f..1dc8520e 100644 --- a/src/floating_text.rs +++ b/src/floating_text.rs @@ -7,7 +7,6 @@ use ratatui::{ widgets::{Block, Borders, List}, Frame, }; -use std::path::PathBuf; pub struct FloatingText { text: Vec, @@ -19,7 +18,7 @@ impl FloatingText { Self { text, scroll: 0 } } - pub fn from_command(command: &Command, mut full_path: PathBuf) -> Option { + pub fn from_command(command: &Command) -> Option { let lines = match command { Command::Raw(cmd) => { // Reconstruct the line breaks and file formatting after the @@ -27,9 +26,8 @@ impl FloatingText { cmd.lines().map(|line| line.to_string()).collect() } Command::LocalFile(file_path) => { - full_path.push(file_path); - let file_contents = std::fs::read_to_string(&full_path) - .map_err(|_| format!("File not found: {:?}", &full_path)) + let file_contents = std::fs::read_to_string(file_path) + .map_err(|_| format!("File not found: {:?}", file_path)) .unwrap(); file_contents.lines().map(|line| line.to_string()).collect() } diff --git a/src/main.rs b/src/main.rs index 195f46ee..7fcbea97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,7 @@ fn main() -> std::io::Result<()> { .extract(temp_dir.path()) .expect("Failed to extract the saved directory"); - let mut state = AppState::new(theme, temp_dir.path().to_owned()); + let mut state = AppState::new(theme, temp_dir.path(), args.override_validation); stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; diff --git a/src/running_command.rs b/src/running_command.rs index ec76db88..b2b30291 100644 --- a/src/running_command.rs +++ b/src/running_command.rs @@ -13,7 +13,7 @@ use ratatui::{ }; use std::{ io::Write, - path::{Path, PathBuf}, + path::PathBuf, sync::{Arc, Mutex}, thread::JoinHandle, }; @@ -126,7 +126,7 @@ impl FloatContent for RunningCommand { } impl RunningCommand { - pub fn new(command: Command, temp_path: &Path) -> Self { + pub fn new(command: Command) -> Self { let pty_system = NativePtySystem::default(); // Build the command based on the provided Command enum variant @@ -142,8 +142,6 @@ impl RunningCommand { Command::None => panic!("Command::None was treated as a command"), } - cmd.cwd(temp_path); - // Open a pseudo-terminal with initial size let pair = pty_system .openpty(PtySize { diff --git a/src/state.rs b/src/state.rs index 1c07da13..d04930c8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,13 +14,11 @@ use ratatui::{ widgets::{Block, Borders, List, ListState, Paragraph}, Frame, }; -use std::path::PathBuf; +use std::path::Path; pub struct AppState { /// Selected theme theme: Theme, - /// Path to the root of the unpacked files in /tmp - temp_path: PathBuf, /// Currently focused area focus: Focus, /// List of tabs @@ -53,12 +51,11 @@ struct ListEntry { } impl AppState { - pub fn new(theme: Theme, temp_path: PathBuf) -> Self { - let tabs = crate::tabs::get_tabs(&temp_path, true); + pub fn new(theme: Theme, temp_path: &Path, override_validation: bool) -> Self { + let tabs = crate::tabs::get_tabs(temp_path, !override_validation); let root_id = tabs[0].tree.root().id(); let mut state = Self { theme, - temp_path, focus: Focus::List, tabs, current_tab: ListState::default().with_selected(Some(0)), @@ -304,14 +301,14 @@ impl AppState { } fn enable_preview(&mut self) { if let Some(command) = self.get_selected_command(false) { - if let Some(preview) = FloatingText::from_command(&command, self.temp_path.clone()) { + if let Some(preview) = FloatingText::from_command(&command) { self.spawn_float(preview, 80, 80); } } } fn handle_enter(&mut self) { if let Some(cmd) = self.get_selected_command(true) { - let command = RunningCommand::new(cmd, &self.temp_path); + let command = RunningCommand::new(cmd); self.spawn_float(command, 80, 80); } } diff --git a/src/tabs.rs b/src/tabs.rs index e46947ce..987ff587 100644 --- a/src/tabs.rs +++ b/src/tabs.rs @@ -1,28 +1,36 @@ use crate::running_command::Command; -use ego_tree::{NodeId, Tree}; +use ego_tree::{NodeMut, Tree}; use serde::Deserialize; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; +use std::path::{Path, PathBuf}; #[derive(Deserialize)] -struct ScriptInfo { - // Path to the script file in the UI, formatted as an array of directory names (first of which being the tab) - ui_path: Vec, - #[allow(dead_code)] - #[serde(default)] - // Description: Currently unused field, should be added in the future - description: String, - #[serde(default)] - // Requirements that must be met for the script to be displayed - preconditions: Option>, - #[serde(default)] - // Optional command. This is used for adding "raw" commands to the UI. - command: Option, +struct TabEntry { + name: String, + data: Vec, } -impl ScriptInfo { +#[derive(Deserialize)] +enum Entry { + #[serde(rename = "directory")] + Directory(EntryData>), + #[serde(rename = "command")] + Command(EntryData), + #[serde(rename = "script")] + Script(EntryData), +} + +#[derive(Deserialize)] +struct EntryData { + name: String, + #[allow(dead_code)] + #[serde(default)] + description: String, + data: T, + #[serde(default)] + preconditions: Option>, +} + +impl EntryData { fn is_supported(&self) -> bool { self.preconditions.as_deref().map_or(true, |preconditions| { preconditions.iter().all( @@ -79,107 +87,79 @@ pub struct ListNode { } pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec { - let scripts = get_script_list(command_dir); + let tab_files = + std::fs::read_to_string(command_dir.join("tabs.json")).expect("Failed to read tabs.json"); + let tab_files: Vec = + serde_json::from_str(&tab_files).expect("Failed to parse tabs.json"); + let tabs = tab_files.into_iter().map(|path| { + let file = command_dir.join(&path); + let directory = file.parent().unwrap().to_owned(); + let data = + std::fs::read_to_string(command_dir.join(path)).expect("Failed to read tab data"); + let mut tab_data: TabEntry = serde_json::from_str(&data).expect("Failed to parse tab data"); - let mut paths: HashMap, (String, NodeId)> = HashMap::new(); - let mut tabs: Vec = Vec::new(); + if validate { + filter_entries(&mut tab_data.data); + } + (tab_data, directory) + }); - for (json_file, script) in scripts { - let json_text = std::fs::read_to_string(&json_file).unwrap(); - let script_info: ScriptInfo = - serde_json::from_str(&json_text).expect("Unexpected JSON input"); - if validate && !script_info.is_supported() { - continue; - } - if script_info.ui_path.len() < 2 { - panic!( - "UI path must contain a tab. Ensure that {} has correct data", - json_file.display() - ); - } - let command = match script_info.command { - Some(command) => Command::Raw(command), - None if script.exists() => Command::LocalFile(script), - _ => panic!( - "Command not specified & matching script does not exist for JSON {}", - json_file.display() - ), - }; - for path_index in 1..script_info.ui_path.len() { - let path = &script_info.ui_path[..path_index]; - // Create tabs and directories which don't yet exist - if !paths.contains_key(path) { - let path = path.to_vec(); - let tab_name = script_info.ui_path[0].clone(); - if path_index == 1 { - let tab = Tab { - name: tab_name.clone(), - tree: Tree::new(ListNode { - name: "root".to_string(), - command: Command::None, - }), - }; - let root_id = tab.tree.root().id(); - tabs.push(tab); - paths.insert(path, (tab_name, root_id)); - } else { - let parent_path = &script_info.ui_path[..path_index - 1]; - let (tab, parent_id) = paths.get(parent_path).unwrap(); - let tab = tabs - .iter_mut() - .find(|Tab { name, .. }| name == tab) - .unwrap(); - let mut parent = tab.tree.get_mut(*parent_id).unwrap(); - let new_node = ListNode { - name: script_info.ui_path[path_index - 1].clone(), - command: Command::None, - }; - let new_id = parent.append(new_node).id(); - paths.insert(path, (tab_name, new_id)); - } - } - } - let (tab, parent_id) = paths - .get(&script_info.ui_path[..script_info.ui_path.len() - 1]) - .unwrap(); - let tab = tabs - .iter_mut() - .find(|Tab { name, .. }| name == tab) - .unwrap(); - let mut parent = tab.tree.get_mut(*parent_id).unwrap(); + let tabs: Vec = tabs + .map(|(TabEntry { name, data }, directory)| { + let mut tree = Tree::new(ListNode { + name: "root".to_string(), + command: Command::None, + }); + let mut root = tree.root_mut(); + create_directory(data, &mut root, &directory); + Tab { name, tree } + }) + .collect(); - let command = ListNode { - name: script_info.ui_path.last().unwrap().clone(), - command, - }; - parent.append(command); - } if tabs.is_empty() { - panic!("No tabs found."); + panic!("No tabs found"); } tabs } -fn get_script_list(directory: &Path) -> Vec<(PathBuf, PathBuf)> { - let mut entries = std::fs::read_dir(directory) - .expect("Command directory does not exist.") - .flatten() - .collect::>(); - entries.sort_by_key(|d| d.path()); - - entries - .into_iter() - .filter_map(|entry| { - let path = entry.path(); - // Recursively iterate through directories - if entry.file_type().map_or(false, |f| f.is_dir()) { - Some(get_script_list(&path)) - } else { - let is_json = path.extension().map_or(false, |ext| ext == "json"); - let script = path.with_extension("sh"); - is_json.then_some(vec![(path, script)]) - } - }) - .flatten() - .collect() +fn filter_entries(entries: &mut Vec) { + entries.retain_mut(|entry| match entry { + Entry::Script(entry) => entry.is_supported(), + Entry::Command(entry) => entry.is_supported(), + Entry::Directory(entry) if !entry.is_supported() => false, + Entry::Directory(entry) => { + filter_entries(&mut entry.data); + !entry.data.is_empty() + } + }); +} + +fn create_directory(data: Vec, node: &mut NodeMut, command_dir: &Path) { + for entry in data { + match entry { + Entry::Directory(entry) => { + let mut node = node.append(ListNode { + name: entry.name, + command: Command::None, + }); + create_directory(entry.data, &mut node, command_dir); + } + Entry::Command(entry) => { + node.append(ListNode { + name: entry.name, + command: Command::Raw(entry.data), + }); + } + Entry::Script(entry) => { + let dir = command_dir.join(entry.data); + if !dir.exists() { + panic!("Script {} does not exist", dir.display()); + } + node.append(ListNode { + name: entry.name, + command: Command::LocalFile(dir), + }); + } + } + } }