Merge remote-tracking branch 'linutil'

This commit is contained in:
zdiff 2025-02-11 18:39:42 -05:00
commit fc3bfc6df3
55 changed files with 466 additions and 282 deletions

View File

@ -1,51 +0,0 @@
Babel==2.15.0
bracex==2.5
cairocffi==1.7.1
CairoSVG==2.7.1
certifi==2024.7.4
cffi==1.17.0
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
csscompressor==0.9.5
cssselect2==0.7.0
defusedxml==0.7.1
ghp-import==2.1.0
gitdb==4.0.11
GitPython==3.1.43
htmlmin2==0.1.13
idna==3.7
Jinja2==3.1.4
jsmin==3.0.1
Markdown==3.6
MarkupSafe==2.1.5
mergedeep==1.3.4
mkdocs==1.6.0
mkdocs-awesome-pages-plugin==2.9.3
mkdocs-get-deps==0.2.0
mkdocs-git-revision-date-localized-plugin==1.2.6
mkdocs-material==9.5.31
mkdocs-material-extensions==1.3.1
mkdocs-minify-plugin==0.8.0
natsort==8.4.0
packaging==24.1
paginate==0.5.6
pathspec==0.12.1
pillow==10.4.0
platformdirs==4.2.2
pycparser==2.22
Pygments==2.18.0
pymdown-extensions==10.9
python-dateutil==2.9.0.post0
pytz==2024.1
PyYAML==6.0.2
pyyaml_env_tag==0.1
regex==2024.7.24
requests==2.32.3
six==1.16.0
smmap==5.0.1
tinycss2==1.3.0
urllib3==2.2.2
watchdog==4.0.1
wcmatch==9.0
webencodings==0.5.1

View File

@ -31,9 +31,3 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Run shfmt
uses: reviewdog/action-shfmt@v1
with:
shfmt_flags: '-i 4 -ci'
reviewdog_flags: '-fail-level=any'

View File

@ -27,25 +27,18 @@ curl -fsSL https://christitus.com/linuxdev | sh
### CLI arguments
Linutil supports various command-line arguments to customize its behavior. Here are some common arguments you can use:
- `-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
```
View available options by running:
```bash
linutil --help
```
For installer options:
```bash
curl -fsSL https://christitus.com/linux | sh -s -- --help
```
## ⬇️ Installation
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
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)

View File

@ -111,18 +111,16 @@ fn default_true() -> bool {
}
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
enum EntryType {
#[serde(rename = "entries")]
Entries(Vec<Entry>),
#[serde(rename = "command")]
Command(String),
#[serde(rename = "script")]
Script(PathBuf),
}
impl Entry {
fn is_supported(&self) -> bool {
self.preconditions.as_deref().map_or(true, |preconditions| {
self.preconditions.as_deref().is_none_or(|preconditions| {
preconditions.iter().all(
|Precondition {
matches,
@ -132,14 +130,16 @@ impl Entry {
match data {
SystemDataType::Environment(var_name) => std::env::var(var_name)
.is_ok_and(|var| values.contains(&var) == *matches),
SystemDataType::File(path) => {
std::fs::read_to_string(path).is_ok_and(|data| {
values.iter().all(|matching| data.contains(matching)) == *matches
})
}
SystemDataType::ContainingFile(file) => std::fs::read_to_string(file)
.is_ok_and(|data| {
values
.iter()
.all(|matching| data.contains(matching) == *matches)
}),
SystemDataType::CommandExists => values
.iter()
.all(|command| which::which(command).is_ok() == *matches),
SystemDataType::FileExists => values.iter().all(|p| Path::new(p).is_file()),
}
},
)
@ -157,12 +157,11 @@ struct Precondition {
}
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
enum SystemDataType {
#[serde(rename = "environment")]
Environment(String),
#[serde(rename = "file")]
File(PathBuf),
#[serde(rename = "command_exists")]
ContainingFile(PathBuf),
FileExists,
CommandExists,
}

View File

@ -19,7 +19,7 @@ install_adb() {
"$ESCALATION_TOOL" "$PACKAGER" add android-tools
;;
*)
printf "%b\n" "${RED}Unsupported package manager: "$PACKAGER"${RC}"
printf "%b\n" "${RED}Unsupported package manager: $PACKAGER${RC}"
exit 1
;;
esac

View File

@ -8,7 +8,7 @@ installLibreWolf() {
case "$PACKAGER" in
apt-get|nala)
"$ESCALATION_TOOL" "$PACKAGER" install -y gnupg lsb-release apt-transport-https ca-certificates
distro=`if echo " una bookworm vanessa focal jammy bullseye vera uma " | grep -q " $(lsb_release -sc) "; then lsb_release -sc; else echo focal; fi`
distro=$(if echo " una bookworm vanessa focal jammy bullseye vera uma " | grep -q "$(lsb_release -sc)"; then "$(lsb_release -sc)"; else echo 'focal'; fi)
curl -fsSL https://deb.librewolf.net/keyring.gpg | "$ESCALATION_TOOL" gpg --dearmor -o /usr/share/keyrings/librewolf.gpg
echo "Types: deb
URIs: https://deb.librewolf.net

View File

@ -0,0 +1,29 @@
#!/bin/sh -e
. ../../common-script.sh
installZenBrowser() {
if ! command_exists io.github.zen_browser.zen && ! command_exists zen-browser; then
printf "%b\n" "${YELLOW}Installing Zen Browser...${RC}"
case "$PACKAGER" in
pacman)
if grep -q avx2 /proc/cpuinfo; then
"$AUR_HELPER" -S --needed --noconfirm zen-browser-avx2-bin
else
"$AUR_HELPER" -S --needed --noconfirm zen-browser-bin
fi
;;
*)
checkFlatpak
flatpak install -y flathub io.github.zen_browser.zen
;;
esac
else
printf "%b\n" "${GREEN}Zen Browser is already installed.${RC}"
fi
}
checkEnv
checkEscalationTool
checkAURHelper
installZenBrowser

View File

@ -17,7 +17,7 @@ installDiscord() {
"$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm discord
;;
dnf)
"$ESCALATION_TOOL" "$PACKAGER" install -y https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
"$ESCALATION_TOOL" "$PACKAGER" install -y "https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm"
"$ESCALATION_TOOL" "$PACKAGER" install -y discord
;;
apk)

View File

@ -8,7 +8,7 @@ installSignal() {
case "$PACKAGER" in
apt-get|nala)
curl -fsSL https://updates.signal.org/desktop/apt/keys.asc | gpg --dearmor > signal-desktop-keyring.gpg
cat signal-desktop-keyring.gpg | "$ESCALATION_TOOL" tee /usr/share/keyrings/signal-desktop-keyring.gpg > /dev/null
"$ESCALATION_TOOL" tee /usr/share/keyrings/signal-desktop-keyring.gpg < signal-desktop-keyring.gpg > /dev/null
printf "%b\n" 'deb [arch=amd64 signed-by=/usr/share/keyrings/signal-desktop-keyring.gpg] https://updates.signal.org/desktop/apt xenial main' | "$ESCALATION_TOOL" tee /etc/apt/sources.list.d/signal-xenial.list
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" -y install signal-desktop

View File

@ -0,0 +1,52 @@
#!/bin/sh -e
. ../../common-script.sh
installZed() {
if ! command_exists dev.zed.Zed && ! command_exists zed && ! command_exists zeditor; then
printf "%b\n" "${CYAN}Installing Zed.${RC}"
case "$PACKAGER" in
apk)
"$ESCALATION_TOOL" "$PACKAGER" add zed
;;
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -S zed
;;
zypper)
"$ESCALATION_TOOL" "$PACKAGER" addrepo -f https://download.opensuse.org/repositories/editors/openSUSE_Tumbleweed/editors.repo
"$ESCALATION_TOOL" "$PACKAGER" install -y zed
;;
*)
printf "%b\n" "${YELLOW}No official package found for package manager $PACKAGER. Do you want to install flathub package or from source?${RC}"
printf "%b\n" "1) Flathub package"
printf "%b\n" "2) Source"
printf "%b\n" "3) Exit"
printf "%b" "Choose an option: "
read -r choice
case "$choice" in
1)
checkFlatpak
flatpak install -y flathub dev.zed.Zed
;;
2)
curl -f https://zed.dev/install.sh | sh
;;
3)
printf "%b\n" "${GREEN}Exiting.${RC}"
exit 0
;;
*)
printf "%b\n" "${RED}Invalid option.${RC}"
exit 1
;;
esac
;;
esac
else
printf "%b\n" "${GREEN}Zed is already installed.${RC}"
fi
}
checkEnv
clear
installZed

