Merge pull request #153 from lj3954/simple_additions

feat: Use TOML data to add tabs to UI.
This commit is contained in:
Chris Titus 2024-08-20 15:41:34 -05:00 committed by GitHub
commit 5b2f5f20b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 542 additions and 220 deletions

153
Cargo.lock generated
View File

@ -269,6 +269,22 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" 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]] [[package]]
name = "filedescriptor" name = "filedescriptor"
version = "0.8.2" version = "0.8.2"
@ -302,6 +318,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 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]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.60" version = "0.1.60"
@ -344,6 +369,16 @@ dependencies = [
"quote", "quote",
] ]
[[package]]
name = "indexmap"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]] [[package]]
name = "ioctl-rs" name = "ioctl-rs"
version = "0.1.6" version = "0.1.6"
@ -395,6 +430,12 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@ -420,6 +461,12 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.6.5" version = "0.6.5"
@ -626,6 +673,19 @@ dependencies = [
"winapi", "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]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.17" version = "1.0.17"
@ -644,6 +704,35 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 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]] [[package]]
name = "serial" name = "serial"
version = "0.4.0" version = "0.4.0"
@ -832,6 +921,40 @@ dependencies = [
"syn", "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]] [[package]]
name = "tui" name = "tui"
version = "0.1.0" version = "0.1.0"
@ -844,8 +967,11 @@ dependencies = [
"oneshot", "oneshot",
"portable-pty", "portable-pty",
"ratatui", "ratatui",
"serde",
"tempdir", "tempdir",
"toml",
"tui-term", "tui-term",
"which",
] ]
[[package]] [[package]]
@ -992,6 +1118,18 @@ version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -1162,6 +1300,15 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.10.1"
@ -1171,6 +1318,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.34" version = "0.7.34"

View File

@ -14,6 +14,9 @@ ratatui = "0.27.0"
tui-term = "0.1.12" tui-term = "0.1.12"
include_dir = "0.7.4" include_dir = "0.7.4"
tempdir = "0.3.7" tempdir = "0.3.7"
serde = { version = "1.0.205", features = ["derive"] }
toml = "0.8.19"
which = "6.0.2"
[[bin]] [[bin]]
name = "linutil" name = "linutil"

4
build.rs Normal file
View File

