mirror of
https://github.com/ChrisTitusTech/linutil.git
synced 2024-11-05 13:15:21 +00:00
feat: Update tabs.rs to use tab data
This commit is contained in:
parent
d07946fc41
commit
e73c978d95
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -7,7 +7,6 @@ use ratatui::{
|
|||
widgets::{Block, Borders, List},
|
||||
Frame,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct FloatingText {
|
||||
text: Vec<String>,
|
||||
|
@ -19,7 +18,7 @@ impl FloatingText {
|
|||
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 {
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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 {
|
||||
|
|
13
src/state.rs
13
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);
|
||||
}
|
||||
}
|
||||
|
|
208
src/tabs.rs
208
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<String>,
|
||||
#[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>,
|
||||
struct TabEntry {
|
||||
name: String,
|
||||
data: Vec<Entry>,
|
||||
}
|
||||
|
||||
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 {
|
||||
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<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();
|
||||
let mut tabs: Vec<Tab> = 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<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();
|
||||
|
||||
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::<Vec<_>>();
|
||||
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<Entry>) {
|
||||
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<Entry>, node: &mut NodeMut<ListNode>, 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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user