feat: Update tabs.rs to use tab data

This commit is contained in:
Liam 2024-08-13 14:55:59 -07:00
parent d07946fc41
commit e73c978d95
No known key found for this signature in database
7 changed files with 108 additions and 135 deletions

4
Cargo.lock generated
View File

@ -672,9 +672,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.122" version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",

View File

@ -14,8 +14,8 @@ 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_json = "1.0.122"
serde = { version = "1.0.205", features = ["derive"] } serde = { version = "1.0.205", features = ["derive"] }
serde_json = "1.0.124"
[[bin]] [[bin]]
name = "linutil" name = "linutil"

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

@ -52,7 +52,7 @@ fn main() -> std::io::Result<()> {
.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(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, PathBuf}, path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
thread::JoinHandle, thread::JoinHandle,
}; };
@ -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
@ -142,8 +142,6 @@ impl RunningCommand {
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

@ -14,13 +14,11 @@ 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
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 /// List of tabs
@ -53,12 +51,11 @@ 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 tabs = crate::tabs::get_tabs(&temp_path, true); let tabs = crate::tabs::get_tabs(temp_path, !override_validation);
let root_id = tabs[0].tree.root().id(); 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, tabs,
current_tab: ListState::default().with_selected(Some(0)), current_tab: ListState::default().with_selected(Some(0)),
@ -304,14 +301,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);
} }
} }

View File

@ -1,28 +1,36 @@
use crate::running_command::Command; use crate::running_command::Command;
use ego_tree::{NodeId, Tree}; use ego_tree::{NodeMut, Tree};
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::path::{Path, PathBuf};
collections::HashMap,
path::{Path, PathBuf},
};
#[derive(Deserialize)] #[derive(Deserialize)]
struct ScriptInfo { struct TabEntry {
// Path to the script file in the UI, formatted as an array of directory names (first of which being the tab) name: String,
ui_path: Vec<String>, data: Vec<Entry>,
#[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<Vec<Precondition>>,
#[serde(default)]
// Optional command. This is used for adding "raw" commands to the UI.
command: Option<String>,
} }
impl ScriptInfo { #[derive(Deserialize)]
enum Entry {
#[serde(rename = "directory")]
Directory(EntryData<Vec<Entry>>),
#[serde(rename = "command")]
Command(EntryData<String>),
#[serde(rename = "script")]
Script(EntryData<PathBuf>),
}
#[derive(Deserialize)]
struct EntryData<T> {
name: String,
#[allow(dead_code)]
#[serde(default)]
description: String,
data: T,
#[serde(default)]
preconditions: Option<Vec<Precondition>>,
}
impl<T> EntryData<T> {
fn is_supported(&self) -> bool { fn is_supported(&self) -> bool {
self.preconditions.as_deref().map_or(true, |preconditions| { self.preconditions.as_deref().map_or(true, |preconditions| {
preconditions.iter().all( preconditions.iter().all(
@ -79,107 +87,79 @@ pub struct ListNode {
} }
pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec<Tab> { pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec<Tab> {
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<PathBuf> =
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<Vec<String>, (String, NodeId)> = HashMap::new(); if validate {
let mut tabs: Vec<Tab> = Vec::new(); filter_entries(&mut tab_data.data);
}
(tab_data, directory)
});
for (json_file, script) in scripts { let tabs: Vec<Tab> = tabs
let json_text = std::fs::read_to_string(&json_file).unwrap(); .map(|(TabEntry { name, data }, directory)| {
let script_info: ScriptInfo = let mut tree = Tree::new(ListNode {
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(), name: "root".to_string(),
command: Command::None, command: Command::None,
}), });
}; let mut root = tree.root_mut();
let root_id = tab.tree.root().id(); create_directory(data, &mut root, &directory);
tabs.push(tab); Tab { name, tree }
paths.insert(path, (tab_name, root_id)); })
} else { .collect();
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 command = ListNode {
name: script_info.ui_path.last().unwrap().clone(),
command,
};
parent.append(command);
}
if tabs.is_empty() { if tabs.is_empty() {
panic!("No tabs found."); panic!("No tabs found");
} }
tabs tabs
} }
fn get_script_list(directory: &Path) -> Vec<(PathBuf, PathBuf)> { fn filter_entries(entries: &mut Vec<Entry>) {
let mut entries = std::fs::read_dir(directory) entries.retain_mut(|entry| match entry {
.expect("Command directory does not exist.") Entry::Script(entry) => entry.is_supported(),
.flatten() Entry::Command(entry) => entry.is_supported(),
.collect::<Vec<_>>(); Entry::Directory(entry) if !entry.is_supported() => false,
entries.sort_by_key(|d| d.path()); Entry::Directory(entry) => {
filter_entries(&mut entry.data);
!entry.data.is_empty()
}
});
}
entries fn create_directory(data: Vec<Entry>, node: &mut NodeMut<ListNode>, command_dir: &Path) {
.into_iter() for entry in data {
.filter_map(|entry| { match entry {
let path = entry.path(); Entry::Directory(entry) => {
// Recursively iterate through directories let mut node = node.append(ListNode {
if entry.file_type().map_or(false, |f| f.is_dir()) { name: entry.name,
Some(get_script_list(&path)) command: Command::None,
} else { });
let is_json = path.extension().map_or(false, |ext| ext == "json"); create_directory(entry.data, &mut node, command_dir);
let script = path.with_extension("sh"); }
is_json.then_some(vec![(path, script)]) 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),
});
}
}
} }
})
.flatten()
.collect()
} }