@ -0,0 +1,4 @@
fn main() {
// Rebuild program if any file in commands directory changes.
println!("cargo:rerun-if-changed=src/commands");
}

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
setupAlacritty() { setupAlacritty() {
echo "Install Alacritty if not already installed..." echo "Install Alacritty if not already installed..."

View File

@ -1,5 +1,5 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
makeDWM(){ makeDWM(){
cd $HOME && git clone https://github.com/ChrisTitusTech/dwm-titus.git # CD to Home directory to install dwm-titus cd $HOME && git clone https://github.com/ChrisTitusTech/dwm-titus.git # CD to Home directory to install dwm-titus

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
setupKitty() { setupKitty() {
echo "Install Kitty if not already installed..." echo "Install Kitty if not already installed..."

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
setupRofi() { setupRofi() {
echo "Install Rofi if not already installed..." echo "Install Rofi if not already installed..."

View File

@ -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"

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
. ./common-script.sh . ../common-script.sh
# Function to install zsh # Function to install zsh
install_zsh() { install_zsh() {

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
installPkg() { installPkg() {
echo "Install UFW if not already installed..." echo "Install UFW if not already installed..."

View File

@ -0,0 +1,5 @@
name = "Security"
[[data]]
name = "Firewall Baselines (CTT)"
script = "firewall-baselines.sh"

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
# Check if the home directory and linuxtoolbox folder exist, create them if they don't # Check if the home directory and linuxtoolbox folder exist, create them if they don't
LINUXTOOLBOXDIR="$HOME/linuxtoolbox" LINUXTOOLBOXDIR="$HOME/linuxtoolbox"

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
installDepend() { installDepend() {
## Check for dependencies. ## Check for dependencies.

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
# Check if the home directory and linuxtoolbox folder exist, create them if they don't # Check if the home directory and linuxtoolbox folder exist, create them if they don't
LINUXTOOLBOXDIR="$HOME/linuxtoolbox" LINUXTOOLBOXDIR="$HOME/linuxtoolbox"

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
removeSnaps() { removeSnaps() {
case $PACKAGER in case $PACKAGER in

View File

@ -1,6 +1,6 @@
#!/bin/sh -e #!/bin/sh -e
. ./common-script.sh . ../common-script.sh
fastUpdate() { fastUpdate() {
case ${PACKAGER} in case ${PACKAGER} in

View File

@ -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"

1
src/commands/tabs.toml Normal file
View File

@ -0,0 +1 @@
directories = ["system-setup", "applications-setup", "security", "utils"]

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
# Function to auto-detect displays and set common resolution # Function to auto-detect displays and set common resolution
auto_detect_displays() { auto_detect_displays() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
# Function to change monitor orientation # Function to change monitor orientation
change_orientation() { change_orientation() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
RESET='\033[0m' RESET='\033[0m'
BOLD='\033[1m' BOLD='\033[1m'

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
# Function to duplicate displays # Function to duplicate displays
duplicate_displays() { duplicate_displays() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
RESET='\033[0m' RESET='\033[0m'
BOLD='\033[1m' BOLD='\033[1m'

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
# Function to extend displays # Function to extend displays
extend_displays() { extend_displays() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
# Function to manage monitor arrangement # Function to manage monitor arrangement
manage_arrangement() { manage_arrangement() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/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 # Function to reset scaling back to 1 (native resolution) for all monitors
reset_scaling() { reset_scaling() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/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 # Function to scale smaller monitors to the highest resolution of a bigger monitor
scale_monitors() { scale_monitors() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
# Function to set a monitor as primary # Function to set a monitor as primary
set_primary_monitor() { set_primary_monitor() {

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
source ./utils/monitor-control/utility_functions.sh source ./utility_functions.sh
RESET='\033[0m' RESET='\033[0m'
BOLD='\033[1m' BOLD='\033[1m'

View File

@ -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

View File

@ -7,7 +7,6 @@ use ratatui::{
widgets::{Block, Borders, List}, widgets::{Block, Borders, List},
Frame, Frame,
}; };
use std::path::PathBuf;
pub struct FloatingText { pub struct FloatingText {
text: Vec<String>, text: Vec<String>,
@ -19,7 +18,7 @@ impl FloatingText {
Self { text, scroll: 0 } Self { text, scroll: 0 }
} }
pub fn from_command(command: &Command, mut full_path: PathBuf) -> Option<Self> { pub fn from_command(command: &Command) -> Option<Self> {
let lines = match command { let lines = match command {
Command::Raw(cmd) => { Command::Raw(cmd) => {
// Reconstruct the line breaks and file formatting after the // Reconstruct the line breaks and file formatting after the
@ -27,9 +26,8 @@ impl FloatingText {
cmd.lines().map(|line| line.to_string()).collect() cmd.lines().map(|line| line.to_string()).collect()
} }
Command::LocalFile(file_path) => { Command::LocalFile(file_path) => {
full_path.push(file_path); let file_contents = std::fs::read_to_string(file_path)
let file_contents = std::fs::read_to_string(&full_path) .map_err(|_| format!("File not found: {:?}", file_path))
.map_err(|_| format!("File not found: {:?}", &full_path))
.unwrap(); .unwrap();
file_contents.lines().map(|line| line.to_string()).collect() file_contents.lines().map(|line| line.to_string()).collect()
} }

View File

@ -34,20 +34,21 @@ struct Args {
#[arg(default_value_t = Theme::Default)] #[arg(default_value_t = Theme::Default)]
#[arg(help = "Set the theme to use in the application")] #[arg(help = "Set the theme to use in the application")]
theme: Theme, 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<()> { fn main() -> std::io::Result<()> {
let args = Args::parse(); let args = Args::parse();
let theme = args.theme;
let commands_dir = include_dir!("src/commands"); let commands_dir = include_dir!("src/commands");
let temp_dir: TempDir = TempDir::new("linutil_scripts").unwrap(); let temp_dir: TempDir = TempDir::new("linutil_scripts").unwrap();
commands_dir commands_dir
.extract(temp_dir.path()) .extract(temp_dir.path())
.expect("Failed to extract the saved directory"); .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)?; stdout().execute(EnterAlternateScreen)?;
enable_raw_mode()?; enable_raw_mode()?;

View File

@ -13,7 +13,7 @@ use ratatui::{
}; };
use std::{ use std::{
io::Write, io::Write,
path::Path, path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
thread::JoinHandle, thread::JoinHandle,
}; };
@ -22,10 +22,10 @@ use tui_term::{
widget::PseudoTerminal, widget::PseudoTerminal,
}; };
#[derive(Clone)] #[derive(Clone, Hash, Eq, PartialEq)]
pub enum Command { pub enum Command {
Raw(&'static str), Raw(String),
LocalFile(&'static str), LocalFile(PathBuf),
None, // Directory None, // Directory
} }
@ -126,7 +126,7 @@ impl FloatContent for RunningCommand {
} }
impl RunningCommand { impl RunningCommand {
pub fn new(command: Command, temp_path: &Path) -> Self { pub fn new(command: Command) -> Self {
let pty_system = NativePtySystem::default(); let pty_system = NativePtySystem::default();
// Build the command based on the provided Command enum variant // Build the command based on the provided Command enum variant
@ -137,13 +137,14 @@ impl RunningCommand {
cmd.arg(prompt); cmd.arg(prompt);
} }
Command::LocalFile(file) => { 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"), Command::None => panic!("Command::None was treated as a command"),
} }
cmd.cwd(temp_path);
// Open a pseudo-terminal with initial size // Open a pseudo-terminal with initial size
let pair = pty_system let pair = pty_system
.openpty(PtySize { .openpty(PtySize {

View File

@ -2,7 +2,7 @@ use crate::{
float::{Float, FloatContent}, float::{Float, FloatContent},
floating_text::FloatingText, floating_text::FloatingText,
running_command::{Command, RunningCommand}, running_command::{Command, RunningCommand},
tabs::{ListNode, TABS}, tabs::{ListNode, Tab},
theme::Theme, theme::Theme,
}; };
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
@ -14,15 +14,15 @@ use ratatui::{
widgets::{Block, Borders, List, ListState, Paragraph}, widgets::{Block, Borders, List, ListState, Paragraph},
Frame, Frame,
}; };
use std::path::PathBuf; use std::path::Path;
pub struct AppState { pub struct AppState {
/// Selected theme /// Selected theme
pub theme: Theme, theme: Theme,
/// Path to the root of the unpacked files in /tmp
temp_path: PathBuf,
/// Currently focused area /// Currently focused area
focus: Focus, focus: Focus,
/// List of tabs
tabs: Vec<Tab>,
/// Current tab /// Current tab
current_tab: ListState, current_tab: ListState,
/// Current search query /// Current search query
@ -51,12 +51,13 @@ struct ListEntry {
} }
impl AppState { impl AppState {
pub fn new(theme: Theme, temp_path: PathBuf) -> Self { pub fn new(theme: Theme, temp_path: &Path, override_validation: bool) -> Self {
let root_id = TABS[0].tree.root().id(); let tabs = crate::tabs::get_tabs(temp_path, !override_validation);
let root_id = tabs[0].tree.root().id();
let mut state = Self { let mut state = Self {
theme, theme,
temp_path,
focus: Focus::List, focus: Focus::List,
tabs,
current_tab: ListState::default().with_selected(Some(0)), current_tab: ListState::default().with_selected(Some(0)),
search_query: String::new(), search_query: String::new(),
items: vec![], items: vec![],
@ -67,7 +68,8 @@ impl AppState {
state state
} }
pub fn draw(&mut self, frame: &mut Frame) { pub fn draw(&mut self, frame: &mut Frame) {
let longest_tab_display_len = TABS let longest_tab_display_len = self
.tabs
.iter() .iter()
.map(|tab| tab.name.len() + self.theme.tab_icon().len()) .map(|tab| tab.name.len() + self.theme.tab_icon().len())
.max() .max()
@ -85,7 +87,11 @@ impl AppState {
.constraints([Constraint::Length(3), Constraint::Min(1)]) .constraints([Constraint::Length(3), Constraint::Min(1)])
.split(horizontal[0]); .split(horizontal[0]);
let tabs = TABS.iter().map(|tab| tab.name).collect::<Vec<_>>(); let tabs = self
.tabs
.iter()
.map(|tab| tab.name.as_str())
.collect::<Vec<_>>();
let tab_hl_style = if let Focus::TabList = self.focus { let tab_hl_style = if let Focus::TabList = self.focus {
Style::default().reversed().fg(self.theme.tab_color()) Style::default().reversed().fg(self.theme.tab_color())
@ -186,7 +192,7 @@ impl AppState {
self.focus = Focus::List self.focus = Focus::List
} }
KeyCode::Char('j') | KeyCode::Down 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.current_tab.select_next();
self.refresh_tab(); self.refresh_tab();
@ -224,7 +230,7 @@ impl AppState {
} }
pub fn update_items(&mut self) { pub fn update_items(&mut self) {
if self.search_query.is_empty() { if self.search_query.is_empty() {
let curr = TABS[self.current_tab.selected().unwrap()] let curr = self.tabs[self.current_tab.selected().unwrap()]
.tree .tree
.get(*self.visit_stack.last().unwrap()) .get(*self.visit_stack.last().unwrap())
.unwrap(); .unwrap();
@ -241,7 +247,7 @@ impl AppState {
self.items.clear(); self.items.clear();
let query_lower = self.search_query.to_lowercase(); 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()]; let mut stack = vec![tab.tree.root().id()];
while let Some(node_id) = stack.pop() { while let Some(node_id) = stack.pop() {
let node = tab.tree.get(node_id).unwrap(); let node = tab.tree.get(node_id).unwrap();
@ -259,7 +265,7 @@ impl AppState {
stack.extend(node.children().map(|child| child.id())); 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) /// 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) { fn enable_preview(&mut self) {
if let Some(command) = self.get_selected_command(false) { 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); self.spawn_float(preview, 80, 80);
} }
} }
} }
fn handle_enter(&mut self) { fn handle_enter(&mut self) {
if let Some(cmd) = self.get_selected_command(true) { 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); self.spawn_float(command, 80, 80);
} }
} }
@ -323,7 +329,10 @@ impl AppState {
self.update_items(); self.update_items();
} }
fn refresh_tab(&mut self) { 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.selection.select(Some(0));
self.update_items(); self.update_items();
} }

View File

@ -1,175 +1,190 @@
use std::sync::LazyLock;
use ego_tree::{tree, Tree};
use crate::running_command::Command; use crate::running_command::Command;
use ego_tree::{NodeMut, Tree};
use serde::Deserialize;
use std::path::{Path, PathBuf};
#[derive(Deserialize)]
struct TabList {
directories: Vec<PathBuf>,
}
#[derive(Deserialize)]
struct TabEntry {
name: String,
data: Vec<Entry>,
}
#[derive(Deserialize)]
struct Entry {
name: String,
#[allow(dead_code)]
#[serde(default)]
description: String,
#[serde(default)]
preconditions: Option<Vec<Precondition>>,
#[serde(default)]
entries: Option<Vec<Entry>>,
#[serde(default)]
command: Option<String>,
#[serde(default)]
script: Option<PathBuf>,
}
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<String>,
}
#[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 struct Tab {
pub name: &'static str, pub name: String,
pub tree: Tree<ListNode>, pub tree: Tree<ListNode>,
} }
#[derive(Clone)] #[derive(Clone, Hash, Eq, PartialEq)]
pub struct ListNode { pub struct ListNode {
pub name: &'static str, pub name: String,
pub command: Command, pub command: Command,
} }
pub static TABS: LazyLock<Vec<Tab>> = LazyLock::new(|| { pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec<Tab> {
vec![ let tab_files = TabList::get_tabs(command_dir);
Tab { let tabs = tab_files.into_iter().map(|path| {
name: "System Setup", let directory = path.parent().unwrap().to_owned();
tree: tree!(ListNode { let data = std::fs::read_to_string(path).expect("Failed to read tab data");
name: "root", let mut tab_data: TabEntry = toml::from_str(&data).expect("Failed to parse tab data");
command: Command::None,
} => { if validate {
ListNode { filter_entries(&mut tab_data.data);
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"),
} }
}, (tab_data, directory)
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",
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 tabs: Vec<Tab> = 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();
if tabs.is_empty() {
panic!("No tabs found");
}
tabs
}
fn filter_entries(entries: &mut Vec<Entry>) {
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<Entry>, node: &mut NodeMut<ListNode>, 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,
});
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<PathBuf> {
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()
}
}