diff --git a/Cargo.lock b/Cargo.lock index 36fae34a..142e1b61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,22 @@ 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 = "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" @@ -302,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" @@ -344,6 +369,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" @@ -395,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" @@ -420,6 +461,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" @@ -626,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" @@ -644,6 +704,35 @@ 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_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "serial" version = "0.4.0" @@ -832,6 +921,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" @@ -844,8 +967,11 @@ dependencies = [ "oneshot", "portable-pty", "ratatui", + "serde", "tempdir", + "toml", "tui-term", + "which", ] [[package]] @@ -992,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" @@ -1162,6 +1300,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" @@ -1171,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 cacbdcb3..03a8205c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ ratatui = "0.27.0" tui-term = "0.1.12" 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/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"); +} diff --git a/src/commands/applications-setup/alacritty-setup.sh b/src/commands/applications-setup/alacritty-setup.sh index 57fa181c..6911fd3c 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 df90467e..d8543fc7 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 3de69169..8088f594 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 c41495cb..236e42e2 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/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/applications-setup/zsh-setup.sh b/src/commands/applications-setup/zsh-setup.sh index b2746ba9..f3e7d0c1 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 5078ad12..69963083 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/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/1-compile-setup.sh b/src/commands/system-setup/1-compile-setup.sh index 384b2ef1..1130cee1 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 631a6ad3..4393b0b0 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 9c48c12e..e255ea76 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-update.sh b/src/commands/system-setup/system-update.sh similarity index 99% rename from src/commands/system-update.sh rename to src/commands/system-setup/system-update.sh index e88cd221..1277143a 100755 --- a/src/commands/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/system-setup/tab_data.toml b/src/commands/system-setup/tab_data.toml new file mode 100644 index 00000000..53ec55cd --- /dev/null +++ b/src/commands/system-setup/tab_data.toml @@ -0,0 +1,37 @@ +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" + +[[data.entries]] +name = "Paru AUR Helper" +script = "arch/paru-setup.sh" + +[[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.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/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/commands/utils/tab_data.toml b/src/commands/utils/tab_data.toml new file mode 100644 index 00000000..41490334 --- /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" + +[[data.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/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 6a6f5713..0f3fb38f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,20 +34,21 @@ struct Args { #[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<()> { let args = Args::parse(); - let theme = args.theme; - let commands_dir = include_dir!("src/commands"); let temp_dir: TempDir = TempDir::new("linutil_scripts").unwrap(); commands_dir .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(args.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 d07c0428..55cc8e79 100644 --- a/src/running_command.rs +++ b/src/running_command.rs @@ -13,7 +13,7 @@ use ratatui::{ }; use std::{ io::Write, - 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 } @@ -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 @@ -137,13 +137,14 @@ 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"), } - 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 5e31b521..66f99972 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}; @@ -14,15 +14,15 @@ use ratatui::{ widgets::{Block, Borders, List, ListState, Paragraph}, Frame, }; -use std::path::PathBuf; +use std::path::Path; pub struct AppState { /// Selected theme - pub theme: Theme, - /// Path to the root of the unpacked files in /tmp - temp_path: PathBuf, + theme: Theme, /// Currently focused area focus: Focus, + /// List of tabs + tabs: Vec, /// Current tab current_tab: ListState, /// Current search query @@ -51,12 +51,13 @@ struct ListEntry { } impl AppState { - pub fn new(theme: Theme, temp_path: PathBuf) -> Self { - let root_id = TABS[0].tree.root().id(); + 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)), search_query: String::new(), items: vec![], @@ -67,7 +68,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 +87,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 +192,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(); @@ -224,7 +230,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(); @@ -241,7 +247,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(); @@ -259,7 +265,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) @@ -299,14 +305,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); } } @@ -323,7 +329,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 fa4ce3dd..97969326 100644 --- a/src/tabs.rs +++ b/src/tabs.rs @@ -1,175 +1,190 @@ -use std::sync::LazyLock; - -use ego_tree::{tree, Tree}; - use crate::running_command::Command; +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, + data: Vec, +} + +#[derive(Deserialize)] +struct Entry { + name: String, + #[allow(dead_code)] + #[serde(default)] + description: String, + #[serde(default)] + preconditions: Option>, + #[serde(default)] + entries: Option>, + #[serde(default)] + command: Option, + #[serde(default)] + script: Option, +} + +impl Entry { + 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 + }) + } + SystemDataType::CommandExists => values + .iter() + .all(|command| which::which(command).is_ok() == *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), + #[serde(rename = "command_exists")] + CommandExists, +} + +#[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", +pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec { + let tab_files = TabList::get_tabs(command_dir); + let tabs = tab_files.into_iter().map(|path| { + 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); + } + (tab_data, directory) + }); + + let tabs: Vec = tabs + .map(|(TabEntry { name, data }, directory)| { + let mut tree = Tree::new(ListNode { + name: "root".to_string(), command: Command::None, - } => { - ListNode { - name: "Arch Linux", - command: Command::None, - } => { - ListNode { - name: "Yay AUR Helper", - command: Command::LocalFile("system-setup/arch/yay-setup.sh"), - }, - ListNode { - name: "Paru AUR Helper", - command: Command::LocalFile("system-setup/arch/paru-setup.sh"), - } - }, - 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"), - } - }), - }, - Tab { - name: "Applications Setup", - tree: tree!(ListNode { - name: "root", + }); + let mut root = tree.root_mut(); + create_directory(data, &mut root, &directory); + Tab { name, tree } + }) + .collect(); + + if tabs.is_empty() { + panic!("No tabs found"); + } + tabs +} + +fn filter_entries(entries: &mut Vec) { + 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 { + 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, - } => { - 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"), - } - }, - }), - }, - ] -}); + }); + 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() + } +}