Merge pull request #274 from lj3954/restructure_linutil

refactor: Split linutil into TUI and Core crates
This commit is contained in:
Chris Titus 2024-09-12 13:34:56 -05:00 committed by GitHub
commit 7104991472
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 167 additions and 139 deletions

64
Cargo.lock generated
View File

@ -86,9 +86,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.87" version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
@ -450,6 +450,34 @@ version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "linutil_core"
version = "0.1.0"
dependencies = [
"ego-tree",
"include_dir",
"serde",
"tempdir",
"toml",
"which",
]
[[package]]
name = "linutil_tui"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"crossterm",
"ego-tree",
"linutil_core",
"oneshot",
"portable-pty",
"ratatui",
"tui-term",
"unicode-width",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
@ -678,9 +706,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
] ]
@ -696,9 +724,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.36" version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
@ -972,26 +1000,6 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "tui"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"crossterm",
"ego-tree",
"include_dir",
"oneshot",
"portable-pty",
"ratatui",
"serde",
"tempdir",
"toml",
"tui-term",
"unicode-width",
"which",
]
[[package]] [[package]]
name = "tui-term" name = "tui-term"
version = "0.1.13" version = "0.1.13"
@ -1004,9 +1012,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"

View File

@ -1,34 +1,16 @@
[package] [workspace.package]
name = "tui" license = "MIT"
version = "0.1.0" version = "0.1.0"
edition = "2021"
[dependencies] [workspace.dependencies]
clap = { version = "4.5.16", features = ["derive"] }
crossterm = "0.28.1"
ego-tree = "0.6.2" ego-tree = "0.6.2"
oneshot = "0.1.8"
portable-pty = "0.8.1"
ratatui = "0.28.1"
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.3"
unicode-width = "0.1.13"
[build-dependencies]
chrono = "0.4.33"
[[bin]]
name = "linutil"
path = "src/main.rs"
[workspace]
members = ["tui", "core"]
resolver = "2"
[profile.release] [profile.release]
opt-level = 3 opt-level = "z"
debug = false debug = false
lto = true lto = true
codegen-units = 1 codegen-units = 1

View File

@ -1,12 +0,0 @@
fn main() {
// Rebuild program if any file in commands directory changes.
println!("cargo:rerun-if-changed=src/commands");
// Rerun build script if any code is modified
println!("cargo:rerun-if-changed=src");
// Add current date as a variable to be displayed in the 'Linux Toolbox' text.
println!(
"cargo:rustc-env=BUILD_DATE={}",
chrono::Local::now().format("%Y-%m-%d")
);
}

13
core/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "linutil_core"
edition = "2021"
version.workspace = true
license.workspace = true
[dependencies]
include_dir = "0.7.4"
tempdir = "0.3.7"
serde = { version = "1.0.205", features = ["derive"] }
toml = "0.8.19"
which = "6.0.3"
ego-tree = { workspace = true }

View File

