From c18c76c694d0393501605c7bd8f32a1cb6bb804f Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sat, 10 Aug 2024 19:52:49 -0700 Subject: [PATCH 01/14] feat: Add logic for runtime tab creation based on JSON data --- Cargo.lock | 40 ++++++ Cargo.toml | 2 + src/running_command.rs | 8 +- src/state.rs | 30 ++-- src/tabs.rs | 319 ++++++++++++++++++++++------------------- 5 files changed, 235 insertions(+), 164 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36fae34a..ad35a5f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -420,6 +420,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "memoffset" version = "0.6.5" @@ -644,6 +650,38 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.205" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.205" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serial" version = "0.4.0" @@ -844,6 +882,8 @@ dependencies = [ "oneshot", "portable-pty", "ratatui", + "serde", + "serde_json", "tempdir", "tui-term", ] diff --git a/Cargo.toml b/Cargo.toml index cacbdcb3..a5d4df3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +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"] } [[bin]] name = "linutil" diff --git a/src/running_command.rs b/src/running_command.rs index d07c0428..ec76db88 100644 --- a/src/running_command.rs +++ b/src/running_command.rs @@ -13,7 +13,7 @@ use ratatui::{ }; use std::{ io::Write, - path::Path, + path::{Path, PathBuf}, sync::{Arc, Mutex}, thread::JoinHandle, }; @@ -22,10 +22,10 @@ use tui_term::{ widget::PseudoTerminal, }; -#[derive(Clone)] +#[derive(Clone, Hash, Eq, PartialEq)] pub enum Command { - Raw(&'static str), - LocalFile(&'static str), + Raw(String), + LocalFile(PathBuf), None, // Directory } diff --git a/src/state.rs b/src/state.rs index 5f8ef1a6..1c07da13 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use crate::{ float::{Float, FloatContent}, floating_text::FloatingText, running_command::{Command, RunningCommand}, - tabs::{ListNode, TABS}, + tabs::{ListNode, Tab}, theme::Theme, }; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; @@ -23,6 +23,8 @@ pub struct AppState { temp_path: PathBuf, /// Currently focused area focus: Focus, + /// List of tabs + tabs: Vec, /// Current tab current_tab: ListState, /// Current search query @@ -52,11 +54,13 @@ struct ListEntry { impl AppState { pub fn new(theme: Theme, temp_path: PathBuf) -> Self { - let root_id = TABS[0].tree.root().id(); + let tabs = crate::tabs::get_tabs(&temp_path, true); + 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)), search_query: String::new(), items: vec![], @@ -67,7 +71,8 @@ impl AppState { state } pub fn draw(&mut self, frame: &mut Frame) { - let longest_tab_display_len = TABS + let longest_tab_display_len = self + .tabs .iter() .map(|tab| tab.name.len() + self.theme.tab_icon.len()) .max() @@ -85,7 +90,11 @@ impl AppState { .constraints([Constraint::Length(3), Constraint::Min(1)]) .split(horizontal[0]); - let tabs = TABS.iter().map(|tab| tab.name).collect::>(); + let tabs = self + .tabs + .iter() + .map(|tab| tab.name.as_str()) + .collect::>(); let tab_hl_style = if let Focus::TabList = self.focus { Style::default().reversed().fg(self.theme.tab_color) @@ -186,7 +195,7 @@ impl AppState { self.focus = Focus::List } KeyCode::Char('j') | KeyCode::Down - if self.current_tab.selected().unwrap() + 1 < TABS.len() => + if self.current_tab.selected().unwrap() + 1 < self.tabs.len() => { self.current_tab.select_next(); self.refresh_tab(); @@ -220,7 +229,7 @@ impl AppState { } pub fn update_items(&mut self) { if self.search_query.is_empty() { - let curr = TABS[self.current_tab.selected().unwrap()] + let curr = self.tabs[self.current_tab.selected().unwrap()] .tree .get(*self.visit_stack.last().unwrap()) .unwrap(); @@ -237,7 +246,7 @@ impl AppState { self.items.clear(); let query_lower = self.search_query.to_lowercase(); - for tab in TABS.iter() { + for tab in self.tabs.iter() { let mut stack = vec![tab.tree.root().id()]; while let Some(node_id) = stack.pop() { let node = tab.tree.get(node_id).unwrap(); @@ -255,7 +264,7 @@ impl AppState { stack.extend(node.children().map(|child| child.id())); } } - self.items.sort_by(|a, b| a.node.name.cmp(b.node.name)); + self.items.sort_by(|a, b| a.node.name.cmp(&b.node.name)); } } /// Checks ehther the current tree node is the root node (can we go up the tree or no) @@ -319,7 +328,10 @@ impl AppState { self.update_items(); } fn refresh_tab(&mut self) { - self.visit_stack = vec![TABS[self.current_tab.selected().unwrap()].tree.root().id()]; + self.visit_stack = vec![self.tabs[self.current_tab.selected().unwrap()] + .tree + .root() + .id()]; self.selection.select(Some(0)); self.update_items(); } diff --git a/src/tabs.rs b/src/tabs.rs index e7ef5d35..a01b5fa2 100644 --- a/src/tabs.rs +++ b/src/tabs.rs @@ -1,162 +1,179 @@ -use std::sync::LazyLock; - -use ego_tree::{tree, Tree}; - use crate::running_command::Command; +use ego_tree::{tree, NodeId, Tree}; +use serde::Deserialize; +use std::{ + collections::{HashMap, HashSet}, + path::{Path, PathBuf}, + sync::LazyLock, +}; +#[derive(Deserialize)] +struct ScriptInfo { + ui_path: Vec, + #[serde(default)] + description: String, + #[serde(default)] + preconditions: Option>, + #[serde(default)] + command: Option, +} + +impl ScriptInfo { + fn is_supported(&self) -> bool { + self.preconditions.as_deref().map_or(true, |preconditions| { + preconditions.iter().all( + |Precondition { + matches, + data, + values, + }| { + match data { + SystemDataType::Environment(var_name) => std::env::var(var_name) + .map_or(false, |var| values.contains(&var) == *matches), + SystemDataType::File(path) => { + std::fs::read_to_string(path).map_or(false, |data| { + values + .iter() + .any(|matching_value| data.contains(matching_value)) + == *matches + }) + } + } + }, + ) + }) + } +} + +#[derive(Deserialize)] +struct Precondition { + // If true, the data must be contained within the list of values. + // Otherwise, the data must not be contained within the list of values + matches: bool, + data: SystemDataType, + values: Vec, +} + +#[derive(Deserialize)] +enum SystemDataType { + #[serde(rename = "environment")] + Environment(String), + #[serde(rename = "file")] + File(PathBuf), +} + +#[derive(Hash, Eq, PartialEq)] pub struct Tab { - pub name: &'static str, + pub name: String, pub tree: Tree, } -#[derive(Clone)] +#[derive(Clone, Hash, Eq, PartialEq)] pub struct ListNode { - pub name: &'static str, + pub name: String, pub command: Command, } -pub static TABS: LazyLock> = LazyLock::new(|| { - vec![ - Tab { - name: "System Setup", - tree: tree!(ListNode { - name: "root", - command: Command::None, - } => { - ListNode { - name: "Full System Update", - command: Command::LocalFile("system-update.sh"), - }, - ListNode { - name: "Build Prerequisites", - command: Command::LocalFile("system-setup/1-compile-setup.sh"), - }, - ListNode { - name: "Gaming Dependencies", - command: Command::LocalFile("system-setup/2-gaming-setup.sh"), - }, - ListNode { - name: "Global Theme", - command: Command::LocalFile("system-setup/3-global-theme.sh"), - }, - ListNode { - name: "Remove Snaps", - command: Command::LocalFile("system-setup/4-remove-snaps.sh"), +pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec { + let scripts = get_script_list(command_dir); + + let mut paths: HashMap, (String, NodeId)> = HashMap::new(); + let mut tabs: Vec = Vec::new(); + + 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].to_vec(); + if !paths.contains_key(&path) { + 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)); } - }), - }, - Tab { - name: "Applications Setup", - tree: tree!(ListNode { - name: "root", - command: Command::None, - } => { - ListNode { - name: "Alacritty", - command: Command::LocalFile("applications-setup/alacritty-setup.sh"), - }, - ListNode { - name: "Bash Prompt", - command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\""), - }, - ListNode { - name: "DWM-Titus", - command: Command::LocalFile("applications-setup/dwmtitus-setup.sh") - }, - ListNode { - name: "Kitty", - command: Command::LocalFile("applications-setup/kitty-setup.sh") - }, - ListNode { - name: "Neovim", - command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\""), - }, - ListNode { - name: "Rofi", - command: Command::LocalFile("applications-setup/rofi-setup.sh"), - }, - ListNode { - name: "ZSH Prompt", - command: Command::LocalFile("applications-setup/zsh-setup.sh"), - } - }), - }, - Tab { - name: "Security", - tree: tree!(ListNode { - name: "root", - command: Command::None, - } => { - ListNode { - name: "Firewall Baselines (CTT)", - command: Command::LocalFile("security/firewall-baselines.sh"), - } - }), - }, - Tab { - name: "Utilities", - tree: tree!(ListNode { - name: "root", - command: Command::None, - } => { - ListNode { - name: "Wifi Manager", - command: Command::LocalFile("utils/wifi-control.sh"), - }, - ListNode { - name: "Bluetooth Manager", - command: Command::LocalFile("utils/bluetooth-control.sh"), - }, - ListNode { - name: "MonitorControl(xorg)", - command: Command::None, - } => { - ListNode { - name: "Set Resolution", - command: Command::LocalFile("utils/monitor-control/set_resolutions.sh"), - }, - ListNode { - name: "Duplicate Displays", - command: Command::LocalFile("utils/monitor-control/duplicate_displays.sh"), - }, - ListNode { - name: "Extend Displays", - command: Command::LocalFile("utils/monitor-control/extend_displays.sh"), - }, - ListNode { - name: "Auto Detect Displays", - command: Command::LocalFile("utils/monitor-control/auto_detect_displays.sh"), - }, - ListNode { - name: "Enable Monitor", - command: Command::LocalFile("utils/monitor-control/enable_monitor.sh"), - }, - ListNode { - name: "Disable Monitor", - command: Command::LocalFile("utils/monitor-control/disable_monitor.sh"), - }, - ListNode { - name: "Set Primary Monitor", - command: Command::LocalFile("utils/monitor-control/set_primary_monitor.sh"), - }, - ListNode { - name: "Change Orientation", - command: Command::LocalFile("utils/monitor-control/change_orientation.sh"), - }, - ListNode { - name: "Manage Arrangement", - command: Command::LocalFile("utils/monitor-control/manage_arrangement.sh"), - }, - ListNode { - name: "Scale Monitors", - command: Command::LocalFile("utils/monitor-control/scale_monitor.sh"), - }, - ListNode { - name: "Reset Scaling", - command: Command::LocalFile("utils/monitor-control/reset_scaling.sh"), - } - }, - }), - }, - ] -}); + } + } + 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 command = ListNode { + name: script_info.ui_path.last().unwrap().clone(), + command, + }; + parent.append(command); + } + if tabs.is_empty() { + 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() +} From d4a3889e00759ffbb491b1e62ddaf83c7ed369fc Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sat, 10 Aug 2024 19:55:36 -0700 Subject: [PATCH 02/14] fix: Rebuild program upon any changes to command directory --- build.rs | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 build.rs diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..dc9c06dd --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() { + // Rebuild program if any file in commands directory changes. + println!("cargo:rerun-if-changed=src/commands"); +} From da2433d9d98ff21603c88ac58bf00247744d1387 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sat, 10 Aug 2024 19:58:59 -0700 Subject: [PATCH 03/14] feat: Add option to override compatibility checks --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.rs b/src/main.rs index 7502b61b..195f46ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,9 @@ struct Args { /// Enable compatibility mode (disable icons and RGB colors) #[arg(short, long, default_value_t = false)] compat: bool, + #[arg(long, default_value_t = false)] + #[clap(help = "Show all available options, disregarding compatibility checks (UNSAFE)")] + override_validation: bool, } fn main() -> std::io::Result<()> { From 9afadee250bd74a60bf0ed3e95603e744c8a1de5 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sat, 10 Aug 2024 20:05:19 -0700 Subject: [PATCH 04/14] fix: Remove unused imports. Comment scriptinfo fields --- src/tabs.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tabs.rs b/src/tabs.rs index a01b5fa2..6c032746 100644 --- a/src/tabs.rs +++ b/src/tabs.rs @@ -1,20 +1,24 @@ use crate::running_command::Command; -use ego_tree::{tree, NodeId, Tree}; +use ego_tree::{NodeId, Tree}; use serde::Deserialize; use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, path::{Path, PathBuf}, - sync::LazyLock, }; #[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, } From d07946fc416d3231414eade3ccb5e3c25f1082aa Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sat, 10 Aug 2024 20:48:04 -0700 Subject: [PATCH 05/14] chore: Slightly improve code structure and commenting --- src/tabs.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tabs.rs b/src/tabs.rs index 6c032746..e46947ce 100644 --- a/src/tabs.rs +++ b/src/tabs.rs @@ -106,8 +106,10 @@ pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec { ), }; for path_index in 1..script_info.ui_path.len() { - let path = script_info.ui_path[..path_index].to_vec(); - if !paths.contains_key(&path) { + 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 { @@ -175,7 +177,7 @@ fn get_script_list(directory: &Path) -> Vec<(PathBuf, PathBuf)> { } 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)]) + is_json.then_some(vec![(path, script)]) } }) .flatten() 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 06/14] 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), + }); + } + } + } } From 7d2f92a38c3fb02188bf1c66279d8aa3f77b9784 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:55:16 -0700 Subject: [PATCH 07/14] feat: Add tab JSON --- src/commands/applications-setup/tab_data.json | 47 ++++++++ src/commands/security/tab_data.json | 6 ++ .../{ => system-setup}/system-update.sh | 0 src/commands/system-setup/tab_data.json | 35 ++++++ src/commands/tabs.json | 6 ++ src/commands/utils/tab_data.json | 101 ++++++++++++++++++ 6 files changed, 195 insertions(+) create mode 100644 src/commands/applications-setup/tab_data.json create mode 100644 src/commands/security/tab_data.json rename src/commands/{ => system-setup}/system-update.sh (100%) create mode 100644 src/commands/system-setup/tab_data.json create mode 100644 src/commands/tabs.json create mode 100644 src/commands/utils/tab_data.json diff --git a/src/commands/applications-setup/tab_data.json b/src/commands/applications-setup/tab_data.json new file mode 100644 index 00000000..538966d3 --- /dev/null +++ b/src/commands/applications-setup/tab_data.json @@ -0,0 +1,47 @@ +{ + "name": "Applications Setup", + "data": [ + { + "script": { + "name": "Alacritty", + "data": "alacritty-setup.sh" + } + }, + { + "command": { + "name": "Bash Prompt", + "data": "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\"" + } + }, + { + "script": { + "name": "DWM-Titus", + "data": "dwmtitus-setup.sh" + } + }, + { + "script": { + "name": "Kitty", + "data": "kitty-setup.sh" + } + }, + { + "command": { + "name": "Neovim", + "data": "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\"" + } + }, + { + "script": { + "name": "Rofi", + "data": "rofi-setup.sh" + } + }, + { + "script": { + "name": "ZSH Prompt", + "data": "zsh-setup.sh" + } + } + ] +} diff --git a/src/commands/security/tab_data.json b/src/commands/security/tab_data.json new file mode 100644 index 00000000..603a1ac1 --- /dev/null +++ b/src/commands/security/tab_data.json @@ -0,0 +1,6 @@ +{ + "name": "Security", + "data": [ + {"script": { "name": "Firewall Baselines (CTT)", "data": "firewall-baselines.sh" }} + ] +} diff --git a/src/commands/system-update.sh b/src/commands/system-setup/system-update.sh similarity index 100% rename from src/commands/system-update.sh rename to src/commands/system-setup/system-update.sh diff --git a/src/commands/system-setup/tab_data.json b/src/commands/system-setup/tab_data.json new file mode 100644 index 00000000..6cd91094 --- /dev/null +++ b/src/commands/system-setup/tab_data.json @@ -0,0 +1,35 @@ +{ + "name": "System Setup", + "data": [ + { + "script": { + "name": "Full System Update", + "data": "system-update.sh" + } + }, + { + "script": { + "name": "Build Prerequisites", + "data": "1-compile-setup.sh" + } + }, + { + "script": { + "name": "Gaming Dependencies", + "data": "2-gaming-setup.sh" + } + }, + { + "script": { + "name": "Global Theme", + "data": "3-global-theme.sh" + } + }, + { + "script": { + "name": "Remove Snaps", + "data": "4-remove-snaps.sh" + } + } + ] +} diff --git a/src/commands/tabs.json b/src/commands/tabs.json new file mode 100644 index 00000000..c3c753b7 --- /dev/null +++ b/src/commands/tabs.json @@ -0,0 +1,6 @@ +[ + "system-setup/tab_data.json", + "applications-setup/tab_data.json", + "security/tab_data.json", + "utils/tab_data.json" +] diff --git a/src/commands/utils/tab_data.json b/src/commands/utils/tab_data.json new file mode 100644 index 00000000..2de6cbe2 --- /dev/null +++ b/src/commands/utils/tab_data.json @@ -0,0 +1,101 @@ +{ + "name": "Utilities", + "data": [ + { + "script": { + "name": "WiFi Manager", + "data": "wifi-control.sh" + } + }, + { + "script": { + "name": "Bluetooth Manager", + "data": "bluetooth-control.sh" + } + }, + { + "directory": { + "name": "Monitor Control", + "preconditions": [ + { + "matches": true, + "data": { + "environment": "XDG_SESSION_TYPE" + }, + "values": [ + "x11" + ] + } + ], + "data": [ + { + "script": { + "name": "Set Resolution", + "data": "monitor-control/set_resolutions.sh" + } + }, + { + "script": { + "name": "Duplicate Displays", + "data": "monitor-control/duplicate_displays.sh" + } + }, + { + "script": { + "name": "Extend Displays", + "data": "monitor-control/extend_displays.sh" + } + }, + { + "script": { + "name": "Auto Detect Displays", + "data": "monitor-control/auto_detect_displays.sh" + } + }, + { + "script": { + "name": "Enable Monitor", + "data": "monitor-control/enable_monitor.sh" + } + }, + { + "script": { + "name": "Disable Monitor", + "data": "monitor-control/disable_monitor.sh" + } + }, + { + "script": { + "name": "Set Primary Monitor", + "data": "monitor-control/set_primary_monitor.sh" + } + }, + { + "script": { + "name": "Change Orientation", + "data": "monitor-control/change_orientation.sh" + } + }, + { + "script": { + "name": "Manage Arrangement", + "data": "monitor-control/manage_arrangement.sh" + } + }, + { + "script": { + "name": "Scale Monitors", + "data": "monitor-control/scale_monitor.sh" + } + }, + { + "script": { + "name": "Reset Scaling", + "data": "monitor-control/reset_scaling.sh" + } + } + ] + } + } + ] +} From 1d5b1e66765ce04cc312d7b8d059392dc7dd9765 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:09:31 -0700 Subject: [PATCH 08/14] Replace JSON with TOML --- Cargo.lock | 70 +++++++++- Cargo.toml | 2 +- src/commands/applications-setup/tab_data.json | 47 ------- src/commands/applications-setup/tab_data.toml | 29 ++++ src/commands/security/tab_data.json | 6 - src/commands/security/tab_data.toml | 5 + src/commands/system-setup/tab_data.json | 35 ----- src/commands/system-setup/tab_data.toml | 21 +++ src/commands/tabs.json | 6 - src/commands/tabs.toml | 1 + src/commands/utils/tab_data.json | 101 -------------- src/commands/utils/tab_data.toml | 66 ++++++++++ src/tabs.rs | 124 ++++++++++-------- 13 files changed, 258 insertions(+), 255 deletions(-) delete mode 100644 src/commands/applications-setup/tab_data.json create mode 100644 src/commands/applications-setup/tab_data.toml delete mode 100644 src/commands/security/tab_data.json create mode 100644 src/commands/security/tab_data.toml delete mode 100644 src/commands/system-setup/tab_data.json create mode 100644 src/commands/system-setup/tab_data.toml delete mode 100644 src/commands/tabs.json create mode 100644 src/commands/tabs.toml delete mode 100644 src/commands/utils/tab_data.json create mode 100644 src/commands/utils/tab_data.toml diff --git a/Cargo.lock b/Cargo.lock index cb426968..b9bed308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "filedescriptor" version = "0.8.2" @@ -344,6 +350,16 @@ dependencies = [ "quote", ] +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "ioctl-rs" version = "0.1.6" @@ -671,14 +687,11 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.124" +name = "serde_spanned" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ - "itoa", - "memchr", - "ryu", "serde", ] @@ -870,6 +883,40 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tui" version = "0.1.0" @@ -883,8 +930,8 @@ dependencies = [ "portable-pty", "ratatui", "serde", - "serde_json", "tempdir", + "toml", "tui-term", ] @@ -1202,6 +1249,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index dd7c34cc..5a336acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ tui-term = "0.1.12" include_dir = "0.7.4" tempdir = "0.3.7" serde = { version = "1.0.205", features = ["derive"] } -serde_json = "1.0.124" +toml = "0.8.19" [[bin]] name = "linutil" diff --git a/src/commands/applications-setup/tab_data.json b/src/commands/applications-setup/tab_data.json deleted file mode 100644 index 538966d3..00000000 --- a/src/commands/applications-setup/tab_data.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "Applications Setup", - "data": [ - { - "script": { - "name": "Alacritty", - "data": "alacritty-setup.sh" - } - }, - { - "command": { - "name": "Bash Prompt", - "data": "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\"" - } - }, - { - "script": { - "name": "DWM-Titus", - "data": "dwmtitus-setup.sh" - } - }, - { - "script": { - "name": "Kitty", - "data": "kitty-setup.sh" - } - }, - { - "command": { - "name": "Neovim", - "data": "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\"" - } - }, - { - "script": { - "name": "Rofi", - "data": "rofi-setup.sh" - } - }, - { - "script": { - "name": "ZSH Prompt", - "data": "zsh-setup.sh" - } - } - ] -} diff --git a/src/commands/applications-setup/tab_data.toml b/src/commands/applications-setup/tab_data.toml new file mode 100644 index 00000000..2ad73892 --- /dev/null +++ b/src/commands/applications-setup/tab_data.toml @@ -0,0 +1,29 @@ +name = "Applications Setup" + +[[data]] +name = "Alacritty" +script = "alacritty-setup.sh" + +[[data]] +name = "Bash Prompt" +command = "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\"" + +[[data]] +name = "DWM-Titus" +script = "dwmtitus-setup.sh" + +[[data]] +name = "Kitty" +script = "kitty-setup.sh" + +[[data]] +name = "Neovim" +command = "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\"" + +[[data]] +name = "Rofi" +script = "rofi-setup.sh" + +[[data]] +name = "ZSH Prompt" +script = "zsh-setup.sh" diff --git a/src/commands/security/tab_data.json b/src/commands/security/tab_data.json deleted file mode 100644 index 603a1ac1..00000000 --- a/src/commands/security/tab_data.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Security", - "data": [ - {"script": { "name": "Firewall Baselines (CTT)", "data": "firewall-baselines.sh" }} - ] -} diff --git a/src/commands/security/tab_data.toml b/src/commands/security/tab_data.toml new file mode 100644 index 00000000..6d81cc36 --- /dev/null +++ b/src/commands/security/tab_data.toml @@ -0,0 +1,5 @@ +name = "Security" + +[[data]] +name = "Firewall Baselines (CTT)" +script = "firewall-baselines.sh" diff --git a/src/commands/system-setup/tab_data.json b/src/commands/system-setup/tab_data.json deleted file mode 100644 index 6cd91094..00000000 --- a/src/commands/system-setup/tab_data.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "System Setup", - "data": [ - { - "script": { - "name": "Full System Update", - "data": "system-update.sh" - } - }, - { - "script": { - "name": "Build Prerequisites", - "data": "1-compile-setup.sh" - } - }, - { - "script": { - "name": "Gaming Dependencies", - "data": "2-gaming-setup.sh" - } - }, - { - "script": { - "name": "Global Theme", - "data": "3-global-theme.sh" - } - }, - { - "script": { - "name": "Remove Snaps", - "data": "4-remove-snaps.sh" - } - } - ] -} diff --git a/src/commands/system-setup/tab_data.toml b/src/commands/system-setup/tab_data.toml new file mode 100644 index 00000000..6ede3012 --- /dev/null +++ b/src/commands/system-setup/tab_data.toml @@ -0,0 +1,21 @@ +name = "System Setup" + +[[data]] +name = "Full System Update" +script = "system-update.sh" + +[[data]] +name = "Build Prerequisites" +script = "1-compile-setup.sh" + +[[data]] +name = "Gaming Dependencies" +script = "2-gaming-setup.sh" + +[[data]] +name = "Global Theme" +script = "3-global-theme.sh" + +[[data]] +name = "Remove Snaps" +script = "4-remove-snaps.sh" diff --git a/src/commands/tabs.json b/src/commands/tabs.json deleted file mode 100644 index c3c753b7..00000000 --- a/src/commands/tabs.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - "system-setup/tab_data.json", - "applications-setup/tab_data.json", - "security/tab_data.json", - "utils/tab_data.json" -] diff --git a/src/commands/tabs.toml b/src/commands/tabs.toml new file mode 100644 index 00000000..ab72befa --- /dev/null +++ b/src/commands/tabs.toml @@ -0,0 +1 @@ +directories = ["system-setup", "applications-setup", "security", "utils"] diff --git a/src/commands/utils/tab_data.json b/src/commands/utils/tab_data.json deleted file mode 100644 index 2de6cbe2..00000000 --- a/src/commands/utils/tab_data.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "name": "Utilities", - "data": [ - { - "script": { - "name": "WiFi Manager", - "data": "wifi-control.sh" - } - }, - { - "script": { - "name": "Bluetooth Manager", - "data": "bluetooth-control.sh" - } - }, - { - "directory": { - "name": "Monitor Control", - "preconditions": [ - { - "matches": true, - "data": { - "environment": "XDG_SESSION_TYPE" - }, - "values": [ - "x11" - ] - } - ], - "data": [ - { - "script": { - "name": "Set Resolution", - "data": "monitor-control/set_resolutions.sh" - } - }, - { - "script": { - "name": "Duplicate Displays", - "data": "monitor-control/duplicate_displays.sh" - } - }, - { - "script": { - "name": "Extend Displays", - "data": "monitor-control/extend_displays.sh" - } - }, - { - "script": { - "name": "Auto Detect Displays", - "data": "monitor-control/auto_detect_displays.sh" - } - }, - { - "script": { - "name": "Enable Monitor", - "data": "monitor-control/enable_monitor.sh" - } - }, - { - "script": { - "name": "Disable Monitor", - "data": "monitor-control/disable_monitor.sh" - } - }, - { - "script": { - "name": "Set Primary Monitor", - "data": "monitor-control/set_primary_monitor.sh" - } - }, - { - "script": { - "name": "Change Orientation", - "data": "monitor-control/change_orientation.sh" - } - }, - { - "script": { - "name": "Manage Arrangement", - "data": "monitor-control/manage_arrangement.sh" - } - }, - { - "script": { - "name": "Scale Monitors", - "data": "monitor-control/scale_monitor.sh" - } - }, - { - "script": { - "name": "Reset Scaling", - "data": "monitor-control/reset_scaling.sh" - } - } - ] - } - } - ] -} diff --git a/src/commands/utils/tab_data.toml b/src/commands/utils/tab_data.toml new file mode 100644 index 00000000..594ec62f --- /dev/null +++ b/src/commands/utils/tab_data.toml @@ -0,0 +1,66 @@ +name = "Utilities" + +[[data]] +name = "WiFi Manager" +script = "wifi-control.sh" + +[[data]] +name = "Bluetooth Manager" +script = "bluetooth-control.sh" + +[[data]] +name = "Monitor Control" +preconditions = [ + { matches = true, data = { environment = "XDG_SESSION_TYPE" }, values = [ + "x11", + ] }, +] + +[[data.entries]] +name = "Set Resolution" +script = "monitor-control/set_resolutions.sh" + +[[data.entries]] +name = "Set Resolution" +script = "monitor-control/set_resolutions.sh" + +[[data.entries]] +name = "Duplicate Displays" +script = "monitor-control/duplicate_displays.sh" + +[[data.entries]] +name = "Extend Displays" +script = "monitor-control/extend_displays.sh" + +[[data.entries]] +name = "Auto Detect Displays" +script = "monitor-control/auto_detect_displays.sh" + +[[data.entries]] +name = "Enable Monitor" +script = "monitor-control/enable_monitor.sh" + +[[data.entries]] +name = "Disable Monitor" +script = "monitor-control/disable_monitor.sh" + +[[data.entries]] +name = "Set Primary Monitor" +script = "monitor-control/set_primary_monitor.sh" + +[[data.entries]] +name = "Change Orientation" +script = "monitor-control/change_orientation.sh" + +[[data.entries]] +name = "Manage Arrangement" +script = "monitor-control/manage_arrangement.sh" + +[[data.entries]] +name = "Scale Monitors" +script = "monitor-control/scale_monitor.sh" + +[[data.entries]] +name = "Reset Scaling" +script = "monitor-control/reset_scaling.sh" +matches = true diff --git a/src/tabs.rs b/src/tabs.rs index 987ff587..6f32a8dc 100644 --- a/src/tabs.rs +++ b/src/tabs.rs @@ -3,6 +3,11 @@ use ego_tree::{NodeMut, Tree}; use serde::Deserialize; use std::path::{Path, PathBuf}; +#[derive(Deserialize)] +struct TabList { + directories: Vec, +} + #[derive(Deserialize)] struct TabEntry { name: String, @@ -10,27 +15,22 @@ struct TabEntry { } #[derive(Deserialize)] -enum Entry { - #[serde(rename = "directory")] - Directory(EntryData>), - #[serde(rename = "command")] - Command(EntryData), - #[serde(rename = "script")] - Script(EntryData), -} - -#[derive(Deserialize)] -struct EntryData { +struct Entry { name: String, #[allow(dead_code)] #[serde(default)] description: String, - data: T, #[serde(default)] preconditions: Option>, + #[serde(default)] + entries: Option>, + #[serde(default)] + command: Option, + #[serde(default)] + script: Option, } -impl EntryData { +impl Entry { fn is_supported(&self) -> bool { self.preconditions.as_deref().map_or(true, |preconditions| { preconditions.iter().all( @@ -87,16 +87,11 @@ pub struct ListNode { } pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec { - 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 tab_files = TabList::get_tabs(command_dir); 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 directory = path.parent().unwrap().to_owned(); + let data = std::fs::read_to_string(path).expect("Failed to read tab data"); + let mut tab_data: TabEntry = toml::from_str(&data).expect("Failed to parse tab data"); if validate { filter_entries(&mut tab_data.data); @@ -123,43 +118,68 @@ pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec { } 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() + entries.retain_mut(|entry| { + if !entry.is_supported() { + return false; + } + if let Some(entries) = &mut entry.entries { + filter_entries(entries); + !entries.is_empty() + } else { + true } }); } 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), - }); + if [ + entry.entries.is_some(), + entry.command.is_some(), + entry.script.is_some(), + ] + .iter() + .filter(|&&x| x) + .count() + > 1 + { + panic!("Entry must have only one data type"); + } + + if let Some(entries) = entry.entries { + let mut node = node.append(ListNode { + name: entry.name, + command: Command::None, + }); + create_directory(entries, &mut node, command_dir); + } else if let Some(command) = entry.command { + node.append(ListNode { + name: entry.name, + command: Command::Raw(command), + }); + } else if let Some(script) = entry.script { + let dir = command_dir.join(script); + if !dir.exists() { + panic!("Script {} does not exist", dir.display()); } + node.append(ListNode { + name: entry.name, + command: Command::LocalFile(dir), + }); + } else { + panic!("Entry must have data"); } } } +impl TabList { + fn get_tabs(command_dir: &Path) -> Vec { + let tab_files = std::fs::read_to_string(command_dir.join("tabs.toml")) + .expect("Failed to read tabs.toml"); + let data: Self = toml::from_str(&tab_files).expect("Failed to parse tabs.toml"); + + data.directories + .into_iter() + .map(|path| command_dir.join(path).join("tab_data.toml")) + .collect() + } +} From d4489648b4ea7d17f38ebeb8577b79e37bd83ef6 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:13:56 -0700 Subject: [PATCH 09/14] fix: Launch script from the directory in which it's contained --- src/commands/applications-setup/alacritty-setup.sh | 2 +- src/commands/applications-setup/dwmtitus-setup.sh | 4 ++-- src/commands/applications-setup/kitty-setup.sh | 2 +- src/commands/applications-setup/rofi-setup.sh | 2 +- src/commands/applications-setup/zsh-setup.sh | 4 ++-- src/commands/security/firewall-baselines.sh | 2 +- src/commands/system-setup/1-compile-setup.sh | 2 +- src/commands/system-setup/2-gaming-setup.sh | 2 +- src/commands/system-setup/3-global-theme.sh | 2 +- src/commands/system-setup/4-remove-snaps.sh | 2 +- src/commands/system-setup/system-update.sh | 2 +- src/commands/utils/monitor-control/auto_detect_displays.sh | 2 +- src/commands/utils/monitor-control/change_orientation.sh | 2 +- src/commands/utils/monitor-control/disable_monitor.sh | 2 +- src/commands/utils/monitor-control/duplicate_displays.sh | 2 +- src/commands/utils/monitor-control/enable_monitor.sh | 2 +- src/commands/utils/monitor-control/extend_displays.sh | 2 +- src/commands/utils/monitor-control/manage_arrangement.sh | 2 +- src/commands/utils/monitor-control/reset_scaling.sh | 2 +- src/commands/utils/monitor-control/scale_monitor.sh | 2 +- src/commands/utils/monitor-control/set_primary_monitor.sh | 2 +- src/commands/utils/monitor-control/set_resolutions.sh | 2 +- src/running_command.rs | 5 ++++- 23 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/commands/applications-setup/alacritty-setup.sh b/src/commands/applications-setup/alacritty-setup.sh index 46143729..69581953 100755 --- a/src/commands/applications-setup/alacritty-setup.sh +++ b/src/commands/applications-setup/alacritty-setup.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh setupAlacritty() { echo "Install Alacritty if not already installed..." diff --git a/src/commands/applications-setup/dwmtitus-setup.sh b/src/commands/applications-setup/dwmtitus-setup.sh index e6253ec4..6421b31e 100644 --- a/src/commands/applications-setup/dwmtitus-setup.sh +++ b/src/commands/applications-setup/dwmtitus-setup.sh @@ -1,5 +1,5 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh makeDWM(){ cd $HOME && git clone https://github.com/ChrisTitusTech/dwm-titus.git # CD to Home directory to install dwm-titus @@ -24,4 +24,4 @@ setupDWM() { checkEnv setupDWM -makeDWM \ No newline at end of file +makeDWM diff --git a/src/commands/applications-setup/kitty-setup.sh b/src/commands/applications-setup/kitty-setup.sh index b2bce9ea..f4a69d12 100755 --- a/src/commands/applications-setup/kitty-setup.sh +++ b/src/commands/applications-setup/kitty-setup.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh setupKitty() { echo "Install Kitty if not already installed..." diff --git a/src/commands/applications-setup/rofi-setup.sh b/src/commands/applications-setup/rofi-setup.sh index a6639bb9..a571c16d 100755 --- a/src/commands/applications-setup/rofi-setup.sh +++ b/src/commands/applications-setup/rofi-setup.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh setupRofi() { echo "Install Rofi if not already installed..." diff --git a/src/commands/applications-setup/zsh-setup.sh b/src/commands/applications-setup/zsh-setup.sh index c7a7ea9e..e2ec8fd8 100644 --- a/src/commands/applications-setup/zsh-setup.sh +++ b/src/commands/applications-setup/zsh-setup.sh @@ -1,6 +1,6 @@ #!/bin/sh -. ./common-script.sh +. ../common-script.sh # Function to install zsh install_zsh() { @@ -48,4 +48,4 @@ EOL checkEnv setup_zsh_config -install_zsh \ No newline at end of file +install_zsh diff --git a/src/commands/security/firewall-baselines.sh b/src/commands/security/firewall-baselines.sh index 0a062289..40d24fab 100644 --- a/src/commands/security/firewall-baselines.sh +++ b/src/commands/security/firewall-baselines.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh installPkg() { echo "Install UFW if not already installed..." diff --git a/src/commands/system-setup/1-compile-setup.sh b/src/commands/system-setup/1-compile-setup.sh index e2243d94..b5f3eab7 100755 --- a/src/commands/system-setup/1-compile-setup.sh +++ b/src/commands/system-setup/1-compile-setup.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh # Check if the home directory and linuxtoolbox folder exist, create them if they don't LINUXTOOLBOXDIR="$HOME/linuxtoolbox" diff --git a/src/commands/system-setup/2-gaming-setup.sh b/src/commands/system-setup/2-gaming-setup.sh index 96bfe777..24b706b6 100755 --- a/src/commands/system-setup/2-gaming-setup.sh +++ b/src/commands/system-setup/2-gaming-setup.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh installDepend() { ## Check for dependencies. diff --git a/src/commands/system-setup/3-global-theme.sh b/src/commands/system-setup/3-global-theme.sh index eaebcd6e..4d660a37 100644 --- a/src/commands/system-setup/3-global-theme.sh +++ b/src/commands/system-setup/3-global-theme.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh # Check if the home directory and linuxtoolbox folder exist, create them if they don't LINUXTOOLBOXDIR="$HOME/linuxtoolbox" diff --git a/src/commands/system-setup/4-remove-snaps.sh b/src/commands/system-setup/4-remove-snaps.sh index c839e4f0..26a115f3 100644 --- a/src/commands/system-setup/4-remove-snaps.sh +++ b/src/commands/system-setup/4-remove-snaps.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh removeSnaps() { case $PACKAGER in diff --git a/src/commands/system-setup/system-update.sh b/src/commands/system-setup/system-update.sh index 89b2d448..7f93e4f7 100755 --- a/src/commands/system-setup/system-update.sh +++ b/src/commands/system-setup/system-update.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -. ./common-script.sh +. ../common-script.sh fastUpdate() { case ${PACKAGER} in diff --git a/src/commands/utils/monitor-control/auto_detect_displays.sh b/src/commands/utils/monitor-control/auto_detect_displays.sh index 0ff72376..a86ddfc1 100644 --- a/src/commands/utils/monitor-control/auto_detect_displays.sh +++ b/src/commands/utils/monitor-control/auto_detect_displays.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to auto-detect displays and set common resolution auto_detect_displays() { diff --git a/src/commands/utils/monitor-control/change_orientation.sh b/src/commands/utils/monitor-control/change_orientation.sh index 46ca1d34..93927149 100644 --- a/src/commands/utils/monitor-control/change_orientation.sh +++ b/src/commands/utils/monitor-control/change_orientation.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to change monitor orientation change_orientation() { diff --git a/src/commands/utils/monitor-control/disable_monitor.sh b/src/commands/utils/monitor-control/disable_monitor.sh index 4de80062..f999d408 100644 --- a/src/commands/utils/monitor-control/disable_monitor.sh +++ b/src/commands/utils/monitor-control/disable_monitor.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh RESET='\033[0m' BOLD='\033[1m' diff --git a/src/commands/utils/monitor-control/duplicate_displays.sh b/src/commands/utils/monitor-control/duplicate_displays.sh index eb6f542d..4332a0f9 100644 --- a/src/commands/utils/monitor-control/duplicate_displays.sh +++ b/src/commands/utils/monitor-control/duplicate_displays.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to duplicate displays duplicate_displays() { diff --git a/src/commands/utils/monitor-control/enable_monitor.sh b/src/commands/utils/monitor-control/enable_monitor.sh index b18a7c65..bf770053 100644 --- a/src/commands/utils/monitor-control/enable_monitor.sh +++ b/src/commands/utils/monitor-control/enable_monitor.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh RESET='\033[0m' BOLD='\033[1m' diff --git a/src/commands/utils/monitor-control/extend_displays.sh b/src/commands/utils/monitor-control/extend_displays.sh index 974b9e82..d93b2eb3 100644 --- a/src/commands/utils/monitor-control/extend_displays.sh +++ b/src/commands/utils/monitor-control/extend_displays.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to extend displays extend_displays() { diff --git a/src/commands/utils/monitor-control/manage_arrangement.sh b/src/commands/utils/monitor-control/manage_arrangement.sh index 7637641d..305e5210 100644 --- a/src/commands/utils/monitor-control/manage_arrangement.sh +++ b/src/commands/utils/monitor-control/manage_arrangement.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to manage monitor arrangement manage_arrangement() { diff --git a/src/commands/utils/monitor-control/reset_scaling.sh b/src/commands/utils/monitor-control/reset_scaling.sh index 73f03ca8..3a80e856 100644 --- a/src/commands/utils/monitor-control/reset_scaling.sh +++ b/src/commands/utils/monitor-control/reset_scaling.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to reset scaling back to 1 (native resolution) for all monitors reset_scaling() { diff --git a/src/commands/utils/monitor-control/scale_monitor.sh b/src/commands/utils/monitor-control/scale_monitor.sh index 4c88738b..eb680187 100644 --- a/src/commands/utils/monitor-control/scale_monitor.sh +++ b/src/commands/utils/monitor-control/scale_monitor.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to scale smaller monitors to the highest resolution of a bigger monitor scale_monitors() { diff --git a/src/commands/utils/monitor-control/set_primary_monitor.sh b/src/commands/utils/monitor-control/set_primary_monitor.sh index 68d8a17f..f10c8d34 100644 --- a/src/commands/utils/monitor-control/set_primary_monitor.sh +++ b/src/commands/utils/monitor-control/set_primary_monitor.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh # Function to set a monitor as primary set_primary_monitor() { diff --git a/src/commands/utils/monitor-control/set_resolutions.sh b/src/commands/utils/monitor-control/set_resolutions.sh index 40eab9f6..636cf573 100644 --- a/src/commands/utils/monitor-control/set_resolutions.sh +++ b/src/commands/utils/monitor-control/set_resolutions.sh @@ -1,5 +1,5 @@ #!/bin/bash -source ./utils/monitor-control/utility_functions.sh +source ./utility_functions.sh RESET='\033[0m' BOLD='\033[1m' diff --git a/src/running_command.rs b/src/running_command.rs index b2b30291..55cc8e79 100644 --- a/src/running_command.rs +++ b/src/running_command.rs @@ -137,7 +137,10 @@ impl RunningCommand { cmd.arg(prompt); } Command::LocalFile(file) => { - cmd.arg(file); + cmd.arg(&file); + if let Some(parent) = file.parent() { + cmd.cwd(parent); + } } Command::None => panic!("Command::None was treated as a command"), } From 3f81087ae5dcd813f1be5943ca62cb1a5ac7cb02 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:20:05 -0700 Subject: [PATCH 10/14] chore: Slightly prettify utils toml --- src/commands/utils/tab_data.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/utils/tab_data.toml b/src/commands/utils/tab_data.toml index 594ec62f..41490334 100644 --- a/src/commands/utils/tab_data.toml +++ b/src/commands/utils/tab_data.toml @@ -10,11 +10,11 @@ script = "bluetooth-control.sh" [[data]] name = "Monitor Control" -preconditions = [ - { matches = true, data = { environment = "XDG_SESSION_TYPE" }, values = [ - "x11", - ] }, -] + +[[data.preconditions]] +matches = true +data = { environment = "XDG_SESSION_TYPE" } +values = ["x11"] [[data.entries]] name = "Set Resolution" From 583a0038a395ee637932eb793cef61ee9c78cb99 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Fri, 16 Aug 2024 08:41:52 -0700 Subject: [PATCH 11/14] feat: Add AUR Helpers to system setup data --- src/commands/system-setup/tab_data.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/commands/system-setup/tab_data.toml b/src/commands/system-setup/tab_data.toml index 6ede3012..a25cf5c7 100644 --- a/src/commands/system-setup/tab_data.toml +++ b/src/commands/system-setup/tab_data.toml @@ -1,5 +1,16 @@ name = "System Setup" +[[data]] +name = "Arch Linux" + +[[data.entries]] +name = "Yay AUR Helper" +script = "arch/yay-setup.sh" + +[[data.entries]] +name = "Paru AUR Helper" +script = "arch/paru-setup.sh" + [[data]] name = "Full System Update" script = "system-update.sh" From 4fb7fcca7a2af065d6bed740537bc397aa45c0a8 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Fri, 16 Aug 2024 08:48:09 -0700 Subject: [PATCH 12/14] feat: Add 'command_exists' precondition type --- Cargo.lock | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/tabs.rs | 5 +++++ 3 files changed, 63 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index b9bed308..142e1b61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,6 +275,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "filedescriptor" version = "0.8.2" @@ -308,6 +318,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -411,6 +430,12 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -648,6 +673,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -933,6 +971,7 @@ dependencies = [ "tempdir", "toml", "tui-term", + "which", ] [[package]] @@ -1079,6 +1118,18 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "which" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1267,6 +1318,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "zerocopy" version = "0.7.34" diff --git a/Cargo.toml b/Cargo.toml index 5a336acf..03a8205c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ include_dir = "0.7.4" tempdir = "0.3.7" serde = { version = "1.0.205", features = ["derive"] } toml = "0.8.19" +which = "6.0.2" [[bin]] name = "linutil" diff --git a/src/tabs.rs b/src/tabs.rs index 6f32a8dc..97969326 100644 --- a/src/tabs.rs +++ b/src/tabs.rs @@ -50,6 +50,9 @@ impl Entry { == *matches }) } + SystemDataType::CommandExists => values + .iter() + .all(|command| which::which(command).is_ok() == *matches), } }, ) @@ -72,6 +75,8 @@ enum SystemDataType { Environment(String), #[serde(rename = "file")] File(PathBuf), + #[serde(rename = "command_exists")] + CommandExists, } #[derive(Hash, Eq, PartialEq)] From 943c48b97ababa3308a3d0e01773e9b2906bc94f Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Fri, 16 Aug 2024 08:48:18 -0700 Subject: [PATCH 13/14] feat: Update ArchLinux directory to depend on existence of pacman --- src/commands/system-setup/tab_data.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/commands/system-setup/tab_data.toml b/src/commands/system-setup/tab_data.toml index a25cf5c7..53ec55cd 100644 --- a/src/commands/system-setup/tab_data.toml +++ b/src/commands/system-setup/tab_data.toml @@ -3,6 +3,11 @@ name = "System Setup" [[data]] name = "Arch Linux" +[[data.preconditions]] +matches = true +data = "command_exists" +values = ["pacman"] + [[data.entries]] name = "Yay AUR Helper" script = "arch/yay-setup.sh" From 7ff4b9fd0e13bb44f2cb4ecd125da9a8f21f0460 Mon Sep 17 00:00:00 2001 From: Liam <33645555+lj3954@users.noreply.github.com> Date: Sat, 17 Aug 2024 11:27:46 -0700 Subject: [PATCH 14/14] fix: Remove unused 'compat' argument --- src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3deff4b3..0f3fb38f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,16 +30,13 @@ use tempdir::TempDir; // Linux utility toolbox #[derive(Debug, Parser)] struct Args { - /// Enable compatibility mode (disable icons and RGB colors) - #[arg(short, long, default_value_t = false)] - compat: bool, - #[arg(long, default_value_t = false)] - #[clap(help = "Show all available options, disregarding compatibility checks (UNSAFE)")] - override_validation: bool, #[arg(short, long, value_enum)] #[arg(default_value_t = Theme::Default)] #[arg(help = "Set the theme to use in the application")] theme: Theme, + #[arg(long, default_value_t = false)] + #[clap(help = "Show all available options, disregarding compatibility checks (UNSAFE)")] + override_validation: bool, } fn main() -> std::io::Result<()> {