View File

@ -192,7 +192,7 @@ setupDisplayManager() {
"$ESCALATION_TOOL" "$PACKAGER" install -y xorg-x11-xinit xorg-x11-server-Xorg
;;
*)
printf "%b\n" "${RED}Unsupported package manager: "$PACKAGER"${RC}"
printf "%b\n" "${RED}Unsupported package manager: $PACKAGER${RC}"
exit 1
;;
esac
@ -244,7 +244,7 @@ setupDisplayManager() {
"$ESCALATION_TOOL" "$PACKAGER" install -y "$DM"
;;
*)
printf "%b\n" "${RED}Unsupported package manager: "$PACKAGER"${RC}"
printf "%b\n" "${RED}Unsupported package manager: $PACKAGER${RC}"
exit 1
;;
esac

View File

@ -53,7 +53,7 @@ installLinutil() {
;;
*)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. $HOME/.cargo/env
. "$HOME/.cargo/env"
;;
esac
fi

View File

@ -17,7 +17,7 @@ updateLinutil() {
zypper)
"$ESCALATION_TOOL" "$PACKAGER" install -n curl gcc make
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. $HOME/.cargo/env
. "$HOME/.cargo/env"
;;
apk)
"$ESCALATION_TOOL" "$PACKAGER" add build-base

View File

