Merge ad75771799cc451f09694ca3364e7553d485d8bd into efa6ff9cd2eb77204028de361be008a68338aeb8

This commit is contained in:
nyx 2025-02-06 15:16:28 -06:00 committed by GitHub
commit 009cf75a12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 116 additions and 68 deletions

View File

@ -27,25 +27,18 @@ curl -fsSL https://christitus.com/linuxdev | sh
### CLI arguments ### CLI arguments
Linutil supports various command-line arguments to customize its behavior. Here are some common arguments you can use: View available options by running:
- `-c, --config <CONFIG>` : Path to the configuration file.
- `--override-validation` : Show all available options, disregarding compatibility checks (UNSAFE).
- `--size-bypass` : Bypass the terminal size limit.
- `-y, --skip-confirmation` : Skip confirmation prompt before executing commands.
- `-t, --theme <THEME>` : Set the theme to use in the application [default: `default`] [possible values: `default`, `compatible`].
- `-h, --help` : Print help.
For more detailed usage, run:
```bash
curl -fsSL https://christitus.com/linux | sh -s -- --help
```
```bash ```bash
linutil --help linutil --help
``` ```
For installer options:
```bash
curl -fsSL https://christitus.com/linux | sh -s -- --help
```
## ⬇️ Installation ## ⬇️ Installation
Linutil is also available as a package in various repositories: Linutil is also available as a package in various repositories:
@ -142,7 +135,7 @@ Docs are now [here](https://github.com/Chris-Titus-Docs/linutil-docs)
## 🏅 Thanks to All Contributors ## 🏅 Thanks to All Contributors
Thank you to everyone who has contributed to the development of Linutil. Your efforts are greatly appreciated, and youre helping make this tool better for everyone! Thank you to everyone who has contributed to the development of Linutil. Your efforts are greatly appreciated, and you're helping make this tool better for everyone!
[![Contributors](https://contrib.rocks/image?repo=ChrisTitusTech/linutil)](https://github.com/ChrisTitusTech/linutil/graphs/contributors) [![Contributors](https://contrib.rocks/image?repo=ChrisTitusTech/linutil)](https://github.com/ChrisTitusTech/linutil/graphs/contributors)

View File

@ -120,7 +120,7 @@ enum EntryType {
impl Entry { impl Entry {
fn is_supported(&self) -> bool { fn is_supported(&self) -> bool {
self.preconditions.as_deref().map_or(true, |preconditions| { self.preconditions.as_deref().is_none_or(|preconditions| {
preconditions.iter().all( preconditions.iter().all(
|Precondition { |Precondition {
matches, matches,

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
imports_granularity = "Crate"

View File

@ -14,7 +14,7 @@ default = ["tips"]
tips = ["rand"] tips = ["rand"]
[dependencies] [dependencies]
clap = { version = "4.5.20", features = ["derive", "std"], default-features = false } clap = { version = "4.5.20", features = ["derive", "std", "help", "usage"], default-features = false }
oneshot = { version = "0.1.8", features = ["std"], default-features = false } oneshot = { version = "0.1.8", features = ["std"], default-features = false }
portable-pty = "0.8.1" portable-pty = "0.8.1"
ratatui = { version = "0.29.0", features = ["crossterm"], default-features = false } ratatui = { version = "0.29.0", features = ["crossterm"], default-features = false }

35
tui/src/cli.rs Normal file
View File

@ -0,0 +1,35 @@
use crate::theme::Theme;
use clap::Parser;
use std::path::PathBuf;
#[derive(Debug, Parser, Clone)]
pub struct Args {
/// Path to the configuration file
#[arg(short, long)]
pub config: Option<PathBuf>,
/// Set the theme to use in the application
#[arg(short, long, value_enum)]
#[arg(default_value_t = Theme::Default)]
pub theme: Theme,
/// Skip confirmation prompt before executing commands
#[arg(short = 'y', long)]
pub skip_confirmation: bool,
/// Show all available options, disregarding compatibility checks (UNSAFE)
#[arg(short = 'u', long)]
pub override_validation: bool,
/// Bypass the terminal size limit
#[arg(short = 's', long)]
pub size_bypass: bool,
/// Enable mouse interaction
#[arg(short = 'm', long)]
pub mouse: bool,
/// Bypass root user check
#[arg(short = 'r', long)]
pub bypass_root: bool,
}

View File

@ -1,3 +1,4 @@
mod cli;
mod confirmation; mod confirmation;
mod filter; mod filter;
mod float; mod float;
@ -11,7 +12,7 @@ mod theme;
#[cfg(feature = "tips")] #[cfg(feature = "tips")]
mod tips; mod tips;
use crate::theme::Theme; use crate::cli::Args;
use clap::Parser; use clap::Parser;
use ratatui::{ use ratatui::{
backend::CrosstermBackend, backend::CrosstermBackend,
@ -26,40 +27,17 @@ use ratatui::{
use state::AppState; use state::AppState;
use std::{ use std::{
io::{stdout, Result, Stdout}, io::{stdout, Result, Stdout},
path::PathBuf,
time::Duration, time::Duration,
}; };
// Linux utility toolbox
#[derive(Debug, Parser)]
pub struct Args {
#[arg(short, long, help = "Path to the configuration file")]
config: Option<PathBuf>,
#[arg(short, long, value_enum)]
#[arg(default_value_t = Theme::Default)]
#[arg(help = "Set the theme to use in the application")]
theme: Theme,
#[arg(
short = 'y',
long,
help = "Skip confirmation prompt before executing commands"
)]
skip_confirmation: bool,
#[arg(long, default_value_t = false)]
#[clap(help = "Show all available options, disregarding compatibility checks (UNSAFE)")]
override_validation: bool,
#[arg(long, default_value_t = false)]
#[clap(help = "Bypass the terminal size limit")]
size_bypass: bool,
}
fn main() -> Result<()> { fn main() -> Result<()> {
let args = Args::parse(); let args = Args::parse();
let mut state = AppState::new(args.clone());
let mut state = AppState::new(args);
stdout().execute(EnterAlternateScreen)?; stdout().execute(EnterAlternateScreen)?;
stdout().execute(EnableMouseCapture)?; if args.mouse {
stdout().execute(EnableMouseCapture)?;
}
enable_raw_mode()?; enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
@ -70,7 +48,9 @@ fn main() -> Result<()> {
// restore terminal // restore terminal
disable_raw_mode()?; disable_raw_mode()?;
terminal.backend_mut().execute(LeaveAlternateScreen)?; terminal.backend_mut().execute(LeaveAlternateScreen)?;
terminal.backend_mut().execute(DisableMouseCapture)?; if args.mouse {
terminal.backend_mut().execute(DisableMouseCapture)?;
}
terminal.backend_mut().execute(ResetColor)?; terminal.backend_mut().execute(ResetColor)?;
terminal.show_cursor()?; terminal.show_cursor()?;

View File

@ -8,8 +8,12 @@ This means you have full system access and commands can potentially damage your
Please proceed with caution and make sure you understand what each script does before executing it."; Please proceed with caution and make sure you understand what each script does before executing it.";
#[cfg(unix)] #[cfg(unix)]
pub fn check_root_status<'a>() -> Option<FloatingText<'a>> { pub fn check_root_status(bypass_root: bool) -> Option<FloatingText<'static>> {
(Uid::effective().is_root()).then_some(FloatingText::new( if bypass_root {
return None;
}
Uid::effective().is_root().then_some(FloatingText::new(
ROOT_WARNING.into(), ROOT_WARNING.into(),
"Root User Warning", "Root User Warning",
true, true,

View File

@ -66,6 +66,7 @@ pub struct AppState {
tip: &'static str, tip: &'static str,
size_bypass: bool, size_bypass: bool,
skip_confirmation: bool, skip_confirmation: bool,
mouse_enabled: bool,
} }
pub enum Focus { pub enum Focus {
@ -96,6 +97,11 @@ enum SelectedItem {
impl AppState { impl AppState {
pub fn new(args: Args) -> Self { pub fn new(args: Args) -> Self {
#[cfg(unix)]
let root_warning = check_root_status(args.bypass_root);
#[cfg(not(unix))]
let root_warning = None;
let tabs = linutil_core::get_tabs(!args.override_validation); let tabs = linutil_core::get_tabs(!args.override_validation);
let root_id = tabs[0].tree.root().id(); let root_id = tabs[0].tree.root().id();
@ -122,10 +128,11 @@ impl AppState {
tip: crate::tips::get_random_tip(), tip: crate::tips::get_random_tip(),
size_bypass: args.size_bypass, size_bypass: args.size_bypass,
skip_confirmation: args.skip_confirmation, skip_confirmation: args.skip_confirmation,
mouse_enabled: args.mouse,
}; };
#[cfg(unix)] #[cfg(unix)]
if let Some(root_warning) = check_root_status() { if let Some(root_warning) = root_warning {
state.spawn_float(root_warning, FLOAT_SIZE, FLOAT_SIZE); state.spawn_float(root_warning, FLOAT_SIZE, FLOAT_SIZE);
} }
@ -440,6 +447,10 @@ impl AppState {
} }
pub fn handle_mouse(&mut self, event: &MouseEvent) -> bool { pub fn handle_mouse(&mut self, event: &MouseEvent) -> bool {
if !self.mouse_enabled {
return true;
}
if !self.drawable { if !self.drawable {
return true; return true;
} }
@ -598,23 +609,47 @@ impl AppState {
} }
fn scroll_down(&mut self) { fn scroll_down(&mut self) {
if let Some(selected) = self.selection.selected() { let Some(selected) = self.selection.selected() else {
if selected == self.filter.item_list().len() - 1 { return;
self.selection.select_first(); };
} else { let list_len = if !self.at_root() {
self.selection.select_next(); self.filter.item_list().len() + 1
} } else {
} self.filter.item_list().len()
};
if list_len == 0 {
return;
};
let next_selection = if selected >= list_len - 1 {
0
} else {
selected + 1
};
self.selection.select(Some(next_selection));
} }
fn scroll_up(&mut self) { fn scroll_up(&mut self) {
if let Some(selected) = self.selection.selected() { let Some(selected) = self.selection.selected() else {
if selected == 0 { return;
self.selection.select_last(); };
} else { let list_len = if !self.at_root() {
self.selection.select_previous(); self.filter.item_list().len() + 1
} } else {
} self.filter.item_list().len()
};
if list_len == 0 {
return;
};
let next_selection = if selected == 0 {
list_len - 1
} else {
selected - 1
};
self.selection.select(Some(next_selection));
} }
fn toggle_multi_select(&mut self) { fn toggle_multi_select(&mut self) {

View File

@ -2,8 +2,7 @@ use std::fs;
use linutil_core::Command; use linutil_core::Command;
use crate::path; use crate::{path, DynError};
use crate::DynError;
pub const USER_GUIDE: &str = "userguide.md"; pub const USER_GUIDE: &str = "userguide.md";

View File

@ -6,9 +6,10 @@ use std::{env, error::Error};
type DynError = Box<dyn Error>; type DynError = Box<dyn Error>;
pub mod tasks { pub mod tasks {
use crate::docgen::USER_GUIDE; use crate::{
use crate::docgen::{userguide, write}; docgen::{userguide, write, USER_GUIDE},
use crate::DynError; DynError,
};
pub fn docgen() -> Result<(), DynError> { pub fn docgen() -> Result<(), DynError> {
write(USER_GUIDE, &userguide()?); write(USER_GUIDE, &userguide()?);