@ -1,7 +1,42 @@
use crate::running_command::Command; use crate::{Command, ListNode, Tab};
use ego_tree::{NodeMut, Tree}; use ego_tree::{NodeMut, Tree};
use include_dir::{include_dir, Dir};
use serde::Deserialize; use serde::Deserialize;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tempdir::TempDir;
const TAB_DATA: Dir = include_dir!("$CARGO_MANIFEST_DIR/../tabs");
pub fn get_tabs(validate: bool) -> Vec<Tab> {
let tab_files = TabList::get_tabs();
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<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
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct TabList { struct TabList {
@ -79,49 +114,6 @@ enum SystemDataType {
CommandExists, CommandExists,
} }
#[derive(Hash, Eq, PartialEq)]
pub struct Tab {
pub name: String,
pub tree: Tree<ListNode>,
}
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct ListNode {
pub name: String,
pub command: Command,
}
pub fn get_tabs(command_dir: &Path, validate: bool) -> Vec<Tab> {
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<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>) { fn filter_entries(entries: &mut Vec<Entry>) {
entries.retain_mut(|entry| { entries.retain_mut(|entry| {
if !entry.is_supported() { if !entry.is_supported() {
@ -176,15 +168,21 @@ fn create_directory(data: Vec<Entry>, node: &mut NodeMut<ListNode>, command_dir:
} }
} }
} }
impl TabList { impl TabList {
fn get_tabs(command_dir: &Path) -> Vec<PathBuf> { fn get_tabs() -> Vec<PathBuf> {
let tab_files = std::fs::read_to_string(command_dir.join("tabs.toml")) let temp_dir = TempDir::new("linutil_scripts").unwrap().into_path();
.expect("Failed to read tabs.toml"); TAB_DATA
.extract(&temp_dir)
.expect("Failed to extract the saved directory");
let tab_files =
std::fs::read_to_string(temp_dir.join("tabs.toml")).expect("Failed to read tabs.toml");
let data: Self = toml::from_str(&tab_files).expect("Failed to parse tabs.toml"); let data: Self = toml::from_str(&tab_files).expect("Failed to parse tabs.toml");
data.directories data.directories
.into_iter() .iter()
.map(|path| command_dir.join(path).join("tab_data.toml")) .map(|path| temp_dir.join(path).join("tab_data.toml"))
.collect() .collect()
} }
} }

25
core/src/lib.rs Normal file
View File

@ -0,0 +1,25 @@
mod inner;
use ego_tree::Tree;
use std::path::PathBuf;
pub use inner::get_tabs;
#[derive(Clone, Hash, Eq, PartialEq)]
pub enum Command {
Raw(String),
LocalFile(PathBuf),
None, // Directory
}
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct Tab {
pub name: String,
pub tree: Tree<ListNode>,
}
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct ListNode {
pub name: String,
pub command: Command,
}

23
tui/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "linutil_tui"
edition = "2021"
version.workspace = true
license.workspace = true
[dependencies]
clap = { version = "4.5.16", features = ["derive"] }
crossterm = "0.28.1"
ego-tree = { workspace = true }
oneshot = "0.1.8"
portable-pty = "0.8.1"
ratatui = "0.28.1"
tui-term = "0.1.12"
unicode-width = "0.1.13"
linutil_core = { path = "../core" }
[build-dependencies]
chrono = "0.4.33"
[[bin]]
name = "linutil"
path = "src/main.rs"

7
tui/build.rs Normal file
View File

@ -0,0 +1,7 @@
fn main() {
// Add current date as a variable to be displayed in the 'Linux Toolbox' text.
println!(
"cargo:rustc-env=BUILD_DATE={}",
chrono::Local::now().format("%Y-%m-%d")
);
}

View File

@ -1,6 +1,7 @@
use crate::{state::ListEntry, tabs::Tab, theme::Theme}; use crate::{state::ListEntry, theme::Theme};
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::{KeyCode, KeyEvent};
use ego_tree::NodeId; use ego_tree::NodeId;
use linutil_core::Tab;
use ratatui::{ use ratatui::{
layout::{Position, Rect}, layout::{Position, Rect},
style::Style, style::Style,

View File

@ -1,9 +1,9 @@
use crate::{ use crate::{
float::FloatContent, float::FloatContent,
hint::{Shortcut, ShortcutList}, hint::{Shortcut, ShortcutList},
running_command::Command,
}; };
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::{KeyCode, KeyEvent};
use linutil_core::Command;
use ratatui::{ use ratatui::{
layout::Rect, layout::Rect,
style::{Style, Stylize}, style::{Style, Stylize},

View File

@ -4,7 +4,6 @@ mod floating_text;
mod hint; mod hint;
mod running_command; mod running_command;
pub mod state; pub mod state;
mod tabs;
mod theme; mod theme;
use std::{ use std::{
@ -21,13 +20,11 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand, ExecutableCommand,
}; };
use include_dir::include_dir;
use ratatui::{ use ratatui::{
backend::{Backend, CrosstermBackend}, backend::{Backend, CrosstermBackend},
Terminal, Terminal,
}; };
use state::AppState; use state::AppState;
use tempdir::TempDir;
// Linux utility toolbox // Linux utility toolbox
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
@ -44,13 +41,7 @@ struct Args {
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let args = Args::parse(); let args = Args::parse();
let commands_dir = include_dir!("src/commands"); let mut state = AppState::new(args.theme, args.override_validation);
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(args.theme, temp_dir.path(), args.override_validation);
stdout().execute(EnterAlternateScreen)?; stdout().execute(EnterAlternateScreen)?;
enable_raw_mode()?; enable_raw_mode()?;

View File

@ -3,6 +3,7 @@ use crate::{
hint::{Shortcut, ShortcutList}, hint::{Shortcut, ShortcutList},
}; };
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use linutil_core::Command;
use oneshot::{channel, Receiver}; use oneshot::{channel, Receiver};
use portable_pty::{ use portable_pty::{
ChildKiller, CommandBuilder, ExitStatus, MasterPty, NativePtySystem, PtySize, PtySystem, ChildKiller, CommandBuilder, ExitStatus, MasterPty, NativePtySystem, PtySize, PtySystem,
@ -16,7 +17,6 @@ use ratatui::{
}; };
use std::{ use std::{
io::Write, io::Write,
path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
thread::JoinHandle, thread::JoinHandle,
}; };
@ -25,13 +25,6 @@ use tui_term::{
widget::PseudoTerminal, widget::PseudoTerminal,
}; };
#[derive(Clone, Hash, Eq, PartialEq)]
pub enum Command {
Raw(String),
LocalFile(PathBuf),
None, // Directory
}
pub struct RunningCommand { pub struct RunningCommand {
/// A buffer to save all the command output (accumulates, until the command exits) /// A buffer to save all the command output (accumulates, until the command exits)
buffer: Arc<Mutex<Vec<u8>>>, buffer: Arc<Mutex<Vec<u8>>>,

View File

@ -3,12 +3,12 @@ use crate::{
float::{Float, FloatContent}, float::{Float, FloatContent},
floating_text::FloatingText, floating_text::FloatingText,
hint::{draw_shortcuts, SHORTCUT_LINES}, hint::{draw_shortcuts, SHORTCUT_LINES},
running_command::{Command, RunningCommand}, running_command::RunningCommand,
tabs::{ListNode, Tab},
theme::Theme, theme::Theme,
}; };
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use ego_tree::NodeId; use ego_tree::NodeId;
use linutil_core::{Command, ListNode, Tab};
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout}, layout::{Alignment, Constraint, Direction, Layout},
style::{Style, Stylize}, style::{Style, Stylize},
@ -16,7 +16,6 @@ use ratatui::{
widgets::{Block, Borders, List, ListState, Paragraph}, widgets::{Block, Borders, List, ListState, Paragraph},
Frame, Frame,
}; };
use std::path::Path;
pub struct AppState { pub struct AppState {
/// Selected theme /// Selected theme
@ -50,8 +49,8 @@ pub struct ListEntry {
} }
impl AppState { impl AppState {
pub fn new(theme: Theme, temp_path: &Path, override_validation: bool) -> Self { pub fn new(theme: Theme, override_validation: bool) -> Self {
let tabs = crate::tabs::get_tabs(temp_path, !override_validation); let tabs = linutil_core::get_tabs(!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,