@ -57,49 +57,55 @@ name = "Developer Tools"
[[data.entries]]
name = "Github Desktop"
description = "GitHub Desktop is a user-friendly application that simplifies the process of managing Git repositories and interacting with GitHub, providing a graphical interface for tasks like committing, branching, and syncing changes."
script = "Developer-tools/githubdesktop-setup.sh"
script = "developer-tools/githubdesktop.sh"
task_list = "I"
[[data.entries]]
name = "JetBrains Toolbox"
description = "JetBrains Toolbox is a collection of tools and an app that help developers work with JetBrains products."
script = "Developer-tools/jetbrains-toolbox.sh"
script = "developer-tools/jetbrains-toolbox.sh"
task_list = "I"
[[data.entries]]
name = "Meld"
description = "Meld is a visual diff and merge tool that helps compare files, directories, and version-controlled projects."
script = "Developer-tools/meld-setup.sh"
script = "developer-tools/meld.sh"
task_list = "I FI"
[[data.entries]]
name = "Neovim"
description = "Neovim is a refactor, and sometimes redactor, in the tradition of Vim.\nIt is not a rewrite but a continuation and extension of Vim.\nThis command configures neovim from CTT's neovim configuration.\nhttps://github.com/ChrisTitusTech/neovim"
script = "Developer-tools/neovim-setup.sh"
script = "developer-tools/neovim.sh"
task_list = "I FM"
[[data.entries]]
name = "Ngrok"
description = "Ngrok is a tool that creates secure, temporary tunnels to expose local servers to the internet for testing and development."
script = "Developer-tools/ngrok-setup.sh"
script = "developer-tools/ngrok.sh"
task_list = "I"
[[data.entries]]
name = "Sublime Text"
description = "Sublime Text is a fast, lightweight, and customizable text editor known for its simplicity, powerful features, and wide range of plugins for various programming languages."
script = "Developer-tools/sublime-setup.sh"
script = "developer-tools/sublime.sh"
task_list = "I"
[[data.entries]]
name = "VS Code"
description = "Visual Studio Code (VS Code) is a lightweight, open-source code editor with built-in support for debugging, version control, and extensions for various programming languages and frameworks."
script = "Developer-tools/vscode-setup.sh"
script = "developer-tools/vscode.sh"
task_list = "I"
[[data.entries]]
name = "VS Codium"
description = "VSCodium is a free, open-source version of Visual Studio Code (VS Code) that removes Microsoft-specific telemetry and branding."
script = "Developer-tools/vscodium-setup.sh"
script = "developer-tools/vscodium.sh"
task_list = "I"
[[data.entries]]
name = "Zed"
description = "Zed is a next-generation code editor written in rust, designed for high-performance collaboration with humans and AI."
script = "developer-tools/zed.sh"
task_list = "I"
[[data]]
@ -186,6 +192,12 @@ description = "Mozilla Firefox is a free and open-source web browser developed b
script = "browsers/firefox.sh"
task_list = "I"
[[data.entries]]
name = "Zen Browser"
description = "Zen Browser is a privacy-focused web browser designed for enhanced security and a seamless browsing experience."
script = "browsers/zen-browser.sh"
task_list = "I"
[[data.entries]]
name = "Thorium"
description = "Thorium is a Chromium-based browser focused on privacy and performance."

View File

@ -0,0 +1,15 @@
#!/bin/sh
. ../../common-script.sh
printf "%b\n" "${YELLOW}Starting Hyprland JaKooLit installation${RC}"
if ! pacman -Q base-devel >/dev/null 2>&1; then
printf "%b\n" "${YELLOW}Installing base-devel...${RC}"
"$ESCALATION_TOOL" pacman -S --noconfirm base-devel
fi
git clone --depth=1 https://github.com/JaKooLit/Arch-Hyprland.git "$HOME/Arch-Hyprland" || { printf "%b\n" "${RED}Failed to clone Jakoolits Arch-Hyprland repo${RC}"; exit 1; }
cd "$HOME/Arch-Hyprland" || { printf "%b\n" "${RED}Failed to navigate to Arch-Hyprland directory${RC}"; exit 1; }
chmod +x install.sh
./install.sh

View File

@ -15,33 +15,33 @@ installDepend() {
else
printf "%b\n" "${GREEN}Multilib is already enabled.${RC}"
fi
"$AUR_HELPER" -S --needed --noconfirm $DEPENDENCIES
"$AUR_HELPER" -S --needed --noconfirm "$DEPENDENCIES"
;;
apt-get|nala)
COMPILEDEPS='build-essential'
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" dpkg --add-architecture i386
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" install -y $DEPENDENCIES $COMPILEDEPS
"$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES" "$COMPILEDEPS"
;;
dnf)
COMPILEDEPS='@development-tools'
"$ESCALATION_TOOL" "$PACKAGER" update
"$ESCALATION_TOOL" "$PACKAGER" config-manager --set-enabled powertools
"$ESCALATION_TOOL" "$PACKAGER" install -y $DEPENDENCIES $COMPILEDEPS
"$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES" "$COMPILEDEPS"
"$ESCALATION_TOOL" "$PACKAGER" install -y glibc-devel.i686 libgcc.i686
;;
zypper)
COMPILEDEPS='patterns-devel-base-devel_basis'
"$ESCALATION_TOOL" "$PACKAGER" refresh
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install $DEPENDENCIES $COMPILEDEPS
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install "$DEPENDENCIES" "$COMPILEDEPS"
"$ESCALATION_TOOL" "$PACKAGER" --non-interactive install libgcc_s1-gcc7-32bit glibc-devel-32bit
;;
apk)
"$ESCALATION_TOOL" "$PACKAGER" add build-base multitail tar tree trash-cli unzip cmake jq
;;
*)
"$ESCALATION_TOOL" "$PACKAGER" install -y $DEPENDENCIES
"$ESCALATION_TOOL" "$PACKAGER" install -y "$DEPENDENCIES"
;;
esac
}

View File

@ -0,0 +1,9 @@
#!/bin/sh
. ../../common-script.sh
printf "%b\n" "${YELLOW}Starting Hyprland JaKooLit installation${RC}"
git clone --depth=1 https://github.com/JaKooLit/Debian-Hyprland.git "$HOME/Debian-Hyprland" || { printf "%b\n" "${RED}Failed to clone Jakoolits Debian-Hyprland repo${RC}"; exit 1; }
cd "$HOME/Debian-Hyprland" || { printf "%b\n" "${RED}Failed to navigate to Debian-Hyprland directory${RC}"; exit 1; }
chmod +x install.sh
./install.sh

View File

@ -0,0 +1,10 @@
#!/bin/sh
. ../../common-script.sh
printf "%b\n" "${YELLOW}Starting Hyprland JaKooLit installation${RC}"
git clone --depth=1 https://github.com/JaKooLit/Fedora-Hyprland.git "$HOME/Fedora-Hyprland" || { printf "%b\n" "${RED}Failed to clone Jakoolits Fedora-Hyprland repo${RC}"; exit 1; }
cd "$HOME/Fedora-Hyprland" || { printf "%b\n" "${RED}Failed to navigate to Fedora-Hyprland directory${RC}"; exit 1; }
chmod +x install.sh
./install.sh

View File

@ -22,7 +22,7 @@ cleanup_system() {
;;
pacman)
"$ESCALATION_TOOL" "$PACKAGER" -Sc --noconfirm
"$ESCALATION_TOOL" "$PACKAGER" -Rns $(pacman -Qtdq) --noconfirm > /dev/null || true
"$ESCALATION_TOOL" "$PACKAGER" -Rns "$(pacman -Qtdq)" --noconfirm > /dev/null || true
;;
apk)
"$ESCALATION_TOOL" "$PACKAGER" cache clean

View File

@ -16,15 +16,22 @@ task_list = "SI D"
multi_select = false
[[data.entries]]
name ="Linux Neptune for SteamDeck"
name = "Hyprland JaKooLit"
description = "Install JaKooLit's Hyprland configuration"
script = "arch/hyprland-kool.sh"
task_list = "I MP"
multi_select = false
[[data.entries]]
name = "Linux Neptune for SteamDeck"
description = "Valve's fork of Linux Kernel for the SteamDeck"
script = "arch/linux-neptune.sh"
task_list = "I PFM K"
[[data.entries.preconditions]]
matches = true
data = { file = "/sys/devices/virtual/dmi/id/board_vendor" }
values = [ "Valve" ]
data = { containing_file = "/sys/devices/virtual/dmi/id/board_vendor" }
values = ["Valve"]
[[data.entries]]
name = "Nvidia Drivers && Hardware Acceleration"
@ -50,6 +57,20 @@ description = "Yet Another Yogurt - An AUR Helper Written in Go. To know more ab
script = "arch/yay-setup.sh"
task_list = "I"
[[data]]
name = "Debian"
[[data.preconditions]]
matches = true
data = { containing_file = "/etc/os-release" }
values = ["ID=debian"]
[[data.entries]]
name = "Hyprland JaKooLit"
description = "Install JaKooLit's Hyprland configuration"
script = "debian/hyprland-kool-deb.sh"
task_list = "I MP"
[[data]]
name = "Fedora"
@ -64,6 +85,12 @@ description = "Optimizes DNF for parallel downloads"
script = "fedora/configure-dnf.sh"
task_list = "PFM"
[[data.entries]]
name = "Hyprland JaKooLit"
description = "Install JaKooLit's Hyprland configuration"
script = "fedora/hyprland-kool-fed.sh"
task_list = "I MP"
[[data.entries]]
name = "Multimedia Codecs"
description = "This script is designed to install multimedia codecs, and to ensure RPM Fusion repositories are installed."
@ -103,7 +130,26 @@ task_list = "I PFM SS"
[[data.preconditions]]
matches = true
data = "command_exists"
values = [ "btrfs" ]
values = ["btrfs"]
[[data]]
name = "Ubuntu"
[[data.preconditions]]
matches = true
data = { containing_file = "/etc/os-release" }
values = ["ID=ubuntu"]
[[data.entries]]
name = "Hyprland JaKooLit"
description = "Install JaKooLit's Hyprland configuration"
script = "ubuntu/hyprland-kool-ubuntu24.sh"
task_list = "I MP"
[[data.preconditions]]
matches = true
data = { containing_file = "/etc/os-release" }
values = ['VERSION_ID="24.04"']
[[data]]
name = "Build Prerequisites"

View File

@ -0,0 +1,10 @@
#!/bin/sh
. ../../common-script.sh
printf "%b\n" "${YELLOW}Starting Hyprland JaKooLit installation${RC}"
git clone -b 24.04 --depth=1 https://github.com/JaKooLit/Ubuntu-Hyprland.git "$HOME/Ubuntu-Hyprland-24.04" || { printf "%b\n" "${RED}Failed to clone Jakoolits Ubuntu-Hyprland repo${RC}"; exit 1; }
cd "$HOME/Ubuntu-Hyprland-24.04" || { printf "%b\n" "${RED}Failed to navigate to Ubuntu-Hyprland-24.04 directory${RC}"; exit 1; }
chmod +x install.sh
./install.sh

View File

@ -65,12 +65,11 @@ scan_devices() {
printf "%b\n" "$devices"
fi
printf "%b" "Press any key to return to the main menu..."
read -r dummy
read -r _
}
# Function to prompt for MAC address using numbers
prompt_for_mac() {
action=$1
command=$2
prompt_msg=$3
success_msg=$4
@ -82,14 +81,14 @@ prompt_for_mac() {
if [ -z "$devices" ]; then
printf "%b\n" "${RED}No devices available. Please scan for devices first.${RC}"
printf "%b" "Press any key to return to the main menu..."
read -r dummy
read -r _
return
fi
# Display devices with numbers
device_list=$(echo "$devices" | tr '\n' '\n')
i=1
echo "$device_list" | while IFS= read -r device; do
for device in $device_list; do
printf "%d. %s\n" "$i" "$device"
i=$((i + 1))
done
@ -102,12 +101,12 @@ prompt_for_mac() {
device=$(echo "$device_list" | sed -n "${choice}p")
mac=$(echo "$device" | awk '{print $2}')
if bluetoothctl info "$mac" > /dev/null 2>&1; then
bluetoothctl "$command" "$mac" && {
if bluetoothctl "$command" "$mac"; then
printf "%b\n" "${GREEN}$success_msg${RC}"
break
} || {
else
printf "%b\n" "${RED}$failure_msg${RC}"
}
fi
else
printf "%b\n" "${RED}Invalid MAC address. Please try again.${RC}"
fi
@ -118,7 +117,7 @@ prompt_for_mac() {
fi
done
printf "%b" "Press any key to return to the main menu..."
read -r dummy
read -r _
}
# Function to pair with a device

View File

@ -62,11 +62,11 @@ encrypt_file() {
if [ -d "$INPUT_PATH" ]; then
# Encrypt each file in the directory
find "$INPUT_PATH" -type f | while read -r FILE; do
REL_PATH="${FILE#$INPUT_PATH/}"
REL_PATH="${FILE#"$INPUT_PATH"/}"
OUTPUT_FILE="$OUTPUT_PATH/$REL_PATH.enc"
mkdir -p "$(dirname "$OUTPUT_FILE")"
openssl enc -aes-256-cbc -salt -pbkdf2 -in "$FILE" -out "$OUTPUT_FILE" -k "$PASSWORD"
if [ $? -eq 0 ]; then
if [ "$(openssl enc -aes-256-cbc -salt -pbkdf2 -in "$FILE" -out "$OUTPUT_FILE" -k "$PASSWORD")" -eq 0 ]; then
printf "%b\n" "Encrypted: $OUTPUT_FILE"
else
printf "%b\n" "Failed to encrypt: $FILE"
@ -79,8 +79,8 @@ encrypt_file() {
return
fi
mkdir -p "$(dirname "$OUTPUT_PATH")"
openssl enc -aes-256-cbc -salt -pbkdf2 -in "$INPUT_PATH" -out "$OUTPUT_PATH" -k "$PASSWORD"
if [ $? -eq 0 ]; then
if [ "$(openssl enc -aes-256-cbc -salt -pbkdf2 -in "$INPUT_PATH" -out "$OUTPUT_PATH" -k "$PASSWORD")" -eq 0 ]; then
printf "%b\n" "Encrypted: $OUTPUT_PATH"
else
printf "%b\n" "Failed to encrypt: $INPUT_PATH"
@ -107,11 +107,11 @@ decrypt_file() {
if [ -d "$INPUT_PATH" ]; then
# Decrypt each file in the directory
find "$INPUT_PATH" -type f -name '*.enc' | while read -r FILE; do
REL_PATH="${FILE#$INPUT_PATH/}"
REL_PATH="${FILE#"$INPUT_PATH"/}"
OUTPUT_FILE="$OUTPUT_PATH/${REL_PATH%.enc}"
mkdir -p "$(dirname "$OUTPUT_FILE")"
openssl enc -aes-256-cbc -d -pbkdf2 -in "$FILE" -out "$OUTPUT_FILE" -k "$PASSWORD"
if [ $? -eq 0 ]; then
if [ "$(openssl enc -aes-256-cbc -d -pbkdf2 -in "$FILE" -out "$OUTPUT_FILE" -k "$PASSWORD")" -eq 0 ]; then
printf "%b\n" "Decrypted: $OUTPUT_FILE"
else
printf "%b\n" "Failed to decrypt: $FILE"
@ -124,8 +124,8 @@ decrypt_file() {
return
fi
mkdir -p "$(dirname "$OUTPUT_PATH")"
openssl enc -aes-256-cbc -d -pbkdf2 -in "$INPUT_PATH" -out "$OUTPUT_PATH" -k "$PASSWORD"
if [ $? -eq 0 ]; then
if [ "$(openssl enc -aes-256-cbc -d -pbkdf2 -in "$INPUT_PATH" -out "$OUTPUT_PATH" -k "$PASSWORD")" -eq 0 ]; then
printf "%b\n" "Decrypted: $OUTPUT_PATH"
else
printf "%b\n" "Failed to decrypt: $INPUT_PATH"
@ -148,7 +148,7 @@ main(){
esac
printf "%b\n" "Press [Enter] to continue..."
read -r dummy
read -r _
done
}

View File

@ -29,7 +29,7 @@ adjust_monitor_brightness() {
if ! echo "$monitor_choice" | grep -qE '^[0-9]+$'; then
printf "%b\n" "${RED}Invalid selection. Please try again.${RC}"
printf "Press [Enter] to continue..."
read -r dummy
read -r _
continue
fi
@ -37,7 +37,7 @@ adjust_monitor_brightness() {
if [ "$monitor_choice" -lt 1 ] || [ "$monitor_choice" -gt "$monitor_count" ]; then
printf "%b\n" "${RED}Invalid selection. Please try again.${RC}"
printf "Press [Enter] to continue..."
read -r dummy
read -r _
continue
fi

View File

@ -33,7 +33,7 @@ set_resolutions() {
if ! echo "$monitor_choice" | grep -qE '^[0-9]+$' || [ "$monitor_choice" -lt 1 ] || [ "$monitor_choice" -gt "$((i - 1))" ]; then
printf "%b\n" "${RED}Invalid selection. Please try again.${RC}"
printf "%b\n" "Press [Enter] to continue..."
read -r dummy
read -r _
continue
fi

View File

@ -86,6 +86,7 @@ run_model() {
printf "%b\n" "${YELLOW}Custom Models${RC}"
custom_models=$(ollama list | grep 'custom-model-prefix')
printf "%b\n" "${custom_models}"
printf "%b" "Select a model to run: "
printf "%b" "Enter the number corresponding to the model or enter the name of a custom model: "
@ -195,7 +196,7 @@ menu() {
esac
printf "%b\n" "${YELLOW}Press Enter to continue...${RC}"
read -r dummy
read -r _
done
}

View File

@ -173,7 +173,7 @@ setup_ssh_samba(){
printf "%b\n" "5. Exit"
printf "%b" "Enter your choice (1-5): "
read CHOICE
read -r CHOICE
case "$CHOICE" in
1)

View File

@ -267,7 +267,7 @@ main() {
esac
printf "%b\n" "Press [Enter] to continue..."
read -r dummy
read -r _
done
}

View File

@ -67,6 +67,7 @@ create_snapshot() {
"$ESCALATION_TOOL" timeshift --create --comments "$COMMENT" --tags "$TAG"
fi
# shellcheck disable=SC2181
if [ $? -eq 0 ]; then
printf "%b\n" "${GREEN}Snapshot created successfully.${RC}"
else
@ -93,6 +94,7 @@ restore_snapshot() {
"$ESCALATION_TOOL" timeshift --restore --snapshot "$SNAPSHOT" --target-device "$TARGET_DEVICE" --grub-device "$GRUB_DEVICE" --yes
fi
# shellcheck disable=SC2181
if [ $? -eq 0 ]; then
printf "%b\n" "${GREEN}Snapshot restored successfully.${RC}"
else
@ -110,6 +112,7 @@ delete_snapshot() {
printf "%b\n" "${YELLOW}Deleting snapshot $SNAPSHOT...${RC}"
"$ESCALATION_TOOL" timeshift --delete --snapshot "$SNAPSHOT" --yes
# shellcheck disable=SC2181
if [ $? -eq 0 ]; then
printf "%b\n" "${GREEN}Snapshot deleted successfully.${RC}"
else
@ -126,6 +129,7 @@ delete_all_snapshots() {
if [ "$CONFIRMATION" = "y" ] || [ "$CONFIRMATION" = "Y" ]; then
printf "%b\n" "${CYAN}Deleting all snapshots...${RC}"
"$ESCALATION_TOOL" timeshift --delete-all --yes
# shellcheck disable=SC2181
if [ $? -eq 0 ]; then
printf "%b\n" "${GREEN}All snapshots deleted successfully.${RC}"
else
@ -153,7 +157,7 @@ main_menu() {
*) printf "%b\n" "${RED}Invalid option. Please try again.${RC}" ;;
esac
printf "%b\n" "${CYAN}Press Enter to continue...${RC}"
read -r dummy
read -r _
done
}

View File

@ -33,7 +33,7 @@ addToGroup() {
groups_to_add=$(echo "$groups" | tr ' ' ',')
printf "%b" "${YELLOW}Are you sure you want to add user $username to $groups_to_add? [Y/n]: ${RC}"
read -r confirm
read -r _
confirmAction || exit 1
"$ESCALATION_TOOL" usermod -aG "$groups_to_add" "$username"

View File

@ -17,7 +17,7 @@ changePassword() {
read -r password
printf "%b" "${YELLOW}Are you sure you want to change password for ""$username""? [Y/n]: ${RC}"
read -r confirm
read -r _
confirmAction || exit 1
echo "$username:$password" | "$ESCALATION_TOOL" chpasswd

View File

@ -14,7 +14,7 @@ deleteUser() {
if id "$username" > /dev/null 2>&1; then
printf "%b" "${YELLOW}Are you sure you want to delete user ""$username""? [Y/n]: ${RC}"
read -r confirm
read -r _
confirmAction || exit 1
$ESCALATION_TOOL userdel --remove "$username" 2>/dev/null

View File

@ -34,10 +34,10 @@ removeFromGroup() {
groups_to_remove=$(echo "$groups" | tr ' ' ',')
printf "%b" "${YELLOW}Are you sure you want to remove user $username from $groups_to_remove? [Y/n]: ${RC}"
read -r confirm
read -r _
confirmAction || exit 1
$ESCALATION_TOOL usermod -rG $groups_to_remove "$username"
$ESCALATION_TOOL usermod -rG "$groups_to_remove" "$username"
printf "%b\n" "${GREEN}User successfully removed from $groups_to_remove${RC}"
}

View File

@ -30,6 +30,7 @@ execute_command() {
command="$1"
printf "Executing: %s\n" "$command"
eval "$command" 2>&1 | tee /tmp/xrandr.log | tail -n 20
# shellcheck disable=SC2181
if [ $? -ne 0 ]; then
printf "%b\n" "${RED}An error occurred while executing the command. Check /tmp/xrandr.log for details.${RC}"
fi

View File

@ -75,33 +75,33 @@ scan_networks() {
echo "$networks" | awk -F: '{printf("%d. SSID: %-25s \n", NR, $1)}'
fi
printf "%b\n" "Press any key to return to the main menu..."
read -r dummy
read -r _
}
# Function to turn WiFi on
wifi_on() {
clear
printf "%b\n" "${YELLOW}Turning WiFi on...${RC}"
nmcli radio wifi on && {
if "$(nmcli radio wifi on)"; then
printf "%b\n" "${GREEN}WiFi is now turned on.${RC}"
} || {
else
printf "%b\n" "${RED}Failed to turn on WiFi.${RC}"
}
fi
printf "%b\n" "Press any key to return to the main menu..."
read -r dummy
read -r _
}
# Function to turn WiFi off
wifi_off() {
clear
printf "%b\n" "${YELLOW}Turning WiFi off...${RC}"
nmcli radio wifi off && {
if "$(nmcli radio wifi off)"; then
printf "%b\n" "${GREEN}WiFi is now turned off.${RC}"
} || {
else
printf "%b\n" "${RED}Failed to turn off WiFi.${RC}"
}
fi
printf "%b\n" "Press any key to return to the main menu..."
read -r dummy
read -r _
}
# Function to prompt for WiFi network selection
@ -118,7 +118,7 @@ prompt_for_network() {
if [ -z "$networks" ]; then
printf "%b\n" "${RED}No networks available. Please scan for networks first.${RC}"
printf "%b\n" "Press any key to return to the main menu..."
read -r dummy
read -r _
rm -f "$temp_file"
return
fi
@ -142,18 +142,18 @@ prompt_for_network() {
printf "%b" "Enter password for SSID: " "$ssid"
read -r password
printf "\n"
nmcli dev wifi connect "$ssid" password "$password" && {
if "$(nmcli dev wifi connect "$ssid" password "$password")"; then
printf "%b\n" "${GREEN}$success_msg${RC}"
} || {
else
printf "%b\n" "${RED}$failure_msg${RC}"
}
fi
fi
else
printf "%b\n" "${RED}Invalid choice. Please try again.${RC}"
fi
printf "%b\n" "Press any key to return to the selection menu..."
read -r dummy
read -r _
done
rm -f "$temp_file"

1
rustfmt.toml Normal file
View File

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

View File

@ -8,8 +8,9 @@ RED='\033[0;31m'
# Function to fetch the latest release tag from the GitHub API
get_latest_release() {
latest_release=$(curl -s https://api.github.com/repos/ChrisTitusTech/linutil/releases |
grep -oP '"tag_name": "\K[^"]*' |
head -n 1)
grep "tag_name" |
head -n 1 |
sed -E 's/.*"tag_name": "([^"]+)".*/\1/')
if [ -z "$latest_release" ]; then
printf "%b\n" "Error fetching release data" >&2
return 1

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,4 +1,4 @@
use crate::{float::FloatContent, hint::Shortcut, theme};
use crate::{float::FloatContent, hint::Shortcut, shortcuts, theme};
use ratatui::{
crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind},
layout::Alignment,
@ -135,13 +135,13 @@ impl FloatContent for ConfirmPrompt {
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
(
"Confirmation prompt",
Box::new([
Shortcut::new("Continue", ["Y", "y"]),
Shortcut::new("Abort", ["N", "n", "q", "Esc"]),
Shortcut::new("Scroll up", ["k", "Up"]),
Shortcut::new("Scroll down", ["j", "Down"]),
Shortcut::new("Close linutil", ["CTRL-c"]),
]),
shortcuts!(
("Continue", ["Y", "y"]),
("Abort", ["N", "n", "q", "Esc"]),
("Scroll up", ["k", "Up"]),
("Scroll down", ["j", "Down"]),
("Close linutil", ["CTRL-c"]),
),
)
}
}

View File

@ -1,4 +1,4 @@
use crate::{float::FloatContent, hint::Shortcut, theme::Theme};
use crate::{float::FloatContent, hint::Shortcut, shortcuts, theme::Theme};
use linutil_core::Command;
use ratatui::{
crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind},
@ -228,13 +228,13 @@ impl FloatContent for FloatingText<'_> {
fn get_shortcut_list(&self) -> (&str, Box<[Shortcut]>) {
(
&self.mode_title,
Box::new([
Shortcut::new("Scroll down", ["j", "Down"]),
Shortcut::new("Scroll up", ["k", "Up"]),
Shortcut::new("Scroll left", ["h", "Left"]),
Shortcut::new("Scroll right", ["l", "Right"]),
Shortcut::new("Close window", ["Enter", "p", "q", "d", "g"]),
]),
shortcuts!(
("Scroll down", ["j", "Down"]),
("Scroll up", ["k", "Up"]),
("Scroll left", ["h", "Left"]),
("Scroll right", ["l", "Right"]),
("Close window", ["Enter", "p", "q", "d", "g"])
),
)
}
}

View File

@ -78,3 +78,14 @@ impl Shortcut {
.collect()
}
}
#[macro_export]
macro_rules! shortcuts {
($(($name:literal,[$($key:literal),+ $(,)?])),* $(,)?) => {
vec![
$(
Shortcut::new($name, [$($key),*])
),*
].into_boxed_slice()
};
}

View File

@ -1,3 +1,4 @@
mod cli;
mod confirmation;
mod filter;
mod float;
@ -11,7 +12,7 @@ mod theme;
#[cfg(feature = "tips")]
mod tips;
use crate::theme::Theme;
use crate::cli::Args;
use clap::Parser;
use ratatui::{
backend::CrosstermBackend,
@ -26,40 +27,17 @@ use ratatui::{
use state::AppState;
use std::{
io::{stdout, Result, Stdout},
path::PathBuf,
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<()> {
let args = Args::parse();
let mut state = AppState::new(args);
let mut state = AppState::new(args.clone());
stdout().execute(EnterAlternateScreen)?;
if args.mouse {
stdout().execute(EnableMouseCapture)?;
}
enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
@ -70,7 +48,9 @@ fn main() -> Result<()> {
// restore terminal
disable_raw_mode()?;
terminal.backend_mut().execute(LeaveAlternateScreen)?;
if args.mouse {
terminal.backend_mut().execute(DisableMouseCapture)?;
}
terminal.backend_mut().execute(ResetColor)?;
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.";
#[cfg(unix)]
pub fn check_root_status<'a>() -> Option<FloatingText<'a>> {
(Uid::effective().is_root()).then_some(FloatingText::new(
pub fn check_root_status(bypass_root: bool) -> Option<FloatingText<'static>> {
if bypass_root {
return None;
}
Uid::effective().is_root().then_some(FloatingText::new(
ROOT_WARNING.into(),
"Root User Warning",
true,

View File

@ -1,4 +1,4 @@
use crate::{float::FloatContent, hint::Shortcut, theme::Theme};
use crate::{float::FloatContent, hint::Shortcut, shortcuts, theme::Theme};
use linutil_core::Command;
use oneshot::{channel, Receiver};
use portable_pty::{
@ -139,21 +139,21 @@ impl FloatContent for RunningCommand {
if self.is_finished() {
(
"Finished command",
Box::new([
Shortcut::new("Close window", ["Enter", "q"]),
Shortcut::new("Scroll up", ["Page up"]),
Shortcut::new("Scroll down", ["Page down"]),
Shortcut::new("Save log", ["l"]),
]),
shortcuts!(
("Close window", ["Enter", "q"]),
("Scroll up", ["Page up"]),
("Scroll down", ["Page down"]),
("Save log", ["l"]),
),
)
} else {
(
"Running command",
Box::new([
Shortcut::new("Kill the command", ["CTRL-c"]),
Shortcut::new("Scroll up", ["Page up"]),
Shortcut::new("Scroll down", ["Page down"]),
]),
shortcuts!(
("Kill the command", ["CTRL-c"]),
("Scroll up", ["Page up"]),
("Scroll down", ["Page down"]),
),
)
}
}

View File

@ -6,6 +6,7 @@ use crate::{
hint::{create_shortcut_list, Shortcut},
root::check_root_status,
running_command::RunningCommand,
shortcuts,
theme::Theme,
Args,
};
@ -65,6 +66,7 @@ pub struct AppState {
tip: &'static str,
size_bypass: bool,
skip_confirmation: bool,
mouse_enabled: bool,
}
pub enum Focus {
@ -93,8 +95,18 @@ enum SelectedItem {
None,
}
enum ScrollDir {
Up,
Down,
}
impl AppState {
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 root_id = tabs[0].tree.root().id();
@ -121,10 +133,11 @@ impl AppState {
tip: crate::tips::get_random_tip(),
size_bypass: args.size_bypass,
skip_confirmation: args.skip_confirmation,
mouse_enabled: args.mouse,
};
#[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);
}
@ -171,13 +184,13 @@ impl AppState {
fn get_list_item_shortcut(&self) -> Box<[Shortcut]> {
if self.selected_item_is_dir() {
Box::new([Shortcut::new("Go to selected dir", ["l", "Right", "Enter"])])
shortcuts!(("Go to selected dir", ["l", "Right", "Enter"]))
} else {
Box::new([
Shortcut::new("Run selected command", ["l", "Right", "Enter"]),
Shortcut::new("Enable preview", ["p"]),
Shortcut::new("Command Description", ["d"]),
])
shortcuts!(
("Run selected command", ["l", "Right", "Enter"]),
("Enable preview", ["p"]),
("Command Description", ["d"])
)
}
}
@ -185,10 +198,7 @@ impl AppState {
match self.focus {
Focus::Search => (
"Search bar",
Box::new([
Shortcut::new("Abort search", ["Esc", "CTRL-c"]),
Shortcut::new("Search", ["Enter"]),
]),
shortcuts!(("Abort search", ["Esc", "CTRL-c"]), ("Search", ["Enter"])),
),
Focus::List => {
@ -208,35 +218,39 @@ impl AppState {
hints.extend(self.get_list_item_shortcut());
}
hints.push(Shortcut::new("Select item above", ["k", "Up"]));
hints.push(Shortcut::new("Select item below", ["j", "Down"]));
hints.push(Shortcut::new("Next theme", ["t"]));
hints.push(Shortcut::new("Previous theme", ["T"]));
hints.push(Shortcut::new("Multi-selection mode", ["v"]));
hints.extend(shortcuts!(
("Select item above", ["k", "Up"]),
("Select item below", ["j", "Down"]),
("Next theme", ["t"]),
("Previous theme", ["T"]),
("Multi-selection mode", ["v"]),
));
if self.multi_select {
hints.push(Shortcut::new("Select multiple commands", ["Space"]));
}
hints.push(Shortcut::new("Next tab", ["Tab"]));
hints.push(Shortcut::new("Previous tab", ["Shift-Tab"]));
hints.push(Shortcut::new("Important actions guide", ["g"]));
hints.extend(shortcuts!(
("Next tab", ["Tab"]),
("Previous tab", ["Shift-Tab"]),
("Important actions guide", ["g"])
));
("Command list", hints.into_boxed_slice())
}
Focus::TabList => (
"Tab list",
Box::new([
Shortcut::new("Exit linutil", ["q", "CTRL-c"]),
Shortcut::new("Focus action list", ["l", "Right", "Enter"]),
Shortcut::new("Select item above", ["k", "Up"]),
Shortcut::new("Select item below", ["j", "Down"]),
Shortcut::new("Next theme", ["t"]),
Shortcut::new("Previous theme", ["T"]),
Shortcut::new("Next tab", ["Tab"]),
Shortcut::new("Previous tab", ["Shift-Tab"]),
Shortcut::new("Important actions guide", ["g"]),
Shortcut::new("Multi-selection mode", ["v"]),
]),
shortcuts!(
("Exit linutil", ["q", "CTRL-c"]),
("Focus action list", ["l", "Right", "Enter"]),
("Select item above", ["k", "Up"]),
("Select item below", ["j", "Down"]),
("Next theme", ["t"]),
("Previous theme", ["T"]),
("Next tab", ["Tab"]),
("Previous tab", ["Shift-Tab"]),
("Important actions guide", ["g"]),
("Multi-selection mode", ["v"]),
),
),
Focus::FloatingWindow(ref float) => float.get_shortcut_list(),
@ -438,6 +452,10 @@ impl AppState {
}
pub fn handle_mouse(&mut self, event: &MouseEvent) -> bool {
if !self.mouse_enabled {
return true;
}
if !self.drawable {
return true;
}
@ -595,24 +613,35 @@ impl AppState {
true
}
fn scroll_down(&mut self) {
if let Some(selected) = self.selection.selected() {
if selected == self.filter.item_list().len() - 1 {
self.selection.select_first();
fn scroll(&mut self, direction: ScrollDir) {
let Some(selected) = self.selection.selected() else {
return;
};
let list_len = if !self.at_root() {
self.filter.item_list().len() + 1
} else {
self.selection.select_next();
}
}
self.filter.item_list().len()
};
if list_len == 0 {
return;
};
let next_selection = match direction {
ScrollDir::Up if selected == 0 => list_len - 1,
ScrollDir::Down if selected >= list_len - 1 => 0,
ScrollDir::Up => selected - 1,
ScrollDir::Down => selected + 1,
};
self.selection.select(Some(next_selection));
}
fn scroll_up(&mut self) {
if let Some(selected) = self.selection.selected() {
if selected == 0 {
self.selection.select_last();
} else {
self.selection.select_previous();
}
self.scroll(ScrollDir::Up)
}
fn scroll_down(&mut self) {
self.scroll(ScrollDir::Down)
}
fn toggle_multi_select(&mut self) {

View File

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

View File

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