Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a93d367974 | ||
|
|
bcc86814b5 | ||
|
|
462efef034 | ||
|
|
f0c62a5e7f | ||
|
|
df4c63657b | ||
|
|
9c201f0e97 | ||
|
|
c4c52cfb48 | ||
|
|
7c14c67ad0 | ||
|
|
4882bee0ad | ||
|
|
bbfd877ee5 | ||
|
|
1288b47c34 | ||
|
|
d849b22001 | ||
|
|
9fcdb24988 | ||
|
|
3e0bedd9c8 | ||
|
|
481cc94fa8 | ||
|
|
28e1e26b0e | ||
|
|
bddcd170c3 | ||
|
|
0a85d265cb | ||
|
|
c13945ced5 | ||
|
|
a33aa5f6be | ||
|
|
d9a62cf2a8 | ||
|
|
7287d8ef87 | ||
|
|
7c761a4895 | ||
|
|
fef99326c3 | ||
|
|
ac7eba14b0 | ||
|
|
f6bf229c8d | ||
|
|
01e17888fe | ||
|
|
8ac0c1eaf2 | ||
|
|
600f170d00 | ||
|
|
cbe29a05f5 | ||
|
|
8399adbfa0 | ||
|
|
2c81a48fe9 | ||
|
|
d03ced93bf | ||
|
|
8b1fc5f01c | ||
|
|
02cd9a853e | ||
|
|
648777a13e | ||
|
|
5264470127 | ||
|
|
f923dcb758 | ||
|
|
c92ca8a51f | ||
|
|
19ca7fa905 | ||
|
|
6bacb03687 | ||
|
|
5f8800ab44 | ||
|
|
8bfb3272c0 | ||
|
|
1a48280b14 | ||
|
|
9a272689eb | ||
|
|
6e4d052170 | ||
|
|
ceb3386c57 | ||
|
|
d22b24e54c | ||
|
|
28e4c112af | ||
|
|
9ec52aa49f | ||
|
|
1b28e90c62 | ||
|
|
1e31712b60 | ||
|
|
5faae67d11 | ||
|
|
ee72ede116 | ||
|
|
f5244ac062 | ||
|
|
9a089112c3 | ||
|
|
e64a857a43 | ||
|
|
ddd7d4193a | ||
|
|
83a1c8ce48 | ||
|
|
b29fa3b30c | ||
|
|
cd0bcfd214 | ||
|
|
a91c41871a | ||
|
|
9698f0e506 | ||
|
|
9e22f007b9 | ||
|
|
d472fb61aa | ||
|
|
9108ee8266 | ||
|
|
a7f7452b2b | ||
|
|
bc67399ebc | ||
|
|
02b037d0fc | ||
|
|
e567957ea0 | ||
|
|
67bdd3e863 | ||
|
|
fa573bce8f | ||
|
|
241d53ebc4 |
232
README.md
232
README.md
@@ -1,23 +1,45 @@
|
|||||||
# profile
|
# profile
|
||||||
|
|
||||||
This project aims to create an advanced bash profile. It includes aliases,
|
This project aims to create an advanced bash profile. It includes aliases,
|
||||||
a customized prompt and several functions for different purposes. It's mostly
|
a customized prompt and several functions for different purposes. It's mostly
|
||||||
targeted to system administrators but might satisfy some regular users.
|
targeted to system administrators but might satisfy some regular users.
|
||||||
|
|
||||||
## 1. Requirements
|
## 1. Requirements
|
||||||
|
|
||||||
profile requires **Bash 4.3 or higher** (for associative arrays and namerefs).
|
profile requires **Bash 4.3 or higher** (for associative arrays and namerefs).
|
||||||
It will refuse to load on older versions and will also refuse to load if the
|
It will refuse to load on older versions and will also refuse to load if the
|
||||||
current shell is not bash.
|
current shell is not bash.
|
||||||
|
|
||||||
## 2. Getting started
|
## 2. Getting started
|
||||||
|
|
||||||
Download and extract (or use git clone) the profile archive into your home
|
Download and extract (or use git clone) the profile archive into your home
|
||||||
directory. You will have to modify your `~/.bashrc` and/or `~/.profile` file to
|
directory.
|
||||||
add at the end (preferably):
|
|
||||||
|
The profile is designed to be **sourced**, not executed directly.
|
||||||
|
|
||||||
|
Manual setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
source <installpath>/profile/profile.sh
|
source <installpath>/profile/profile.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Automatic setup (recommended):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash <installpath>/profile/profile.sh --install
|
||||||
|
```
|
||||||
|
|
||||||
|
`--install` appends the required `source` line to both `~/.bashrc` and
|
||||||
|
`~/.profile` by default. You can target one file only:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash <installpath>/profile/profile.sh --install --bashrc
|
||||||
|
bash <installpath>/profile/profile.sh --install --profile
|
||||||
|
```
|
||||||
|
|
||||||
You may also set the `PROFILE_PATH` environment variable before sourcing if you
|
You may also set the `PROFILE_PATH` environment variable before sourcing if you
|
||||||
want to override the automatic path detection:
|
want to override the automatic path detection:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export PROFILE_PATH=/opt/profile
|
export PROFILE_PATH=/opt/profile
|
||||||
source /opt/profile/profile.sh
|
source /opt/profile/profile.sh
|
||||||
@@ -26,21 +48,42 @@ source /opt/profile/profile.sh
|
|||||||
It's not recommended to load that profile in `/etc/profile` as users' `.bashrc`
|
It's not recommended to load that profile in `/etc/profile` as users' `.bashrc`
|
||||||
files might interfere with some aliases and functions defined in profile.
|
files might interfere with some aliases and functions defined in profile.
|
||||||
|
|
||||||
### 2.1. Initial configuration
|
### 2.1. Interactive vs non-interactive shells
|
||||||
|
|
||||||
|
`profile.sh` detects whether the current shell is interactive.
|
||||||
|
|
||||||
|
In interactive shells (typical terminal sessions), profile enables
|
||||||
|
interactive-only features such as:
|
||||||
|
|
||||||
|
- aliases from `[aliases]`
|
||||||
|
- bash completion scripts from `profile.d/bash-completion/`
|
||||||
|
- prompt initialization (`PROMPT_COMMAND` and timer hook)
|
||||||
|
- welcome display (`showinfo`) and startup update check (`check_updates -q`)
|
||||||
|
|
||||||
|
In non-interactive shells (typical script execution), those features are
|
||||||
|
intentionally skipped to avoid side effects and startup noise. Public functions
|
||||||
|
remain available after sourcing, so scripts can still call profile helpers.
|
||||||
|
|
||||||
|
### 2.2. Initial configuration
|
||||||
|
|
||||||
Copy the example configuration file and customise it to your needs:
|
Copy the example configuration file and customise it to your needs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp <installpath>/profile/doc/profile.conf.example <installpath>/profile/profile.conf
|
cp <installpath>/profile/doc/profile.conf.example <installpath>/profile/profile.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
`profile.conf` is git-ignored so your personal settings will never be
|
`profile.conf` is git-ignored so your personal settings will never be
|
||||||
accidentally committed. All keys are optional — sensible defaults apply when
|
accidentally committed. All keys are optional — sensible defaults apply when
|
||||||
unset. See [section 4](#4-configuration) for the full reference.
|
unset. See [section 4](#4-configuration) for the full reference.
|
||||||
|
|
||||||
## 3. What's the purpose?
|
## 3. What's the purpose?
|
||||||
|
|
||||||
profile gives access to numerous functions, aliases and to an advanced prompt.
|
profile gives access to numerous functions, aliases and to an advanced prompt.
|
||||||
All functions are organized into modules under the `profile.d/` directory and
|
All functions are organized into modules under the `profile.d/` directory and
|
||||||
are loaded automatically at startup.
|
are loaded automatically at startup.
|
||||||
|
|
||||||
### 3.1. Prompt
|
### 3.1. Prompt
|
||||||
|
|
||||||
A bar-style prompt showing current time, execution time of the last command
|
A bar-style prompt showing current time, execution time of the last command
|
||||||
(with sub-millisecond precision), and the exit code of the last command.
|
(with sub-millisecond precision), and the exit code of the last command.
|
||||||
|
|
||||||
@@ -49,34 +92,49 @@ A bar-style prompt showing current time, execution time of the last command
|
|||||||
| Function | Module | Description |
|
| Function | Module | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `busy` | fun | Monitor /dev/urandom for a hex pattern — look busy |
|
| `busy` | fun | Monitor /dev/urandom for a hex pattern — look busy |
|
||||||
| `check_updates` | updates | Check whether a newer profile version is available online |
|
| `check_updates` | updates | Check whether a newer profile version is available online; when called with `-q` at startup a 3-second network timeout is applied so a slow or absent network never delays the prompt |
|
||||||
| `clean` | filefct | Erase backup files in given directories, optionally recursive |
|
| `clean` | filefct | Erase backup files in given directories, optionally recursive |
|
||||||
| `disp` | disp | Display formatted info / warning / error / debug messages |
|
| `conf_dump` | conf | Display the profile configuration file; `-s/--section NAME` restricts output to one section; an optional pattern argument filters keys by substring match |
|
||||||
| `dwl` | net | Download a URL using curl, wget, or fetch transparently |
|
| `conf_save` | conf | Save or update a key=value pair in a configuration section: `conf_save <section> <key> <value>`; creates the section header automatically if absent |
|
||||||
|
| `disp` | disp | Display formatted info / warning / error / debug messages; long messages are word-wrapped and continuation lines are indented to align with the message text |
|
||||||
|
| `dwl` | net | Download a URL using curl, wget, or fetch transparently; supports `-t <seconds>` / `--timeout <seconds>` to cap the transfer time |
|
||||||
| `expandlist` | filefct | Expand glob expressions into a quoted, separated list |
|
| `expandlist` | filefct | Expand glob expressions into a quoted, separated list |
|
||||||
|
| `fake_compile` | fun | Simulate a long compilation process with random warnings and errors |
|
||||||
| `file_stats` | filefct | Display file size statistics for a path |
|
| `file_stats` | filefct | Display file size statistics for a path |
|
||||||
| `findbig` | filefct | Find the biggest files in the given or current directory |
|
| `findbig` | filefct | Find the biggest files in the given or current directory |
|
||||||
| `finddead` | filefct | Find dead symbolic links in the given or current directory |
|
| `finddead` | filefct | Find dead symbolic links in the given or current directory |
|
||||||
| `findzero` | filefct | Find empty files in the given or current directory |
|
| `findzero` | filefct | Find empty files in the given or current directory |
|
||||||
|
| `gacp` | git | Add, commit and push changes; auto-pulls with rebase first if needed |
|
||||||
| `genpwd` | pwd | Generate one or more random secure passwords with configurable constraints |
|
| `genpwd` | pwd | Generate one or more random secure passwords with configurable constraints |
|
||||||
|
| `ggraph` | git | Display a decorated git history graph |
|
||||||
| `gpid` | processes | Give the list of PIDs matching the given process name(s) |
|
| `gpid` | processes | Give the list of PIDs matching the given process name(s) |
|
||||||
| `help` | help | Display the list of available functions and basic usage |
|
| `gprune` | git | Delete local branches already merged into the main branch |
|
||||||
|
| `greset` | git | Reset the current branch to upstream, stashing local changes first |
|
||||||
|
| `groot` | git | Display the repository root path, or change directory to it with `-g` |
|
||||||
|
| `gsync` | git | Fetch and rebase the current branch onto its upstream |
|
||||||
|
| `gst` | git | Display compact git status with branch tracking information |
|
||||||
|
| `gwip` | git | Create a quick WIP checkpoint commit |
|
||||||
|
| `hack` | fun | Simulate a dramatic multi-phase hacking sequence |
|
||||||
|
| `help` | help | Display the list of available functions and basic usage; `help <command>` delegates to `<command> --help` |
|
||||||
| `isipv4` | net | Tell if the given parameter is a valid IPv4 address |
|
| `isipv4` | net | Tell if the given parameter is a valid IPv4 address |
|
||||||
| `isipv6` | net | Tell if the given parameter is a valid IPv6 address |
|
| `isipv6` | net | Tell if the given parameter is a valid IPv6 address |
|
||||||
| `ku` | processes | Kill all processes owned by the given user name or ID |
|
| `ku` | processes | Kill all processes owned by the given user name or ID |
|
||||||
| `matrix` | rain | Console screensaver with Matrix-style digital rain (binary, kana, ascii charset) |
|
| `matrix` | rain | Console screensaver with Matrix-style digital rain (binary, kana, ascii charset) |
|
||||||
| `mcd` | filefct | Create a directory and immediately move into it |
|
| `mcd` | filefct | Create a directory and immediately move into it |
|
||||||
|
| `mdcat` | disp | Render Markdown files in terminal with colors, inline formatting, and framed code blocks |
|
||||||
| `meteo` | info | Display weather forecast for the configured or given city |
|
| `meteo` | info | Display weather forecast for the configured or given city |
|
||||||
| `myextip` | net | Get information about your public IP address |
|
| `myextip` | net | Get information about your public IP address |
|
||||||
| `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) |
|
| `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) |
|
||||||
| `pkgs` | packages | Search for a pattern in installed package names (distro-aware via `get_pkgmgr`, supports `-i`) |
|
| `pkgf` | packages | Find which installed package owns a given file (distro-aware via `get_pkgmgr`) |
|
||||||
|
| `pkgs` | packages | Search for a pattern in installed package names (distro-aware via `get_pkgmgr`) |
|
||||||
| `ppg` | processes | Look for the given pattern in running processes |
|
| `ppg` | processes | Look for the given pattern in running processes |
|
||||||
| `ppn` | processes | List processes matching an exact command name |
|
| `ppn` | processes | List processes matching an exact command name |
|
||||||
| `ppu` | processes | List processes owned by a specific user |
|
| `ppu` | processes | List processes owned by a specific user |
|
||||||
| `profile_upgrade` | updates | Upgrade profile to the latest version (git pull or archive) |
|
| `profile_upgrade` | updates | Upgrade profile to the latest version (git pull or archive) |
|
||||||
| `pwdscore` | pwd | Calculate the strength score of a given password |
|
| `pwdscore` | pwd | Calculate the strength score of a given password |
|
||||||
| `rain` | rain | Console screensaver with falling-rain effect (multiple color themes) |
|
| `rain` | rain | Console screensaver with falling-rain effect (multiple color themes) |
|
||||||
| `rmhost` | ssh | Remove host (name and IP) from SSH known_hosts; supports `--all-users` as root |
|
| `rainbow` | rain | Full-screen rainbow screensaver using only background colors, with horizontal color shifting |
|
||||||
|
| `rmhost` | ssh | Remove host(s) (name and IP) from SSH known_hosts; supports `--all-users` as root |
|
||||||
| `rmspc` | filefct | Replace spaces in filenames with underscores (or a custom character) |
|
| `rmspc` | filefct | Replace spaces in filenames with underscores (or a custom character) |
|
||||||
| `setc` | lang | Set locale to standard C (POSIX) |
|
| `setc` | lang | Set locale to standard C (POSIX) |
|
||||||
| `setlocale` | lang | Set console locale to any installed locale |
|
| `setlocale` | lang | Set console locale to any installed locale |
|
||||||
@@ -84,6 +142,7 @@ A bar-style prompt showing current time, execution time of the last command
|
|||||||
| `showinfo` | info | Display welcome banner and system information (figlet + neofetch/fastfetch) |
|
| `showinfo` | info | Display welcome banner and system information (figlet + neofetch/fastfetch) |
|
||||||
| `ssr` | ssh | SSH into a server as root, forwarding extra ssh options |
|
| `ssr` | ssh | SSH into a server as root, forwarding extra ssh options |
|
||||||
| `taz` | compress | Compress files and directories into a chosen archive format |
|
| `taz` | compress | Compress files and directories into a chosen archive format |
|
||||||
|
| `term_set` | conf | Set `TERM` to the best available terminal capability |
|
||||||
| `urlencode` | net | URL-encode a string |
|
| `urlencode` | net | URL-encode a string |
|
||||||
| `utaz` | compress | Smartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm) |
|
| `utaz` | compress | Smartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm) |
|
||||||
| `ver` | info | Display the installed profile version |
|
| `ver` | info | Display the installed profile version |
|
||||||
@@ -91,7 +150,15 @@ A bar-style prompt showing current time, execution time of the last command
|
|||||||
Locale shortcut functions (`setfr`, `setus`, etc.) are dynamically generated at
|
Locale shortcut functions (`setfr`, `setus`, etc.) are dynamically generated at
|
||||||
startup from the `SET_LOCALE` configuration key (see section 4).
|
startup from the `SET_LOCALE` configuration key (see section 4).
|
||||||
|
|
||||||
|
### 3.3. Bash completion
|
||||||
|
|
||||||
|
profile loads all `*.sh` files found under `profile.d/bash-completion/`
|
||||||
|
automatically in interactive sessions. This directory is the right place to add
|
||||||
|
any custom completion definitions. profile already ships completions for its git
|
||||||
|
helper functions there (`git-completion.sh`).
|
||||||
|
|
||||||
## 4. Configuration
|
## 4. Configuration
|
||||||
|
|
||||||
profile uses an INI-style configuration file (`profile.conf`) located in the
|
profile uses an INI-style configuration file (`profile.conf`) located in the
|
||||||
same directory as `profile.sh`. Sections are declared with `[section_name]` and
|
same directory as `profile.sh`. Sections are declared with `[section_name]` and
|
||||||
keys follow `key = value` syntax. Each module calls `load_conf "<section>"` at
|
keys follow `key = value` syntax. Each module calls `load_conf "<section>"` at
|
||||||
@@ -101,16 +168,44 @@ apply when unset.
|
|||||||
|
|
||||||
`profile.conf` is listed in `.gitignore` so personal values (API keys, cities,
|
`profile.conf` is listed in `.gitignore` so personal values (API keys, cities,
|
||||||
compiler flags, …) are never accidentally staged. Start from the annotated
|
compiler flags, …) are never accidentally staged. Start from the annotated
|
||||||
template at `doc/profile.conf.example` (see [section 2.1](#21-initial-configuration)).
|
template at `doc/profile.conf.example` (see [section 2.2](#22-initial-configuration)).
|
||||||
|
|
||||||
### 4.1. Core sections
|
### 4.1. Core sections
|
||||||
|
|
||||||
| Section | Purpose |
|
| Section | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `[system]` | Bash history size, pager, and other shell behaviours |
|
| `[system]` | Bash history size, pager, terminal type, and other shell behaviours |
|
||||||
| `[general]` | General-purpose variables (e.g. compilation flags, `MAKEFLAGS`) |
|
| `[general]` | General-purpose variables (e.g. compilation flags, `MAKEFLAGS`) |
|
||||||
| `[aliases]` | User command aliases, loaded for interactive shells only |
|
| `[aliases]` | User command aliases, loaded for interactive shells only |
|
||||||
|
|
||||||
|
#### `TERM` — terminal type detection
|
||||||
|
|
||||||
|
The `TERM` key in `[system]` has special handling via `term_set`, which runs
|
||||||
|
automatically at startup:
|
||||||
|
|
||||||
|
| Value | Behaviour |
|
||||||
|
| --- | --- |
|
||||||
|
| *(absent or `smart`)* | Auto-detect the best available `terminfo` entry (default) |
|
||||||
|
| any other string | Exported as-is — standard Unix behaviour |
|
||||||
|
|
||||||
|
When auto-detection runs, `COLORTERM=truecolor|24bit` is first evaluated as a
|
||||||
|
global truecolor flag. Each emulator hint is then checked in order; within each
|
||||||
|
branch a `*-direct` terminfo entry (24-bit colour) is tried before the
|
||||||
|
`*-256color` fallback. Only entries confirmed to exist via `terminfo` are used.
|
||||||
|
|
||||||
|
1. **`$COLORTERM`** — `truecolor` or `24bit` sets the truecolor flag for all subsequent probes
|
||||||
|
2. **`$TERM_PROGRAM`** — emulator name hints:
|
||||||
|
- `iTerm.app` → `xterm-direct`¹, then `xterm-256color`
|
||||||
|
- `WezTerm` → `xterm-direct`¹, then `wezterm`, then `xterm-256color`
|
||||||
|
- `Hyper`, `vscode` → `xterm-direct`¹, then `xterm-256color`
|
||||||
|
3. **`$VTE_VERSION`** — GNOME Terminal, Tilix, … → `vte-direct`¹, then `vte-256color`, then `xterm-256color`
|
||||||
|
4. **`$WT_SESSION`** — Windows Terminal → `xterm-direct`¹, then `xterm-256color`
|
||||||
|
5. **`$TMUX`** — tmux session → `tmux-direct`¹ → `screen-direct`¹, then `tmux-256color`, then `screen-256color`
|
||||||
|
6. **`$STY`** — GNU screen session → `screen-direct`¹, then `screen-256color`, then `screen`
|
||||||
|
7. **Generic probe** — `xterm-256color` → `xterm-color` → `xterm` → `vt100`
|
||||||
|
|
||||||
|
¹ Only attempted when the truecolor flag is set (`COLORTERM=truecolor` or `24bit`).
|
||||||
|
|
||||||
### 4.2. Module defaults
|
### 4.2. Module defaults
|
||||||
|
|
||||||
Each module exposes its hardcoded defaults as configuration keys. Set a key to
|
Each module exposes its hardcoded defaults as configuration keys. Set a key to
|
||||||
@@ -121,7 +216,7 @@ change the default without having to pass flags every time.
|
|||||||
| Key | Default | Description |
|
| Key | Default | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `TAZ_DEFAULT_FORMAT` | `tar.gz` | Archive format for `taz` (`tar.gz`, `tar.bz2`, `tar.xz`, `zip`, …) |
|
| `TAZ_DEFAULT_FORMAT` | `tar.gz` | Archive format for `taz` (`tar.gz`, `tar.bz2`, `tar.xz`, `zip`, …) |
|
||||||
| `TAZ_DEFAULT_THREADS` | `0` | Compression threads (0 = auto-detect) |
|
| `TAZ_DEFAULT_THREADS` | `auto` | Compression threads (`auto` = runtime CPU count, or explicit positive integer) |
|
||||||
| `TAZ_DEFAULT_LEVEL` | `6` | Compression level (1–9) |
|
| `TAZ_DEFAULT_LEVEL` | `6` | Compression level (1–9) |
|
||||||
| `UTAZ_DEFAULT_DELETE` | `0` | Set to `1` to delete the source archive after extraction |
|
| `UTAZ_DEFAULT_DELETE` | `0` | Set to `1` to delete the source archive after extraction |
|
||||||
| `UTAZ_DEFAULT_DIR_MODE` | `0` | Set to `1` to always extract into a subdirectory |
|
| `UTAZ_DEFAULT_DIR_MODE` | `0` | Set to `1` to always extract into a subdirectory |
|
||||||
@@ -141,8 +236,11 @@ change the default without having to pass flags every time.
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `RAIN_DEFAULT_SPEED` | `0.1` | Falling speed for `rain` |
|
| `RAIN_DEFAULT_SPEED` | `0.1` | Falling speed for `rain` |
|
||||||
| `RAIN_DEFAULT_COLOR` | `Green` | Colour for `rain` |
|
| `RAIN_DEFAULT_COLOR` | `Green` | Colour for `rain` |
|
||||||
|
| `RAIN_DEFAULT_DENSITY` | dynamic | Maximum number of simultaneous falling elements for `rain` |
|
||||||
|
| `RAINBOW_DEFAULT_SPEED` | `0.04` | Horizontal color shift speed for `rainbow` |
|
||||||
| `MATRIX_DEFAULT_SPEED` | `0.05` | Falling speed for `matrix` |
|
| `MATRIX_DEFAULT_SPEED` | `0.05` | Falling speed for `matrix` |
|
||||||
| `MATRIX_DEFAULT_COLOR` | `Green` | Colour for `matrix` |
|
| `MATRIX_DEFAULT_COLOR` | `Green` | Colour for `matrix` |
|
||||||
|
| `MATRIX_DEFAULT_DENSITY` | dynamic | Maximum number of simultaneous falling elements for `matrix` |
|
||||||
| `MATRIX_DEFAULT_CHARSET` | `binary` | Character set for `matrix` (`binary`, `kana`, `ascii`) |
|
| `MATRIX_DEFAULT_CHARSET` | `binary` | Character set for `matrix` (`binary`, `kana`, `ascii`) |
|
||||||
|
|
||||||
**`[ssh]`**
|
**`[ssh]`**
|
||||||
@@ -170,6 +268,20 @@ change the default without having to pass flags every time.
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `BUSY_DEFAULT_PATTERN` | `[0-9a-f]` | Hex pattern matched by `busy` |
|
| `BUSY_DEFAULT_PATTERN` | `[0-9a-f]` | Hex pattern matched by `busy` |
|
||||||
| `BUSY_DEFAULT_DELAY` | `0.1` | Polling delay (seconds) for `busy` |
|
| `BUSY_DEFAULT_DELAY` | `0.1` | Polling delay (seconds) for `busy` |
|
||||||
|
| `FAKE_COMPILE_DEFAULT_MIN_DELAY` | `40` | Min delay between output lines for `fake_compile` (ms) |
|
||||||
|
| `FAKE_COMPILE_DEFAULT_MAX_DELAY` | `150` | Max delay between output lines for `fake_compile` (ms) |
|
||||||
|
| `FAKE_COMPILE_DEFAULT_LANG` | `c` | Default language preset for `fake_compile` (`c`, `cpp`, `java`, `python`, `random`) |
|
||||||
|
| `HACK_DEFAULT_MIN_DELAY` | `60` | Min delay between output lines for `hack` (ms) |
|
||||||
|
| `HACK_DEFAULT_MAX_DELAY` | `250` | Max delay between output lines for `hack` (ms) |
|
||||||
|
|
||||||
|
**`[git]`**
|
||||||
|
|
||||||
|
| Key | Default | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GIT_MAIN_BRANCH` | `main` | Fallback main branch name used when remote HEAD cannot be detected |
|
||||||
|
| `GIT_DEFAULT_REMOTE` | `origin` | Default remote used by git helper functions |
|
||||||
|
| `GIT_WIP_PREFIX` | `wip` | Prefix used by `gwip` when generating automatic checkpoint messages |
|
||||||
|
| `GIT_GACP_AUTO_ADD` | `1` | Set to `1` to make `gacp` automatically add all modified files when no explicit file list is given; set to `0` to require explicit paths or the `-a` flag |
|
||||||
|
|
||||||
**`[info]`**
|
**`[info]`**
|
||||||
|
|
||||||
@@ -182,6 +294,7 @@ change the default without having to pass flags every time.
|
|||||||
| Key | Default | Description |
|
| Key | Default | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `DWL_PREFERRED_TOOL` | _(empty)_ | Force `dwl` to use `curl`, `wget`, or `fetch` (auto-detected when unset) |
|
| `DWL_PREFERRED_TOOL` | _(empty)_ | Force `dwl` to use `curl`, `wget`, or `fetch` (auto-detected when unset) |
|
||||||
|
| `DWL_DEFAULT_RESUME` | `0` | Enable `dwl` resume mode by default (`1`/`true`/`yes`/`on`); applies to file downloads |
|
||||||
| `MYEXTIP_DEFAULT_URL` | `https://ip-api.com/json` | API endpoint used by `myextip` |
|
| `MYEXTIP_DEFAULT_URL` | `https://ip-api.com/json` | API endpoint used by `myextip` |
|
||||||
|
|
||||||
**`[packages]`**
|
**`[packages]`**
|
||||||
@@ -244,6 +357,23 @@ PROMPT_THEME_DIR = ~/.mythemes # optional: custom search directory
|
|||||||
Built-in themes: `default`, `dark`, `light`, `solarized`, `solarized-light`,
|
Built-in themes: `default`, `dark`, `light`, `solarized`, `solarized-light`,
|
||||||
`monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`.
|
`monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`.
|
||||||
|
|
||||||
|
**Runtime theme switching (`set_theme`):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
set_theme --list # list available themes
|
||||||
|
set_theme dark # apply theme for current shell session
|
||||||
|
set_theme --preview dark # preview theme colors without applying
|
||||||
|
set_theme --save # save currently active theme to config
|
||||||
|
set_theme --save dark # apply and save the given theme
|
||||||
|
```
|
||||||
|
|
||||||
|
`set_theme --save` writes `PROMPT_THEME` in `[prompt]` to:
|
||||||
|
|
||||||
|
- `~/.profile.conf` when present
|
||||||
|
- otherwise `profile.conf` in the profile installation directory
|
||||||
|
|
||||||
|
`--preview` and `--save` are mutually exclusive.
|
||||||
|
|
||||||
**Overriding individual prompt colour slots:**
|
**Overriding individual prompt colour slots:**
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
@@ -252,7 +382,7 @@ PROMPT_COLOR_USER_FG = $ICyan
|
|||||||
PROMPT_COLOR_DIR_FG = $IYellow
|
PROMPT_COLOR_DIR_FG = $IYellow
|
||||||
```
|
```
|
||||||
|
|
||||||
The eleven available `PROMPT_COLOR_*` keys are:
|
The twelve available `PROMPT_COLOR_*` keys are:
|
||||||
|
|
||||||
| Key | Role |
|
| Key | Role |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -263,6 +393,30 @@ The eleven available `PROMPT_COLOR_*` keys are:
|
|||||||
| `PROMPT_COLOR_ROOT_FG` | Username colour when running as root |
|
| `PROMPT_COLOR_ROOT_FG` | Username colour when running as root |
|
||||||
| `PROMPT_COLOR_USER_FG` | Username@host colour for normal users |
|
| `PROMPT_COLOR_USER_FG` | Username@host colour for normal users |
|
||||||
| `PROMPT_COLOR_DIR_FG` | Working directory colour |
|
| `PROMPT_COLOR_DIR_FG` | Working directory colour |
|
||||||
|
| `PROMPT_COLOR_CTX_FG` | Git/Conda context segment colour at end of top bar |
|
||||||
|
|
||||||
|
**Top-bar context segment (Git / Conda):**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[prompt]
|
||||||
|
PROMPT_SHOW_GIT = 1
|
||||||
|
PROMPT_SHOW_GIT_STATUS = 1
|
||||||
|
PROMPT_GIT_TIMEOUT = 2
|
||||||
|
PROMPT_SHOW_CONDA = 1
|
||||||
|
PROMPT_SHOW_VENV = 1
|
||||||
|
PROMPT_SHOW_SESSION = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
When enabled, the top prompt bar appends:
|
||||||
|
|
||||||
|
- `git:<branch>` when inside a Git repository
|
||||||
|
- `git:<branch>*` when local changes are present
|
||||||
|
- `git:<branch> +N/-M` when ahead/behind upstream
|
||||||
|
- `git:<branch>?` when `git diff` or `git rev-list` exceeded `PROMPT_GIT_TIMEOUT`
|
||||||
|
- `conda:<env>` when a Conda environment is active
|
||||||
|
- `venv:<name>` when a Python virtualenv is active (and Conda is not)
|
||||||
|
- `ssh`, `tmux`, `screen` markers when the session runs in those contexts
|
||||||
|
- all, separated by `|`, when several are available
|
||||||
|
|
||||||
**Writing a custom theme file:**
|
**Writing a custom theme file:**
|
||||||
|
|
||||||
@@ -286,19 +440,57 @@ and other colour-aware tools.
|
|||||||
True-colour themes (`solarized`, `solarized-light`) require a terminal with
|
True-colour themes (`solarized`, `solarized-light`) require a terminal with
|
||||||
24-bit colour support (Konsole, iTerm2, kitty, Alacritty, Windows Terminal).
|
24-bit colour support (Konsole, iTerm2, kitty, Alacritty, Windows Terminal).
|
||||||
Verify support with:
|
Verify support with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
printf '\e[38;2;38;139;210mTrue colour test\e[0m\n'
|
printf '\e[38;2;38;139;210mTrue colour test\e[0m\n'
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Contact and more information
|
## 5. Optional dependencies
|
||||||
### 5.1. New users
|
|
||||||
|
profile is designed so that every external dependency is optional: all features
|
||||||
|
degrade gracefully (an error message is shown and the specific function returns
|
||||||
|
early) when a binary is absent. The table below lists packages that are **not**
|
||||||
|
part of a standard minimal Linux installation (might differ from your actual
|
||||||
|
distribution, some are still extremely common). Nothing in this list is required
|
||||||
|
to load profile or use its core functions.
|
||||||
|
|
||||||
|
| Binary | Typical package name | Distr. | Function(s) | Behaviour when absent |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `figlet` | `figlet` | all | `showinfo` | ASCII banner skipped |
|
||||||
|
| `neofetch` | `neofetch` | all | `showinfo` | Falls back to `fastfetch`, then skipped |
|
||||||
|
| `fastfetch` | `fastfetch` | all | `showinfo` | Falls back if `neofetch` absent; then skipped |
|
||||||
|
| `jq` | `jq` | all | `myextip` | Raw JSON shown instead of formatted output |
|
||||||
|
| `hexdump` | `util-linux` / `bsdmainutils` | all | `busy` | Error message, function returns 1 |
|
||||||
|
| `numfmt` | `coreutils` ≥ 8.21 | all | `file_stats --min/--max` | Error message, function returns 1 |
|
||||||
|
| `envsubst` | `gettext` | all | config loading | `$VAR` references in `profile.conf` values left literal |
|
||||||
|
| `killall` | `psmisc` | all | `ku` | Falls back to `pkill` (procps); error if both absent |
|
||||||
|
| `pkill` / `pgrep` | `procps` / `procps-ng` | all | `ku`, `ppg`, `gpid` | `ku` falls back to `killall`; `ppg`/`gpid` fall back to `ps + awk` |
|
||||||
|
| `pigz` | `pigz` | all | `taz` (gzip) | Falls back to `gzip` (slower, single-thread) |
|
||||||
|
| `plzip` | `plzip` | all | `taz` (lzip) | Falls back to `lzip` |
|
||||||
|
| `lzip` / `plzip` | `lzip` | all | `taz`/`utaz` (.lz) | Error message if neither is available |
|
||||||
|
| `xz` | `xz-utils` / `xz` | all | `taz`/`utaz` (.xz) | Error message |
|
||||||
|
| `unrar` | `unrar` / `unrar-free` | all | `utaz` (.rar) | Error message |
|
||||||
|
| `unarj` | `arj` | all | `utaz` (.arj) | Error message |
|
||||||
|
| `lha` | `lhasa` | all | `utaz` (.lzh) | Error message |
|
||||||
|
| `unace` | `unace` | all | `utaz` (.ace) | Error message |
|
||||||
|
| `7z` | `p7zip-full` | all | `taz`/`utaz` (.7z) | Error message |
|
||||||
|
| `cabextract` | `cabextract` | all | `utaz` (.cab) | Error message |
|
||||||
|
| `rpm2cpio` | `rpm` | all | `utaz` (.rpm) | Error message |
|
||||||
|
| `nix-locate` | `nix-index` | NixOS | `pkgf` | Error: unsupported package manager |
|
||||||
|
| `qfile` | `gentoolkit` | Gentoo | `pkgf` | Error: unsupported package manager |
|
||||||
|
|
||||||
|
## 6. Contact and more information
|
||||||
|
|
||||||
|
### 6.1. New users
|
||||||
|
|
||||||
If you use (or plan to use) `profile`, I'll be happy if you simply mail me to
|
If you use (or plan to use) `profile`, I'll be happy if you simply mail me to
|
||||||
let me know, especially if you don't plan to contribute. If you plan to
|
let me know, especially if you don't plan to contribute. If you plan to
|
||||||
contribute, I'll be twice happier for sure!
|
contribute, I'll be twice happier for sure!
|
||||||
|
|
||||||
### 5.2. Bugs
|
### 6.2. Bugs
|
||||||
|
|
||||||
**profile** bug tracker is hosted on its Gitea instance. Check the
|
**profile** bug tracker is hosted on its Gitea instance. Check the
|
||||||
https://git.geoffray-levasseur.org/fatalerrors/profile page. If you find a bug,
|
<https://git.geoffray-levasseur.org/fatalerrors/profile> page. If you find a bug,
|
||||||
you can also submit a bug report to the maintainer mail address mentioned at
|
you can also submit a bug report to the maintainer mail address mentioned at
|
||||||
the end of that document. A bug report may contain the command line parameters
|
the end of that document. A bug report may contain the command line parameters
|
||||||
where the bug happens, OS details, the module that triggered it, if any, and the
|
where the bug happens, OS details, the module that triggered it, if any, and the
|
||||||
@@ -310,7 +502,8 @@ have not tested the same code under a real Unix environment.
|
|||||||
Check the [FAQ](./doc/FAQ.md) and the [to-do list](./doc/todo.md) before
|
Check the [FAQ](./doc/FAQ.md) and the [to-do list](./doc/todo.md) before
|
||||||
sending any feature request or bug report, as it might already be documented.
|
sending any feature request or bug report, as it might already be documented.
|
||||||
|
|
||||||
### 5.3. How to contribute?
|
### 6.3. How to contribute?
|
||||||
|
|
||||||
You are free to improve and contribute as you wish. If you have no idea what to
|
You are free to improve and contribute as you wish. If you have no idea what to
|
||||||
do or want some direction, you can check the [to-do list](./doc/todo.md),
|
do or want some direction, you can check the [to-do list](./doc/todo.md),
|
||||||
containing desired future improvements. Make sure you always have the latest
|
containing desired future improvements. Make sure you always have the latest
|
||||||
@@ -332,6 +525,7 @@ installation will probably be rejected.
|
|||||||
If you want to make a financial contribution, please contact me by mail.
|
If you want to make a financial contribution, please contact me by mail.
|
||||||
|
|
||||||
### 5.4. License, website, and maintainer
|
### 5.4. License, website, and maintainer
|
||||||
|
|
||||||
Everything except configuration files is licensed under the BSD-3 license.
|
Everything except configuration files is licensed under the BSD-3 license.
|
||||||
Please check the license file alongside this one.
|
Please check the license file alongside this one.
|
||||||
|
|
||||||
@@ -344,4 +538,4 @@ You can mail the author at fatalerrors \<at\> geoffray-levasseur \<dot\> org.
|
|||||||
Documentation (c) 2021-2026 Geoffray Levasseur.
|
Documentation (c) 2021-2026 Geoffray Levasseur.
|
||||||
|
|
||||||
This file is distributed under the 3-clause BSD license. The complete license
|
This file is distributed under the 3-clause BSD license. The complete license
|
||||||
agreement can be obtained at: https://opensource.org/licenses/BSD-3-Clause
|
agreement can be obtained at: <https://opensource.org/licenses/BSD-3-Clause>
|
||||||
|
|||||||
259
doc/CHANGELOG.md
259
doc/CHANGELOG.md
@@ -7,9 +7,68 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [4.1.0] — 2026-05-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `profile.sh --install` command to automatically configure profile loading in
|
||||||
|
shell startup files.
|
||||||
|
- `--install --bashrc` and `--install --profile` target selectors for
|
||||||
|
single-file installation.
|
||||||
|
- **`git.sh`** — entirely new module providing git workflow helpers: `gst`,
|
||||||
|
`ggraph`, `gsync`, `gacp`, `greset`, `gwip`, `gprune`, `groot`.
|
||||||
|
- Dedicated git helper completions under `profile.d/bash-completion/`, loaded
|
||||||
|
automatically in interactive sessions from `profile.d/bash-completion/*.sh`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `disp` now wraps long messages on terminal width, avoids mid-word splits, and
|
||||||
|
aligns continuation lines with the message body after the prefix.
|
||||||
|
- `help` now supports `help <command>` and delegates to `<command> --help`.
|
||||||
|
- `taz` now supports `-p auto` / `--parallel=auto` to automatically use the
|
||||||
|
runtime CPU count. This mode is now the default via
|
||||||
|
`TAZ_DEFAULT_THREADS=auto`. Backward compatibility with 0 being interpreted as
|
||||||
|
auto is maintained.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Startup responsiveness improved: `check_updates -q` now uses a short network
|
||||||
|
timeout so unavailable/slow networks no longer delay prompt readiness.
|
||||||
|
- `dwl` gained timeout support (`-t` / `--timeout`) and is now used by quiet
|
||||||
|
startup update checks to enforce fast failure.
|
||||||
|
- `profile.sh` now detects direct execution and warns that it is designed to be
|
||||||
|
sourced.
|
||||||
|
|
||||||
|
## [4.0.0] — 2026-04-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- New `profile.conf` reference template at `doc/profile.conf.example`.
|
||||||
|
- Dynamic locale shortcuts generated from `SET_LOCALE` and startup default
|
||||||
|
language selection through `DEFAULT_LANG`.
|
||||||
|
- Prompt theming system with bundled themes (`default`, `dark`, `light`,
|
||||||
|
`solarized`, `solarized-light`, `monokai`, `monochrome`, `abyss`, `plasma`,
|
||||||
|
`adwaita`) and per-key prompt color overrides.
|
||||||
|
- Module defaults exposed as configuration keys in `profile.conf`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `utaz` now supports a wider range of archive formats.
|
||||||
|
- Prompt and theme rendering improved, including better 24-bit color support.
|
||||||
|
- Overall code quality and maintainability improved across modules.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- README updated with full function reference and configuration tables.
|
||||||
|
- New and expanded docs in `doc/` (`CONTRIBUTING.md`, `FAQ.md`, `todo.md`).
|
||||||
|
- Historical releases imported from `history.txt` into this changelog.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [3.99.2-4_rc_2] — 2026-04-21
|
## [3.99.2-4_rc_2] — 2026-04-21
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **`prompt.sh`** — `\$Last_Command` in PS1 was escaped, preventing the exit
|
- **`prompt.sh`** — `\$Last_Command` in PS1 was escaped, preventing the exit
|
||||||
code from ever appearing in the prompt (the local variable no longer exists
|
code from ever appearing in the prompt (the local variable no longer exists
|
||||||
when PS1 is rendered by bash). Removed the backslash so the value is embedded
|
when PS1 is rendered by bash). Removed the backslash so the value is embedded
|
||||||
@@ -24,6 +83,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
|||||||
`"Usage: ppg <string>"` instead of `"Usage: kt <pid>"`.
|
`"Usage: ppg <string>"` instead of `"Usage: kt <pid>"`.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **`packages.sh` — `get_pkgmgr()`** — new exported helper that detects the
|
- **`packages.sh` — `get_pkgmgr()`** — new exported helper that detects the
|
||||||
active package manager of the running distribution. Detection first reads
|
active package manager of the running distribution. Detection first reads
|
||||||
`/etc/os-release` (`ID` then `ID_LIKE`), then falls back to a
|
`/etc/os-release` (`ID` then `ID_LIKE`), then falls back to a
|
||||||
@@ -36,6 +96,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
|||||||
## [3.99.1-4_rc_1] — 2026
|
## [3.99.1-4_rc_1] — 2026
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Theming system** — `load_theme` in `profile.d/prompt.sh` loads `.theme`
|
- **Theming system** — `load_theme` in `profile.d/prompt.sh` loads `.theme`
|
||||||
files from `profile.d/themes/` (or a custom directory set via
|
files from `profile.d/themes/` (or a custom directory set via
|
||||||
`PROMPT_THEME_DIR`). Theme files are **parsed, not executed** — no shell code
|
`PROMPT_THEME_DIR`). Theme files are **parsed, not executed** — no shell code
|
||||||
@@ -63,6 +124,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
|||||||
never accidentally staged.
|
never accidentally staged.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- README §2 now explains how to create `profile.conf` from
|
- README §2 now explains how to create `profile.conf` from
|
||||||
`doc/profile.conf.example` (new section 2.1 "Initial configuration").
|
`doc/profile.conf.example` (new section 2.1 "Initial configuration").
|
||||||
- README §4 updated with full module-defaults tables, theming reference, and a
|
- README §4 updated with full module-defaults tables, theming reference, and a
|
||||||
@@ -71,6 +133,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
|||||||
variables, only data).
|
variables, only data).
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- `load_theme` uses a strict allowlist (no `eval`, no sourcing). Only
|
- `load_theme` uses a strict allowlist (no `eval`, no sourcing). Only
|
||||||
`PROMPT_COLOR_*` keys and known `disp.sh` colour variable names are accepted.
|
`PROMPT_COLOR_*` keys and known `disp.sh` colour variable names are accepted.
|
||||||
Values must match `\$[A-Za-z_][A-Za-z0-9_]*` or `\\e\[[0-9;]*m`; any other
|
Values must match `\$[A-Za-z_][A-Za-z0-9_]*` or `\\e\[[0-9;]*m`; any other
|
||||||
@@ -78,15 +141,10 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> **Note:** Versions prior to `3.95.x-4_beta` did not maintain a formal
|
|
||||||
> changelog. The full history of earlier changes is available through the git
|
|
||||||
> log (`git log --oneline`).
|
|
||||||
|
|
||||||
## [3.95.3-4_beta_3] — 2024
|
## [3.95.3-4_beta_3] — 2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Initial public release candidate series.
|
- Initial public release candidate series.
|
||||||
- Core modules: `compress`, `disp`, `filefct`, `fun`, `help`, `info`, `lang`,
|
- Core modules: `compress`, `disp`, `filefct`, `fun`, `help`, `info`, `lang`,
|
||||||
`net`, `packages`, `processes`, `prompt`, `pwd`, `rain`, `ssh`, `updates`.
|
`net`, `packages`, `processes`, `prompt`, `pwd`, `rain`, `ssh`, `updates`.
|
||||||
@@ -95,3 +153,192 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
|
|||||||
- `genpwd` / `pwdscore` password tools.
|
- `genpwd` / `pwdscore` password tools.
|
||||||
- `matrix` / `rain` screensavers.
|
- `matrix` / `rain` screensavers.
|
||||||
- `profile_upgrade` with git and archive download support.
|
- `profile_upgrade` with git and archive download support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **Note:** The section below was imported from `history.txt` to preserve
|
||||||
|
> pre-`3.95.x-4_beta` release notes.
|
||||||
|
|
||||||
|
## Legacy releases (imported from `history.txt`)
|
||||||
|
|
||||||
|
### [3.6.1] — 2026-03-05
|
||||||
|
|
||||||
|
- Fix typo in `compress.sh`.
|
||||||
|
|
||||||
|
### [3.6.0] — 2026-03-05
|
||||||
|
|
||||||
|
- Improved `utaz` with broader multi-format support.
|
||||||
|
- Introduced `ppu` and `ppn`.
|
||||||
|
- Improved update system.
|
||||||
|
|
||||||
|
### [3.5.0] — 2026-03-04
|
||||||
|
|
||||||
|
- `rain` now has configurable speed and color.
|
||||||
|
- `showinfo` adapted to `fastfetch` (in addition to `neofetch`).
|
||||||
|
|
||||||
|
### [3.3.1] — 2022-02-24
|
||||||
|
|
||||||
|
- Fixed version detection.
|
||||||
|
- Added `busy`.
|
||||||
|
- Fixed use of library functions before loading.
|
||||||
|
|
||||||
|
### [3.3.0] — 2022-11-28
|
||||||
|
|
||||||
|
- Initial version update support.
|
||||||
|
- Changed versioning code.
|
||||||
|
- Added installation path detection.
|
||||||
|
|
||||||
|
### [3.2.3] — 2022-11-28
|
||||||
|
|
||||||
|
- Improved README.
|
||||||
|
|
||||||
|
### [3.2.2] — 2022-11-21
|
||||||
|
|
||||||
|
- Fixed `taz` compression level parsing.
|
||||||
|
- Fixed typo in `dpkgs`.
|
||||||
|
|
||||||
|
### [3.2.1] — 2022-11-20
|
||||||
|
|
||||||
|
- Fixed several messages.
|
||||||
|
- Made `dpkgs` RPM-aware (initial support).
|
||||||
|
- Removed version history from main script and reverted declaration order.
|
||||||
|
- Added required license information in all files.
|
||||||
|
- Completed `LICENSE` file.
|
||||||
|
|
||||||
|
### [3.2.0] — 2022-11-18
|
||||||
|
|
||||||
|
- Created `disp` command and integrated it across the codebase.
|
||||||
|
|
||||||
|
### [3.1.1] — 2022-11-10
|
||||||
|
|
||||||
|
- `genpwd`: added feasibility check for requested password constraints.
|
||||||
|
|
||||||
|
### [3.1.0] — 2022-11-08
|
||||||
|
|
||||||
|
- Added password generator.
|
||||||
|
|
||||||
|
### [3.0.1] — 2022-11-07
|
||||||
|
|
||||||
|
- Added concatenation option to `rmspc`.
|
||||||
|
- Added `ku`.
|
||||||
|
- Improved error handling in `meteo`.
|
||||||
|
|
||||||
|
### [3.0.0] — 2022-08-27
|
||||||
|
|
||||||
|
- Split code into several files/modules.
|
||||||
|
- Added `rain` screensaver.
|
||||||
|
|
||||||
|
### [2.8.2] — 2022-07-29
|
||||||
|
|
||||||
|
- Added warning for non-bash users.
|
||||||
|
|
||||||
|
### [2.8.1] — 2022-07-19
|
||||||
|
|
||||||
|
- Cleanup, fixes and optimizations.
|
||||||
|
|
||||||
|
### [2.8.0] — 2022-06-24
|
||||||
|
|
||||||
|
- Added `backtrace`, `error` and `settrace`.
|
||||||
|
- Bugfixes in `showinfo`.
|
||||||
|
|
||||||
|
### [2.7.1] — 2022-06-22
|
||||||
|
|
||||||
|
- Minor corrections.
|
||||||
|
- Added `help` command.
|
||||||
|
|
||||||
|
### [2.7.0] — 2022-06-21
|
||||||
|
|
||||||
|
- Added `isipv4` and `isipv6`, integrated into `rmhost`.
|
||||||
|
- Removed broken Konsole save/restore support.
|
||||||
|
|
||||||
|
### [2.6.3] — 2021-10-18
|
||||||
|
|
||||||
|
- Changed PS1 to status-bar style.
|
||||||
|
- Minor improvements.
|
||||||
|
|
||||||
|
### [2.6.2] — 2021-02-26
|
||||||
|
|
||||||
|
- Bugfix in `taz` for directories with trailing slash.
|
||||||
|
|
||||||
|
### [2.6.1] — 2020-12-25
|
||||||
|
|
||||||
|
- Added checks in `rmhost`.
|
||||||
|
- Improved `rmspc`.
|
||||||
|
- Created `expandlist`.
|
||||||
|
|
||||||
|
### [2.6.0] — 2020-10-24
|
||||||
|
|
||||||
|
- Added Konsole session save/restore.
|
||||||
|
|
||||||
|
### [2.5.3] — 2020-09-11
|
||||||
|
|
||||||
|
- Added aliases, improved code consistency and fixed typos.
|
||||||
|
- Improved `utaz`, removed `showdiskmap`, removed remaining French text.
|
||||||
|
- Added license information for future publication.
|
||||||
|
|
||||||
|
### [2.5.2] — 2020-03-06
|
||||||
|
|
||||||
|
- Sorted and improved aliases.
|
||||||
|
|
||||||
|
### [2.5.1] — 2020-03-05
|
||||||
|
|
||||||
|
- Language consistency fixes.
|
||||||
|
- Added `pigz` support in `taz`.
|
||||||
|
|
||||||
|
### [2.5.0] — 2020-03-03
|
||||||
|
|
||||||
|
- Added `taz` and `rmspc`.
|
||||||
|
- Renamed `auzip` to `utaz` and improved it.
|
||||||
|
|
||||||
|
### [2.4.0] — 2020-03-02
|
||||||
|
|
||||||
|
- Added `auzip`.
|
||||||
|
|
||||||
|
### [2.3.2] — 2020-01-31
|
||||||
|
|
||||||
|
- `figlet`: changed default font to `ansi_shadow`.
|
||||||
|
|
||||||
|
### [2.3.1] — 2020-01-16
|
||||||
|
|
||||||
|
- Bugfix: non-interactive shells were blocked by some functions.
|
||||||
|
|
||||||
|
### [2.3.0] — 2020-01-08
|
||||||
|
|
||||||
|
- Added `figlet` and `neofetch` as MOTD replacement.
|
||||||
|
|
||||||
|
### [2.2.0] — 2019-12-16
|
||||||
|
|
||||||
|
- Added `showinfo`.
|
||||||
|
- First implementation of `showdiskmap`.
|
||||||
|
|
||||||
|
### [2.1.2] — 2019-09-24
|
||||||
|
|
||||||
|
- Bugfix in profile version display.
|
||||||
|
|
||||||
|
### [2.1.1] — 2019-09-23
|
||||||
|
|
||||||
|
- Bugfix in `dpkgs`.
|
||||||
|
|
||||||
|
### [2.1.0] — 2018-09-16
|
||||||
|
|
||||||
|
- Added `rmhost`, `setc`, `setfr`.
|
||||||
|
- Improved locale management.
|
||||||
|
|
||||||
|
### [2.0.1] — 2017-02-04
|
||||||
|
|
||||||
|
- `clean` improvements (`--shell`).
|
||||||
|
|
||||||
|
### [2.0.0] — 2015-10-24
|
||||||
|
|
||||||
|
- Added advanced functions (`clean`, `ssr`, etc.).
|
||||||
|
|
||||||
|
### [1.0.0] — 2013-02-16
|
||||||
|
|
||||||
|
- Initial version.
|
||||||
|
|
||||||
|
### [Initial fork]
|
||||||
|
|
||||||
|
Forked default Bash profile from Beyond Linux From Scratch by:
|
||||||
|
|
||||||
|
- James Robertson <jameswrobertson@earthlink.net>
|
||||||
|
- Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ to target). Stale forks cause avoidable merge conflicts.
|
|||||||
**New functionality** must always target `master`.
|
**New functionality** must always target `master`.
|
||||||
|
|
||||||
**Bugfixes** must target the branch where the bug was introduced:
|
**Bugfixes** must target the branch where the bug was introduced:
|
||||||
|
|
||||||
- If the bug exists in a released version, open the fix against that version's
|
- If the bug exists in a released version, open the fix against that version's
|
||||||
maintenance branch first, then cherry-pick onto `master`.
|
maintenance branch first, then cherry-pick onto `master`.
|
||||||
- If the bug is only in `master` (unreleased), fix it directly on `master`.
|
- If the bug is only in `master` (unreleased), fix it directly on `master`.
|
||||||
@@ -71,6 +72,7 @@ Any experimental version must have it's dedicated branch.
|
|||||||
---
|
---
|
||||||
|
|
||||||
## 5. Development environment
|
## 5. Development environment
|
||||||
|
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Bash | 4.3 | Namerefs (`local -n`) required |
|
| Bash | 4.3 | Namerefs (`local -n`) required |
|
||||||
| shellcheck | any recent | Run before every commit |
|
| shellcheck | any recent | Run before every commit |
|
||||||
@@ -78,6 +80,7 @@ Any experimental version must have it's dedicated branch.
|
|||||||
| bats-core | 1.x | Optional — for running the test suite |
|
| bats-core | 1.x | Optional — for running the test suite |
|
||||||
|
|
||||||
Install shellcheck:
|
Install shellcheck:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Debian / Ubuntu
|
# Debian / Ubuntu
|
||||||
apt-get install shellcheck
|
apt-get install shellcheck
|
||||||
@@ -94,6 +97,7 @@ brew install shellcheck
|
|||||||
## 6. Code style
|
## 6. Code style
|
||||||
|
|
||||||
### General rules
|
### General rules
|
||||||
|
|
||||||
- **Bash only** — no external interpreters in core modules. Python or Perl is
|
- **Bash only** — no external interpreters in core modules. Python or Perl is
|
||||||
acceptable for completely self-contained, optional utilities that have no
|
acceptable for completely self-contained, optional utilities that have no
|
||||||
dependencies beyond a minimal Debian or CentOS installation.
|
dependencies beyond a minimal Debian or CentOS installation.
|
||||||
@@ -109,6 +113,7 @@ brew install shellcheck
|
|||||||
`${VAR:-default}` and document the key in `profile.conf` and `README.md §4`.
|
`${VAR:-default}` and document the key in `profile.conf` and `README.md §4`.
|
||||||
|
|
||||||
### Function conventions
|
### Function conventions
|
||||||
|
|
||||||
- Public functions **must** be exported: `export -f funcname`.
|
- Public functions **must** be exported: `export -f funcname`.
|
||||||
- Every public function **must** support `-h` / `--help` and print usage to
|
- Every public function **must** support `-h` / `--help` and print usage to
|
||||||
stdout, returning 0.
|
stdout, returning 0.
|
||||||
@@ -120,6 +125,7 @@ brew install shellcheck
|
|||||||
to prevent collisions with caller-scope variables.
|
to prevent collisions with caller-scope variables.
|
||||||
|
|
||||||
### Module structure
|
### Module structure
|
||||||
|
|
||||||
Every new module should follow this pattern:
|
Every new module should follow this pattern:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -172,20 +178,25 @@ comment explaining why the suppression is necessary.
|
|||||||
## 10. Submitting a contribution
|
## 10. Submitting a contribution
|
||||||
|
|
||||||
### Via Git (preferred)
|
### Via Git (preferred)
|
||||||
|
|
||||||
1. Contact the maintainer to obtain push access, or fork on the Gitea instance.
|
1. Contact the maintainer to obtain push access, or fork on the Gitea instance.
|
||||||
2. Create a branch: `git checkout -b feature/my-feature`.
|
2. Create a branch: `git checkout -b feature/my-feature`.
|
||||||
3. Commit with a clear subject line: `module: short description (≤ 72 chars)`.
|
3. Commit with a clear subject line: `module: short description (≤ 72 chars)`.
|
||||||
4. Push and open a pull request against `master`.
|
4. Push and open a pull request against `master`.
|
||||||
|
|
||||||
### Via patch
|
### Via patch
|
||||||
|
|
||||||
If you do not have push access:
|
If you do not have push access:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git format-patch origin/master
|
git format-patch origin/master
|
||||||
```
|
```
|
||||||
|
|
||||||
Send the resulting `.patch` file(s) to
|
Send the resulting `.patch` file(s) to
|
||||||
`fatalerrors <at> geoffray-levasseur <dot> org`.
|
`fatalerrors <at> geoffray-levasseur <dot> org`.
|
||||||
|
|
||||||
### Commit message format
|
### Commit message format
|
||||||
|
|
||||||
```
|
```
|
||||||
module: imperative short description
|
module: imperative short description
|
||||||
|
|
||||||
@@ -197,7 +208,7 @@ Reference issue numbers if applicable: closes #42.
|
|||||||
|
|
||||||
## 11. What will be rejected
|
## 11. What will be rejected
|
||||||
|
|
||||||
- Code requiring packages not in a minimal Debian or CentOS install.
|
- Code requiring packages not in a minimal Debian or CentOS install, unless optionnal.
|
||||||
- Use of `eval`, `source`-based config loading, or other code-injection vectors.
|
- Use of `eval`, `source`-based config loading, or other code-injection vectors.
|
||||||
- Changes that break Bash 4.3 compatibility.
|
- Changes that break Bash 4.3 compatibility.
|
||||||
- Patches without a passing `shellcheck` run.
|
- Patches without a passing `shellcheck` run.
|
||||||
|
|||||||
230
doc/FAQ.md
230
doc/FAQ.md
@@ -4,15 +4,50 @@
|
|||||||
|
|
||||||
## Installation & loading
|
## Installation & loading
|
||||||
|
|
||||||
|
**Q: How do I install profile automatically into my shell startup files?**
|
||||||
|
|
||||||
|
Run the installer directly (no need to source first):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash <installpath>/profile/profile.sh --install
|
||||||
|
```
|
||||||
|
|
||||||
|
This appends the required `source` line to both `~/.bashrc` and `~/.profile`.
|
||||||
|
To target only one file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash <installpath>/profile/profile.sh --install --bashrc
|
||||||
|
bash <installpath>/profile/profile.sh --install --profile
|
||||||
|
```
|
||||||
|
|
||||||
|
The operation is idempotent — running it again will not add a duplicate line.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q: I ran `profile.sh` directly and got a warning about sourcing.**
|
||||||
|
|
||||||
|
profile.sh is designed to be *sourced*, not executed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source <installpath>/profile/profile.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The only exception is `--install`, which must be passed to a direct execution
|
||||||
|
(`bash profile.sh --install`) to set up the sourcing line automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Q: profile refuses to load and prints "This profile requires Bash 4.3 or higher."**
|
**Q: profile refuses to load and prints "This profile requires Bash 4.3 or higher."**
|
||||||
|
|
||||||
Your system's default shell is an older Bash (common on macOS, which ships
|
Your system's default shell is an older Bash (common on macOS, which ships
|
||||||
Bash 3.x for licensing reasons). Install a newer Bash:
|
Bash 3.x for licensing reasons). Install a newer Bash:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# macOS
|
# macOS
|
||||||
brew install bash
|
brew install bash
|
||||||
# then add /opt/homebrew/bin/bash to /etc/shells and chsh
|
# then add /opt/homebrew/bin/bash to /etc/shells and chsh
|
||||||
```
|
```
|
||||||
|
|
||||||
Or point your terminal emulator at the newer binary explicitly.
|
Or point your terminal emulator at the newer binary explicitly.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -25,13 +60,44 @@ scripts start with `#!/usr/bin/env bash`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Q: Can I use profile functions in scripts?**
|
||||||
|
|
||||||
|
Yes. The supported way is to source `profile.sh` from a Bash script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
source /path/to/profile/profile.sh
|
||||||
|
|
||||||
|
taz -p auto -f lz mydir
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also source one module directly (for example
|
||||||
|
`profile.d/compress.sh`) if you only need a subset of functions.
|
||||||
|
|
||||||
|
When you source a module directly, profile configuration parsing/loading from
|
||||||
|
`profile.sh` is skipped, so defaults from `profile.conf` are not applied unless
|
||||||
|
your script loads them explicitly.
|
||||||
|
|
||||||
|
`profile.sh` also detects whether the current shell is interactive. In
|
||||||
|
non-interactive shells (typical script execution), interactive-only features
|
||||||
|
are intentionally disabled: prompt setup, aliases, welcome/info messages, and
|
||||||
|
startup update checks are not enabled.
|
||||||
|
|
||||||
|
In all cases, avoid aliases in scripts. Use real commands/functions instead,
|
||||||
|
because alias expansion is interactive-shell oriented and can be disabled or
|
||||||
|
behave differently in non-interactive execution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Q: I set `PROFILE_PATH` but profile still can't find its modules.**
|
**Q: I set `PROFILE_PATH` but profile still can't find its modules.**
|
||||||
|
|
||||||
`PROFILE_PATH` must be exported *before* you source `profile.sh`:
|
`PROFILE_PATH` must be exported *before* you source `profile.sh`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export PROFILE_PATH=/opt/profile
|
export PROFILE_PATH=/opt/profile
|
||||||
source /opt/profile/profile.sh
|
source /opt/profile/profile.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
If set after sourcing, `MYPATH` is already locked in and the variable has
|
If set after sourcing, `MYPATH` is already locked in and the variable has
|
||||||
no effect.
|
no effect.
|
||||||
|
|
||||||
@@ -65,6 +131,7 @@ See also `README.md §4.2` for a consolidated table.
|
|||||||
**Q: A key I set in `profile.conf` is being ignored.**
|
**Q: A key I set in `profile.conf` is being ignored.**
|
||||||
|
|
||||||
Check that:
|
Check that:
|
||||||
|
|
||||||
1. The key is inside the correct `[section]` header.
|
1. The key is inside the correct `[section]` header.
|
||||||
2. There is no leading space before the section name (`[section]` not
|
2. There is no leading space before the section name (`[section]` not
|
||||||
`[ section ]`).
|
`[ section ]`).
|
||||||
@@ -74,17 +141,42 @@ Check that:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Q: How do I force or auto-detect the correct `TERM` value?**
|
||||||
|
|
||||||
|
Set `TERM` in the `[system]` section of `profile.conf`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[system]
|
||||||
|
# Auto-detect best available terminfo entry at startup (default)
|
||||||
|
#TERM=smart
|
||||||
|
|
||||||
|
# Force a specific entry
|
||||||
|
#TERM=xterm-256color
|
||||||
|
```
|
||||||
|
|
||||||
|
When `TERM` is absent or set to `smart`, `term_set` probes your terminal
|
||||||
|
emulator's environment variables (`COLORTERM`, `TERM_PROGRAM`, `VTE_VERSION`,
|
||||||
|
`WT_SESSION`, `TMUX`, `STY`) and then tests terminfo entries in preference
|
||||||
|
order. If you are experiencing display issues, run `term_set` interactively
|
||||||
|
and check the result with `echo $TERM`. See *Prompt & theming* below for
|
||||||
|
symptoms caused by a wrong `TERM` value.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Prompt & theming
|
## Prompt & theming
|
||||||
|
|
||||||
**Q: How do I change the prompt theme?**
|
**Q: How do I change the prompt theme?**
|
||||||
|
|
||||||
Add to `profile.conf`:
|
Add to `profile.conf`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[prompt]
|
[prompt]
|
||||||
PROMPT_THEME = dark
|
PROMPT_THEME = dark
|
||||||
```
|
```
|
||||||
|
|
||||||
Built-in names: `default`, `dark`, `light`, `solarized`, `solarized-light`,
|
Built-in names: `default`, `dark`, `light`, `solarized`, `solarized-light`,
|
||||||
`monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`.
|
`monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`, but you can create your
|
||||||
|
own theme.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -92,16 +184,45 @@ Built-in names: `default`, `dark`, `light`, `solarized`, `solarized-light`,
|
|||||||
|
|
||||||
Those themes use 24-bit / true-colour ANSI sequences (`\e[38;2;R;G;Bm`).
|
Those themes use 24-bit / true-colour ANSI sequences (`\e[38;2;R;G;Bm`).
|
||||||
Test your terminal:
|
Test your terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
printf '\e[38;2;38;139;210mTrue colour test\e[0m\n'
|
printf '\e[38;2;38;139;210mTrue colour test\e[0m\n'
|
||||||
```
|
```
|
||||||
|
|
||||||
If you see a solid blue word your terminal supports true colour.
|
If you see a solid blue word your terminal supports true colour.
|
||||||
If you see garbage or plain text, switch to a 16-colour theme
|
If you see garbage or plain text, switch to a 16-colour theme
|
||||||
(`dark`, `default`, etc.) or upgrade your terminal emulator.
|
(`dark`, `default`, etc.) or upgrade your terminal emulator.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Q: I created a custom theme but `load_theme` emits "key not allowed" warnings.**
|
**Q: My prompt colours are missing, wrong, or ANSI escape codes appear as raw text.**
|
||||||
|
|
||||||
|
The most common cause is a `TERM` value that does not match what your terminal
|
||||||
|
emulator actually supports — either it points to an entry that does not exist
|
||||||
|
in the local `terminfo` database, or it under-describes the real capabilities
|
||||||
|
(e.g. `TERM=xterm` when the emulator supports 256 colours).
|
||||||
|
|
||||||
|
Diagnose with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "TERM=$TERM"
|
||||||
|
tput colors # should print 256 (or higher) for a capable terminal
|
||||||
|
infocmp | head -5 # verify the loaded terminfo entry looks sane
|
||||||
|
```
|
||||||
|
|
||||||
|
Common scenarios and fixes:
|
||||||
|
|
||||||
|
| Symptom | Likely cause | Fix |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `tput: unknown terminal` | `TERM` value has no terminfo entry | Remove the forced value and let `TERM=smart` auto-detect |
|
||||||
|
| Only 8 colours instead of 256 | `TERM=xterm` instead of `xterm-256color` | Set `TERM=smart` or force `xterm-256color` |
|
||||||
|
| Colours correct in plain shell but wrong inside tmux | tmux overwrites `TERM` | Set `TERM=smart` — `term_set` picks `tmux-256color` |
|
||||||
|
| Colours correct outside screen but wrong inside it | Same, for GNU screen | Set `TERM=smart` — `term_set` picks `screen-256color` |
|
||||||
|
| Prompt renders correctly but `rain` / `matrix` shows garbage | Terminal doesn't support the ANSI codes implied by `TERM` | Match `TERM` to the real emulator capability |
|
||||||
|
|
||||||
|
The recommended approach is to leave `TERM` unset (or set it to `smart`) in
|
||||||
|
`profile.conf` and let `term_set` choose the best available entry automatically.
|
||||||
|
Only force a specific value if auto-detection picks the wrong one.
|
||||||
|
|
||||||
Theme files are parsed, not executed. Only `PROMPT_COLOR_*` keys and the
|
Theme files are parsed, not executed. Only `PROMPT_COLOR_*` keys and the
|
||||||
standard colour variable names from `disp.sh` (`Black`, `Blue`, `On_IBlack`,
|
standard colour variable names from `disp.sh` (`Black`, `Blue`, `On_IBlack`,
|
||||||
@@ -119,12 +240,66 @@ theme file cannot execute code. Values must be a colour variable reference
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Git helpers
|
||||||
|
|
||||||
|
**Q: What git helper functions does profile provide?**
|
||||||
|
|
||||||
|
All git helpers are defined in `profile.d/git.sh` (new in 4.1.0):
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `gst` | Compact status with branch tracking info |
|
||||||
|
| `ggraph` | Decorated history graph |
|
||||||
|
| `gsync` | Fetch and rebase onto upstream |
|
||||||
|
| `gacp` | Add, commit and push in one command |
|
||||||
|
| `greset` | Reset to upstream, stashing local changes first |
|
||||||
|
| `gwip` | Quick WIP checkpoint commit |
|
||||||
|
| `gprune` | Delete merged local branches |
|
||||||
|
| `groot` | Print or cd to repository root |
|
||||||
|
|
||||||
|
All commands accept `-h` / `--help`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q: Tab completion for `gacp` does not show modified files.**
|
||||||
|
|
||||||
|
Profile ships dedicated completions in `profile.d/bash-completion/git-completion.sh`,
|
||||||
|
loaded automatically in interactive sessions. If completions are missing,
|
||||||
|
check that the system git completion is installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debian / Ubuntu
|
||||||
|
apt-get install bash-completion
|
||||||
|
# Fedora / RHEL
|
||||||
|
dnf install bash-completion
|
||||||
|
```
|
||||||
|
|
||||||
|
When the native git completion helpers are available, `gacp` path completion
|
||||||
|
behaves exactly like `git add` (modified files, untracked files, directories).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q: `gacp` says "No files specified" even though I passed `-a`.**
|
||||||
|
|
||||||
|
The `-a` / `--auto` flag adds all modified files (equivalent to `git add -A`).
|
||||||
|
The default depends on `GIT_GACP_AUTO_ADD` in `profile.conf`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[git]
|
||||||
|
GIT_GACP_AUTO_ADD = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Set to `1` to make `-a` the default.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
**Q: `meteo` prints "No city specified" even though I set a default.**
|
**Q: `meteo` prints "No city specified" even though I set a default.**
|
||||||
|
|
||||||
The key is `METEO_DEFAULT_CITY` (not `DEFAULT_CITY`), and it must be in the
|
The key is `METEO_DEFAULT_CITY` (not `DEFAULT_CITY`), and it must be in the
|
||||||
`[info]` section:
|
`[info]` section:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[info]
|
[info]
|
||||||
METEO_DEFAULT_CITY = Paris
|
METEO_DEFAULT_CITY = Paris
|
||||||
@@ -136,6 +311,7 @@ METEO_DEFAULT_CITY = Paris
|
|||||||
|
|
||||||
`dwl` requires one of `curl`, `wget`, or `fetch` to be installed.
|
`dwl` requires one of `curl`, `wget`, or `fetch` to be installed.
|
||||||
Install curl:
|
Install curl:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Debian / Ubuntu
|
# Debian / Ubuntu
|
||||||
apt-get install curl
|
apt-get install curl
|
||||||
@@ -143,15 +319,55 @@ apt-get install curl
|
|||||||
# Fedora / RHEL
|
# Fedora / RHEL
|
||||||
dnf install curl
|
dnf install curl
|
||||||
```
|
```
|
||||||
|
|
||||||
Or set `DWL_PREFERRED_TOOL` in `[net]` to whichever tool you have.
|
Or set `DWL_PREFERRED_TOOL` in `[net]` to whichever tool you have.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**Q: How do I limit how long `dwl` waits for a download?**
|
||||||
|
|
||||||
|
Use the `-t` / `--timeout` option:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dwl -t 5 https://example.com/file.txt /tmp/file.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
This sets a 5-second cap on both the connection and the overall transfer.
|
||||||
|
The timeout is propagated to `curl` (`--max-time` + `--connect-timeout`),
|
||||||
|
`wget` (`--timeout`), or `fetch` (`-T`) transparently.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q: The prompt takes a long time to appear when my network is unavailable.**
|
||||||
|
|
||||||
|
Fixed in 4.1.0. `check_updates -q` (called at startup) now enforces a
|
||||||
|
3-second network timeout. If the update server is unreachable the check
|
||||||
|
fails silently and the prompt appears immediately.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q: `help` only shows a list of functions. Can I get usage for a specific one?**
|
||||||
|
|
||||||
|
Yes — pass the command name as an argument:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
help gacp
|
||||||
|
help dwl
|
||||||
|
help taz
|
||||||
|
```
|
||||||
|
|
||||||
|
This calls `<command> --help` and prints the full usage for that function.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Q: `pkgs` does not find packages I know are installed.**
|
**Q: `pkgs` does not find packages I know are installed.**
|
||||||
|
|
||||||
`pkgs` delegates to `dpkg -l` (Debian/Ubuntu) or `rpm -qa` (RHEL/Fedora).
|
`pkgs` uses `get_pkgmgr` to detect the active package manager and delegates
|
||||||
If your distribution uses a different package manager (pacman, apk, brew …)
|
to the appropriate tool. Supported families: `apt` (Debian/Ubuntu),
|
||||||
it is not yet supported. See `doc/todo.md` for the tracking issue.
|
`dnf` / `yum` (RHEL/Fedora), `zypper` (openSUSE), `pacman` (Arch),
|
||||||
|
`apk` (Alpine), `portage` (Gentoo), `xbps` (Void), `nix`, `brew` (macOS).
|
||||||
|
If your distribution is not detected, run `get_pkgmgr` to see what is
|
||||||
|
identified, and check that the package manager binary is in your `PATH`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -160,9 +376,11 @@ it is not yet supported. See `doc/todo.md` for the tracking issue.
|
|||||||
`check_updates` compares the content of the remote `version` file against
|
`check_updates` compares the content of the remote `version` file against
|
||||||
`$PROFVERSION`. If `UPDT_DEFAULT_BRANCH` in `[updates]` points to a different
|
`$PROFVERSION`. If `UPDT_DEFAULT_BRANCH` in `[updates]` points to a different
|
||||||
branch than your installation, the version files may not match. Check:
|
branch than your installation, the version files may not match. Check:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat "$MYPATH/version"
|
cat "$MYPATH/version"
|
||||||
```
|
```
|
||||||
|
|
||||||
and make sure `UPDT_DEFAULT_BRANCH` matches the branch you track.
|
and make sure `UPDT_DEFAULT_BRANCH` matches the branch you track.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -193,6 +411,7 @@ has not been implemented yet.
|
|||||||
```bash
|
```bash
|
||||||
PROFILE_DISABLED=1 bash --norc
|
PROFILE_DISABLED=1 bash --norc
|
||||||
```
|
```
|
||||||
|
|
||||||
Or simply open a shell without sourcing `~/.bashrc` (`bash --norc`).
|
Or simply open a shell without sourcing `~/.bashrc` (`bash --norc`).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -202,6 +421,7 @@ Or simply open a shell without sourcing `~/.bashrc` (`bash --norc`).
|
|||||||
Open an issue on the
|
Open an issue on the
|
||||||
[Gitea tracker](https://git.geoffray-levasseur.org/fatalerrors/profile/issues)
|
[Gitea tracker](https://git.geoffray-levasseur.org/fatalerrors/profile/issues)
|
||||||
or send a mail to `fatalerrors <at> geoffray-levasseur <dot> org` with:
|
or send a mail to `fatalerrors <at> geoffray-levasseur <dot> org` with:
|
||||||
|
|
||||||
- The exact command that triggered the bug
|
- The exact command that triggered the bug
|
||||||
- Your OS and Bash version (`bash --version`)
|
- Your OS and Bash version (`bash --version`)
|
||||||
- The module involved
|
- The module involved
|
||||||
|
|||||||
45
doc/known-bugs.md
Executable file
45
doc/known-bugs.md
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
# Known bugs
|
||||||
|
|
||||||
|
This document tracks currently known issues and limitations.
|
||||||
|
|
||||||
|
## Open issues
|
||||||
|
|
||||||
|
- None :-)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Won't fix
|
||||||
|
|
||||||
|
These issues are caused by platform or environment limitations outside the scope of this
|
||||||
|
project and will not be addressed in Bash.
|
||||||
|
|
||||||
|
### Prompt execution time is inaccurate in Windows Terminal (WSL)
|
||||||
|
|
||||||
|
- **Description:** In Windows Terminal the displayed duration includes idle and typing time,
|
||||||
|
and is consistently higher than actual command execution time. In a native Linux terminal
|
||||||
|
(including WSL shells inside Konsole, QTerminal, etc.) timing correctly starts on Enter and
|
||||||
|
stops when the prompt reappears; in Windows Terminal, timer events appear tied to prompt
|
||||||
|
display rather than to the Enter keypress.
|
||||||
|
- **Cause:** Execution time is measured via a `DEBUG` trap and `PROMPT_COMMAND` using
|
||||||
|
`date +%s%N` deltas. WSL + Windows Terminal introduces scheduling jitter between Bash signal
|
||||||
|
events and the underlying Windows terminal layer that does not match wall-clock perception.
|
||||||
|
- **Impact:** Cosmetic / observability only — commands execute normally.
|
||||||
|
- **Status:** Not fixable in Bash; this is a limitation of the Windows Terminal / WSL
|
||||||
|
integration layer.
|
||||||
|
- **Workarounds:**
|
||||||
|
- Use a native Linux terminal under WSL (Konsole, QTerminal, Terminator, etc.) to
|
||||||
|
recover the expected Enter→prompt timing behavior.
|
||||||
|
- Use `/usr/bin/time -p <command>` or the shell built-in `time` when accurate timing
|
||||||
|
is required.
|
||||||
|
- Treat prompt timing as an inacurate indicator in this environment.
|
||||||
|
|
||||||
|
### Rain/Matrix rendering is slow on Windows
|
||||||
|
|
||||||
|
- **Description:** The rain, matrix and rainbow terminal effects are significantly slower
|
||||||
|
on Windows, especially with high density settings on every terminal software.
|
||||||
|
- **Cause:** This is due to the way Windows handles terminal display updates, which is
|
||||||
|
inherently less efficient than on Unix-like systems.
|
||||||
|
- **Status:** Not fixable in Bash; this is a limitation of Windows terminal design.
|
||||||
|
- **Workaround:** Lower the density parameter for better performance, or use a Unix-like
|
||||||
|
environment for optimal speed.
|
||||||
@@ -21,8 +21,10 @@ HISTIGNORE="&:[bf]g:exit"
|
|||||||
# Default pager
|
# Default pager
|
||||||
PAGER=less
|
PAGER=less
|
||||||
|
|
||||||
# Terminal colour capability
|
# Terminal type.
|
||||||
TERM=xterm-256color
|
# smart — auto-detect the best available capability at startup (default)
|
||||||
|
# <specific> — force a specific terminfo entry, e.g. xterm-256color or vt100
|
||||||
|
#TERM=smart
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
[compress]
|
[compress]
|
||||||
@@ -30,8 +32,10 @@ TERM=xterm-256color
|
|||||||
# Supported: lz (default), xz, bz2, gz, lzo, tar, zip, zst
|
# Supported: lz (default), xz, bz2, gz, lzo, tar, zip, zst
|
||||||
#TAZ_DEFAULT_FORMAT=lz
|
#TAZ_DEFAULT_FORMAT=lz
|
||||||
|
|
||||||
# taz: Number of compression threads (0 = auto-detect CPU count).
|
# taz: Number of compression threads.
|
||||||
#TAZ_DEFAULT_THREADS=0
|
# auto — detect CPU count at runtime (default)
|
||||||
|
# N — explicit positive integer
|
||||||
|
#TAZ_DEFAULT_THREADS=auto
|
||||||
|
|
||||||
# taz: Compression level 1 (fast/large) … 9 (slow/small).
|
# taz: Compression level 1 (fast/large) … 9 (slow/small).
|
||||||
#TAZ_DEFAULT_LEVEL=6
|
#TAZ_DEFAULT_LEVEL=6
|
||||||
@@ -51,7 +55,8 @@ TERM=xterm-256color
|
|||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
[disp]
|
[disp]
|
||||||
# Uncomment to disable ANSI colours in profile's own output messages.
|
# Uncomment to disable ANSI colours in profile's own output messages. For the
|
||||||
|
# prompt use a non-colored theme in [prompt] section of that file.
|
||||||
#NO_COLOR=1
|
#NO_COLOR=1
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@@ -76,6 +81,36 @@ TERM=xterm-256color
|
|||||||
# busy: Delay between matched lines in milliseconds (0 = no delay).
|
# busy: Delay between matched lines in milliseconds (0 = no delay).
|
||||||
#BUSY_DEFAULT_DELAY=0
|
#BUSY_DEFAULT_DELAY=0
|
||||||
|
|
||||||
|
# fake_compile: Minimum delay between output lines in milliseconds.
|
||||||
|
#FAKE_COMPILE_DEFAULT_MIN_DELAY=40
|
||||||
|
|
||||||
|
# fake_compile: Maximum delay between output lines in milliseconds.
|
||||||
|
#FAKE_COMPILE_DEFAULT_MAX_DELAY=150
|
||||||
|
|
||||||
|
# fake_compile: Default language preset (c, cpp, java, python, random).
|
||||||
|
#FAKE_COMPILE_DEFAULT_LANG=c
|
||||||
|
|
||||||
|
# hack: Minimum delay between output lines in milliseconds.
|
||||||
|
#HACK_DEFAULT_MIN_DELAY=60
|
||||||
|
|
||||||
|
# hack: Maximum delay between output lines in milliseconds.
|
||||||
|
#HACK_DEFAULT_MAX_DELAY=250
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
[git]
|
||||||
|
# Fallback main branch name used when remote HEAD cannot be detected.
|
||||||
|
#GIT_MAIN_BRANCH=main
|
||||||
|
|
||||||
|
# Default remote used by git helper functions.
|
||||||
|
#GIT_DEFAULT_REMOTE=origin
|
||||||
|
|
||||||
|
# Prefix used by gwip when generating automatic checkpoint messages.
|
||||||
|
#GIT_WIP_PREFIX=wip
|
||||||
|
|
||||||
|
# gacp: Automatically add all modified files (git add -A) when no explicit file
|
||||||
|
# list is provided. Set to 0 to require explicit file paths or the -a flag.
|
||||||
|
#GIT_GACP_AUTO_ADD=1
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
[info]
|
[info]
|
||||||
# meteo: Default city when no argument is given. Leave unset to require an
|
# meteo: Default city when no argument is given. Leave unset to require an
|
||||||
@@ -100,6 +135,10 @@ TERM=xterm-256color
|
|||||||
# Unset = auto-detect (curl preferred, then wget, then fetch).
|
# Unset = auto-detect (curl preferred, then wget, then fetch).
|
||||||
#DWL_PREFERRED_TOOL=curl
|
#DWL_PREFERRED_TOOL=curl
|
||||||
|
|
||||||
|
# dwl: Enable resume mode by default (supports curl/wget file downloads).
|
||||||
|
# Values accepted: 1/0, true/false, yes/no, on/off.
|
||||||
|
#DWL_DEFAULT_RESUME=0
|
||||||
|
|
||||||
# myextip: API endpoint for external IP lookup.
|
# myextip: API endpoint for external IP lookup.
|
||||||
# Alternatives: https://ipinfo.io/json, https://ip-api.com/json/
|
# Alternatives: https://ipinfo.io/json, https://ip-api.com/json/
|
||||||
#MYEXTIP_DEFAULT_URL=https://ip-api.com/json/
|
#MYEXTIP_DEFAULT_URL=https://ip-api.com/json/
|
||||||
@@ -154,6 +193,28 @@ TERM=xterm-256color
|
|||||||
#
|
#
|
||||||
# Working directory
|
# Working directory
|
||||||
#PROMPT_COLOR_DIR_FG=$ICyan
|
#PROMPT_COLOR_DIR_FG=$ICyan
|
||||||
|
#
|
||||||
|
# Context segment (Git branch / Conda environment) on the top bar
|
||||||
|
#PROMPT_COLOR_CTX_FG=$BIYellow
|
||||||
|
#
|
||||||
|
# Show Git branch at the end of the top bar when inside a repository.
|
||||||
|
#PROMPT_SHOW_GIT=1
|
||||||
|
#
|
||||||
|
# Include Git dirty marker and upstream drift (+ahead/-behind) in the context.
|
||||||
|
#PROMPT_SHOW_GIT_STATUS=1
|
||||||
|
#
|
||||||
|
# Timeout in seconds for git diff and git rev-list operations.
|
||||||
|
# If exceeded, the prompt displays git:<branch>? instead of full status.
|
||||||
|
#PROMPT_GIT_TIMEOUT=2
|
||||||
|
#
|
||||||
|
# Show Conda environment name at the end of the top bar when active.
|
||||||
|
#PROMPT_SHOW_CONDA=1
|
||||||
|
#
|
||||||
|
# Show Python venv name when active (ignored if Conda is active).
|
||||||
|
#PROMPT_SHOW_VENV=1
|
||||||
|
#
|
||||||
|
# Show session markers (ssh, tmux, screen) when applicable.
|
||||||
|
#PROMPT_SHOW_SESSION=1
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
[pwd]
|
[pwd]
|
||||||
@@ -190,12 +251,24 @@ TERM=xterm-256color
|
|||||||
# rain: Colour theme. Supported: white (default), green, blue, red, yellow, cyan
|
# rain: Colour theme. Supported: white (default), green, blue, red, yellow, cyan
|
||||||
#RAIN_DEFAULT_COLOR=white
|
#RAIN_DEFAULT_COLOR=white
|
||||||
|
|
||||||
|
# rain: Maximum number of simultaneous falling elements.
|
||||||
|
# Leave unset to keep the terminal-size-based dynamic default.
|
||||||
|
#RAIN_DEFAULT_DENSITY=80
|
||||||
|
|
||||||
|
# rainbow: Horizontal color shift speed — integer/100 gives seconds (4 → 0.04 s).
|
||||||
|
# Values < 1 are used as raw seconds.
|
||||||
|
#RAINBOW_DEFAULT_SPEED=4
|
||||||
|
|
||||||
# matrix: Falling speed.
|
# matrix: Falling speed.
|
||||||
#MATRIX_DEFAULT_SPEED=3.5
|
#MATRIX_DEFAULT_SPEED=3.5
|
||||||
|
|
||||||
# matrix: Colour theme. Supported: green (default), blue, red, yellow, cyan, white
|
# matrix: Colour theme. Supported: green (default), blue, red, yellow, cyan, white
|
||||||
#MATRIX_DEFAULT_COLOR=green
|
#MATRIX_DEFAULT_COLOR=green
|
||||||
|
|
||||||
|
# matrix: Maximum number of simultaneous falling elements.
|
||||||
|
# Leave unset to keep the terminal-size-based dynamic default.
|
||||||
|
#MATRIX_DEFAULT_DENSITY=120
|
||||||
|
|
||||||
# matrix: Character set. Supported: binary (default), kana, ascii
|
# matrix: Character set. Supported: binary (default), kana, ascii
|
||||||
#MATRIX_DEFAULT_CHARSET=binary
|
#MATRIX_DEFAULT_CHARSET=binary
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8"
|
|||||||
# Supported values: curl, wget, fetch. Unset uses auto-detection (default).
|
# Supported values: curl, wget, fetch. Unset uses auto-detection (default).
|
||||||
#DWL_PREFERRED_TOOL=curl
|
#DWL_PREFERRED_TOOL=curl
|
||||||
|
|
||||||
|
# dwl: Enable resume mode by default (supports curl/wget file downloads).
|
||||||
|
# Accepted values: 1/0, true/false, yes/no, on/off.
|
||||||
|
#DWL_DEFAULT_RESUME=0
|
||||||
|
|
||||||
# myextip: API endpoint URL used to retrieve external IP information.
|
# myextip: API endpoint URL used to retrieve external IP information.
|
||||||
# Default: https://ip-api.com/json/
|
# Default: https://ip-api.com/json/
|
||||||
# Compatible alternatives: https://ipinfo.io/json, https://ip-api.com/json/
|
# Compatible alternatives: https://ipinfo.io/json, https://ip-api.com/json/
|
||||||
@@ -191,6 +195,10 @@ SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8"
|
|||||||
# Supported values: white (default), green, blue, red, yellow, cyan
|
# Supported values: white (default), green, blue, red, yellow, cyan
|
||||||
#RAIN_DEFAULT_COLOR=white
|
#RAIN_DEFAULT_COLOR=white
|
||||||
|
|
||||||
|
# rain: Maximum number of simultaneous falling elements.
|
||||||
|
# Leave unset to keep the terminal-size-based dynamic default.
|
||||||
|
#RAIN_DEFAULT_DENSITY=80
|
||||||
|
|
||||||
# matrix: Default speed value, using the /100 scale (3.5 => 0.035s).
|
# matrix: Default speed value, using the /100 scale (3.5 => 0.035s).
|
||||||
#MATRIX_DEFAULT_SPEED=3.5
|
#MATRIX_DEFAULT_SPEED=3.5
|
||||||
|
|
||||||
@@ -198,6 +206,10 @@ SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8"
|
|||||||
# Supported values: green (default), blue, red, yellow, cyan, white
|
# Supported values: green (default), blue, red, yellow, cyan, white
|
||||||
#MATRIX_DEFAULT_COLOR=green
|
#MATRIX_DEFAULT_COLOR=green
|
||||||
|
|
||||||
|
# matrix: Maximum number of simultaneous falling elements.
|
||||||
|
# Leave unset to keep the terminal-size-based dynamic default.
|
||||||
|
#MATRIX_DEFAULT_DENSITY=120
|
||||||
|
|
||||||
# matrix: Default character set.
|
# matrix: Default character set.
|
||||||
# Supported values: binary (default), kana, ascii
|
# Supported values: binary (default), kana, ascii
|
||||||
MATRIX_DEFAULT_CHARSET=kana
|
MATRIX_DEFAULT_CHARSET=kana
|
||||||
|
|||||||
41
doc/todo.md
41
doc/todo.md
@@ -12,7 +12,7 @@ version-bump.
|
|||||||
blockers are `local -A` (no associative arrays in ZSH without `typeset -A`)
|
blockers are `local -A` (no associative arrays in ZSH without `typeset -A`)
|
||||||
and `local -n` namerefs. A thin compatibility shim would open the project to
|
and `local -n` namerefs. A thin compatibility shim would open the project to
|
||||||
ZSH users. **[hard]**
|
ZSH users. **[hard]**
|
||||||
- [ ] **Bash completion** — add a `profile.d/completion/` directory and write
|
- [ ] **Bash completion** — add a more bash completion directory and write
|
||||||
`_profile_upgrade`, `_taz`, `_utaz`, `_meteo`, etc. completions so that
|
`_profile_upgrade`, `_taz`, `_utaz`, `_meteo`, etc. completions so that
|
||||||
`<Tab>` works on all public functions. **[medium]**
|
`<Tab>` works on all public functions. **[medium]**
|
||||||
|
|
||||||
@@ -20,18 +20,21 @@ version-bump.
|
|||||||
|
|
||||||
## Prompt & theming
|
## Prompt & theming
|
||||||
|
|
||||||
- [ ] **Git branch in prompt** — show the current branch name (and dirty
|
- [x] **Git branch in prompt** — show the current branch name (with dirty and
|
||||||
indicator) in the PS1 bar when inside a Git repository. Should be
|
upstream drift indicators) in the PS1 bar when inside a Git repository,
|
||||||
gated behind a `[prompt]` config key so it can be disabled. **[medium]**
|
gated by `[prompt]` config keys. **[medium]**
|
||||||
- [ ] **Virtual-env / conda indicator** — detect `$VIRTUAL_ENV` / `$CONDA_DEFAULT_ENV`
|
- [x] **Virtual-env / conda indicator** — detect `$VIRTUAL_ENV` / `$CONDA_DEFAULT_ENV`
|
||||||
and display the name in the prompt bar. **[easy]**
|
and display the active environment in the prompt bar. **[easy]**
|
||||||
- [ ] **True-colour terminal auto-detection** — query `$COLORTERM` and
|
- [x] **Session context markers** — display lightweight session markers
|
||||||
|
(`ssh`, `tmux`, `screen`) at the end of the prompt bar, gated by
|
||||||
|
`[prompt]` config keys. **[easy]**
|
||||||
|
- [x] **True-colour terminal auto-detection** — query `$COLORTERM` and
|
||||||
`$TERM` at load time; automatically fall back from a 24-bit theme to its
|
`$TERM` at load time; automatically fall back from a 24-bit theme to its
|
||||||
16-colour equivalent when the terminal does not support true colour. **[medium]**
|
16-colour equivalent when the terminal does not support true colour. **[medium]**
|
||||||
- [ ] **True-colour variants of other themes** — create `monokai-tc.theme`,
|
- [ ] **True-colour variants of other themes** — create `monokai-tc.theme`,
|
||||||
`abyss-tc.theme`, etc. using the same `\e[38;2;R;G;Bm` approach as the
|
`abyss-tc.theme`, etc. using the same `\e[38;2;R;G;Bm` approach as the
|
||||||
Solarized themes. **[easy]** _(per theme)_
|
Solarized themes. **[easy]** _(per theme)_
|
||||||
- [ ] **Theme preview command** — add a `theme_preview` (or `profile_theme`)
|
- [X] **Theme preview command** — add a `theme_preview` (or `profile_theme`)
|
||||||
function that renders a colour swatch and a sample prompt line for the
|
function that renders a colour swatch and a sample prompt line for the
|
||||||
currently loaded theme, so users can evaluate themes without reloading
|
currently loaded theme, so users can evaluate themes without reloading
|
||||||
the session. **[medium]**
|
the session. **[medium]**
|
||||||
@@ -44,46 +47,54 @@ version-bump.
|
|||||||
## Module improvements
|
## Module improvements
|
||||||
|
|
||||||
### compress
|
### compress
|
||||||
|
|
||||||
- [ ] **`taz` progress bar** — show a `pv` / `dd`-based progress indicator when
|
- [ ] **`taz` progress bar** — show a `pv` / `dd`-based progress indicator when
|
||||||
compressing large trees, gated behind a `-p` flag. **[medium]**
|
compressing large trees, gated behind a `-p` flag. **[medium]**
|
||||||
- [ ] **`utaz` integrity check** — run `tar -tOf` / `unzip -t` / `7z t` before
|
- [ ] **`utaz` integrity check** — run `tar -tOf` / `unzip -t` / `7z t` before
|
||||||
extracting and abort if the archive is corrupt. **[easy]**
|
extracting and abort if the archive is corrupt. **[easy]**
|
||||||
|
|
||||||
### filefct
|
### filefct
|
||||||
- [ ] **`findbig` / `findzero` / `finddead` — `fd` integration** — optionally
|
|
||||||
|
- [x] **`findbig` / `findzero` / `finddead` — `fd` integration** — optionally
|
||||||
use `fd` instead of `find` when available for faster traversal. **[easy]**
|
use `fd` instead of `find` when available for faster traversal. **[easy]**
|
||||||
- [ ] **`file_stats` — human-readable totals** — add `--human` flag to emit
|
- [x] **`file_stats` — human-readable totals** — add `--human` flag to emit
|
||||||
sizes in K/M/G instead of bytes. **[easy]**
|
sizes in K/M/G instead of bytes. **[easy]**
|
||||||
|
|
||||||
### info
|
### info
|
||||||
- [ ] **`showinfo` fallback** — when neither `neofetch` nor `fastfetch` is
|
|
||||||
|
- [X] **`showinfo` fallback** — when neither `neofetch` nor `fastfetch` is
|
||||||
installed, print a minimal sysinfo block (hostname, OS, kernel, uptime,
|
installed, print a minimal sysinfo block (hostname, OS, kernel, uptime,
|
||||||
CPU, RAM) using pure Bash + `/proc`. **[medium]**
|
CPU, RAM) using pure Bash + `/proc`. **[medium]**
|
||||||
|
|
||||||
### net
|
### net
|
||||||
- [ ] **`dwl` resume support** — pass `-C -` to curl / `--continue-at -` to
|
|
||||||
|
- [X] **`dwl` resume support** — pass `-C -` to curl / `--continue-at -` to
|
||||||
wget for interrupted downloads; gate behind a `-r` flag. **[easy]**
|
wget for interrupted downloads; gate behind a `-r` flag. **[easy]**
|
||||||
- [ ] **`myextip` multiple providers** — fall back to a secondary URL
|
- [ ] **`myextip` multiple providers** — fall back to a secondary URL
|
||||||
(configurable via `MYEXTIP_FALLBACK_URL`) when the primary times out.
|
(configurable via `MYEXTIP_FALLBACK_URL`) when the primary times out.
|
||||||
**[easy]**
|
**[easy]**
|
||||||
|
|
||||||
### processes
|
### processes
|
||||||
- [ ] **`ku` dry-run flag** — add `-n` / `--dry-run` to print what would be
|
|
||||||
|
- [X] **`ku` dry-run flag** — add `-n` / `--dry-run` to print what would be
|
||||||
killed without acting. **[easy]**
|
killed without acting. **[easy]**
|
||||||
|
|
||||||
### pwd
|
### pwd
|
||||||
|
|
||||||
- [ ] **`genpwd` passphrase mode** — add `-w` / `--words N` to generate
|
- [ ] **`genpwd` passphrase mode** — add `-w` / `--words N` to generate
|
||||||
word-based passphrases (diceware-style) from `/usr/share/dict/words`.
|
word-based passphrases (diceware-style) from `/usr/share/dict/words`.
|
||||||
**[medium]**
|
**[medium]**
|
||||||
|
|
||||||
### ssh
|
### ssh
|
||||||
|
|
||||||
- [ ] **SSH agent management** — add `ssh_agent_start` / `ssh_agent_stop` helpers
|
- [ ] **SSH agent management** — add `ssh_agent_start` / `ssh_agent_stop` helpers
|
||||||
that start a persistent `ssh-agent`, add configured keys, and survive
|
that start a persistent `ssh-agent`, add configured keys, and survive
|
||||||
re-login via a socket stored in `~/.ssh/agent.env`. **[medium]**
|
re-login via a socket stored in `~/.ssh/agent.env`. **[medium]**
|
||||||
- [ ] **`rmhost` glob support** — allow `rmhost '*.example.com'` to remove all
|
- [x] **`rmhost` glob support** — allow `rmhost '*.example.com'` to remove all
|
||||||
matching entries in one call. **[easy]**
|
matching entries in one call. **[easy]**
|
||||||
|
|
||||||
### updates
|
### updates
|
||||||
|
|
||||||
- [ ] **Automatic update check age** — store a timestamp in `~/.cache/profile_last_check`;
|
- [ ] **Automatic update check age** — store a timestamp in `~/.cache/profile_last_check`;
|
||||||
skip the network request in `check_updates -q` if the last check was less
|
skip the network request in `check_updates -q` if the last check was less
|
||||||
than `UPDT_CHECK_INTERVAL` hours ago (configurable, default 24). **[medium]**
|
than `UPDT_CHECK_INTERVAL` hours ago (configurable, default 24). **[medium]**
|
||||||
@@ -107,7 +118,7 @@ version-bump.
|
|||||||
- [ ] **`profile_uninstall` function** — remove the `source` line from
|
- [ ] **`profile_uninstall` function** — remove the `source` line from
|
||||||
`~/.bashrc` / `~/.profile` and optionally delete the install directory,
|
`~/.bashrc` / `~/.profile` and optionally delete the install directory,
|
||||||
with a dry-run mode. **[medium]**
|
with a dry-run mode. **[medium]**
|
||||||
- [ ] **`disp` syslog integration** — add a `DISP_SYSLOG=1` config key that
|
- [ ] **`disp` syslog integration** — add a `DISP_SYSLOG=<context>` config key that
|
||||||
additionally pipes E/W messages to `logger`. **[easy]**
|
additionally pipes E/W messages to `logger`. **[easy]**
|
||||||
- [ ] **XDG base-dir support** — honour `$XDG_CONFIG_HOME` as an alternative
|
- [ ] **XDG base-dir support** — honour `$XDG_CONFIG_HOME` as an alternative
|
||||||
location for `profile.conf` so users can keep `~` tidy. **[medium]**
|
location for `profile.conf` so users can keep `~` tidy. **[medium]**
|
||||||
|
|||||||
147
history.txt
147
history.txt
@@ -1,147 +0,0 @@
|
|||||||
------------------------------------------------------------------------------
|
|
||||||
Initial version from Beyond Linux From Scratch by
|
|
||||||
* James Robertson <jameswrobertson@earthlink.net>
|
|
||||||
* Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
Current version from Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
Version history:
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
# 05/03/2026 v3.6.1
|
|
||||||
Fix a typo in compress.sh
|
|
||||||
|
|
||||||
# 05/03/2026 v3.6.0
|
|
||||||
Improved utaz to make it multiformat with lot of it
|
|
||||||
Introduced ppu and ppn
|
|
||||||
Improved update system
|
|
||||||
|
|
||||||
# 04/03/2026 v3.5.0
|
|
||||||
rain has now configurable speed and color
|
|
||||||
showinfo adapted to fastfetch, replacing neofetch
|
|
||||||
|
|
||||||
# 24/02/2022 v3.3.1
|
|
||||||
Fixed version detection
|
|
||||||
Added "busy" function
|
|
||||||
Fixed use of library functions before it's loaded
|
|
||||||
|
|
||||||
# 28/11/2022 v3.3.0
|
|
||||||
Initial version update support
|
|
||||||
Changed versioning code
|
|
||||||
Added installation path detection
|
|
||||||
|
|
||||||
# 28/11/2022 v3.2.3
|
|
||||||
Made proper readme file, to improve
|
|
||||||
|
|
||||||
# 21/11/2022 v3.2.2
|
|
||||||
Fixed taz compression level analysis
|
|
||||||
Fixed typo in dpkgs
|
|
||||||
|
|
||||||
# 20/11/2022 v3.2.1
|
|
||||||
Fix some messages
|
|
||||||
Make dpkgs rpm aware (more to come)
|
|
||||||
Removed version history from main script and revert declaration order
|
|
||||||
Added required license information in all files
|
|
||||||
Completed LICENSE file
|
|
||||||
|
|
||||||
# 18/11/2022 v3.2.0
|
|
||||||
Created disp command for display and make use of it
|
|
||||||
|
|
||||||
# 10/11/2022 v3.1.1
|
|
||||||
genpwd: test if password is doable
|
|
||||||
|
|
||||||
# 08/11/2022 v3.1.0
|
|
||||||
Added password generator
|
|
||||||
|
|
||||||
# 07/11/2022 v3.0.1
|
|
||||||
Added concatenation to rmspc
|
|
||||||
Added ku
|
|
||||||
Error managed in meteo
|
|
||||||
|
|
||||||
# 27/08/2022 v3.0.0
|
|
||||||
Splitted everything in several files
|
|
||||||
Added rain screensaver
|
|
||||||
|
|
||||||
# 29/07/2022 v2.8.2
|
|
||||||
Added warning for non bash or zsh users
|
|
||||||
|
|
||||||
# 19/07/2022 v2.8.1
|
|
||||||
Few cleanups, fixes and optimizations
|
|
||||||
|
|
||||||
# 24/06/2022 v2.8.0
|
|
||||||
Added backtrace, error and settrace
|
|
||||||
[bugfix] corrected showinfo
|
|
||||||
|
|
||||||
# 22/06/2022 v2.7.1
|
|
||||||
[bugfix] few minor corrections
|
|
||||||
Added help command
|
|
||||||
|
|
||||||
# 21/06/2022 v2.7.0
|
|
||||||
Added isipv4 and isipv6 and use it in rmhost as an improvement
|
|
||||||
Removed konsole save and restore not working
|
|
||||||
|
|
||||||
# 18/10/2021 v2.6.3
|
|
||||||
Changed PS1 for status bar style version
|
|
||||||
Few minor improvements
|
|
||||||
|
|
||||||
# 26/02/2021 v2.6.2
|
|
||||||
[bugfix] taz: corrected bug with trailing slash on directories
|
|
||||||
|
|
||||||
# 25/12/2020 v2.6.1
|
|
||||||
Add check on rmhost
|
|
||||||
Improvements rmspc
|
|
||||||
Created expendlist
|
|
||||||
|
|
||||||
# 24/10/2020 v2.6.0
|
|
||||||
Added session save and restore for Konsole
|
|
||||||
|
|
||||||
# 11/09/2020 v2.5.3
|
|
||||||
Few more aliases, improved code consistancy and typo,
|
|
||||||
Improved utaz, removed showdiskmap, removed remaining French,
|
|
||||||
Added license information for future publication
|
|
||||||
|
|
||||||
# 06/03/2020 v2.5.2
|
|
||||||
Few aliases sorted out
|
|
||||||
|
|
||||||
# 05/03/2020 v2.5.1
|
|
||||||
Language consistancy fix
|
|
||||||
Added pigz support in taz
|
|
||||||
|
|
||||||
# 03/03/2020 v2.5.0
|
|
||||||
Added command taz and rmspc
|
|
||||||
Renamed auzip => utaz and improved it
|
|
||||||
|
|
||||||
# 02/03/2020 v2.4.0
|
|
||||||
Added command auzip
|
|
||||||
|
|
||||||
# 31/01/2020 v2.3.2
|
|
||||||
Figlet: changed default font to ansi_shadow
|
|
||||||
|
|
||||||
# 16/01/2020 v2.3.1
|
|
||||||
[bugfix] non-interactive were blocked with some functions
|
|
||||||
|
|
||||||
# 08/01/2020 v2.3.0
|
|
||||||
Added use of figlet and neofetch as a motd replace
|
|
||||||
|
|
||||||
# 16/12/2019 v2.2.0
|
|
||||||
Added showinfo
|
|
||||||
Primary write of showdiskmap
|
|
||||||
|
|
||||||
# 24/09/2019 v2.1.2
|
|
||||||
[bugfix] bug in profile version display
|
|
||||||
|
|
||||||
# 23/09/2019 v2.1.1
|
|
||||||
[bugfix] dpkgs
|
|
||||||
|
|
||||||
# 16/09/2018 v2.1.0
|
|
||||||
Added rmhost, setc, setfr
|
|
||||||
More locales management
|
|
||||||
|
|
||||||
# 04/02/2017 v2.0.1
|
|
||||||
clean improvements (--shell)
|
|
||||||
|
|
||||||
# 24/10/2015 v2.0.0
|
|
||||||
Added advanced functionnalities (clean, srr, etc.)
|
|
||||||
|
|
||||||
# 16/02/2013 v1.0.0
|
|
||||||
Initial version
|
|
||||||
|
|
||||||
220
profile.d/bash-completion/git-completion.sh
Normal file
220
profile.d/bash-completion/git-completion.sh
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Git helper completions for profile.d/git.sh shortcuts.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Return 0 when current directory is inside a git work tree.
|
||||||
|
_profile_git_in_repo()
|
||||||
|
{
|
||||||
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load git completion helpers on demand if they are available on the system.
|
||||||
|
_profile_git_load_completion_helpers()
|
||||||
|
{
|
||||||
|
declare -F __git_complete >/dev/null 2>&1 && return 0
|
||||||
|
|
||||||
|
local completion_file
|
||||||
|
for completion_file in \
|
||||||
|
/usr/share/bash-completion/completions/git \
|
||||||
|
/usr/share/git/completion/git-completion.bash \
|
||||||
|
/etc/bash_completion.d/git
|
||||||
|
do
|
||||||
|
if [[ -r "$completion_file" ]]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$completion_file"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
declare -F __git_complete >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
_profile_git_complete_remotes()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
if ! _profile_git_in_repo; then
|
||||||
|
COMPREPLY=()
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$(git remote 2>/dev/null)" -- "$cur")
|
||||||
|
}
|
||||||
|
|
||||||
|
_profile_git_complete_refs()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
if ! _profile_git_in_repo; then
|
||||||
|
COMPREPLY=()
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$(git for-each-ref --format='%(refname:short)' refs/heads refs/remotes refs/tags 2>/dev/null)" -- "$cur")
|
||||||
|
}
|
||||||
|
|
||||||
|
_profile_git_complete_add_paths()
|
||||||
|
{
|
||||||
|
# shellcheck disable=SC2034 # Used indirectly by git-completion helpers via dynamic scope.
|
||||||
|
local cur words cword prev __git_cmd_idx=0
|
||||||
|
local complete_opt="--others --modified --directory --no-empty-directory"
|
||||||
|
|
||||||
|
if declare -F __git_complete_index_file >/dev/null 2>&1; then
|
||||||
|
if declare -F _get_comp_words_by_ref >/dev/null 2>&1; then
|
||||||
|
_get_comp_words_by_ref -n =: cur words cword prev
|
||||||
|
else
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
if (( COMP_CWORD > 0 )); then
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
else
|
||||||
|
prev=""
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC2034 # Used indirectly by git-completion helpers via dynamic scope.
|
||||||
|
cword="$COMP_CWORD"
|
||||||
|
# shellcheck disable=SC2034 # Used indirectly by git-completion helpers via dynamic scope.
|
||||||
|
words=("${COMP_WORDS[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $(__git_find_on_cmdline "-u --update") ]]; then
|
||||||
|
complete_opt="--modified"
|
||||||
|
fi
|
||||||
|
__git_complete_index_file "$complete_opt"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t COMPREPLY < <(compgen -f -- "${COMP_WORDS[COMP_CWORD]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_gst()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
mapfile -t COMPREPLY < <(compgen -d -- "$cur")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_ggraph()
|
||||||
|
{
|
||||||
|
local cur prev
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
case "$prev" in
|
||||||
|
-n|--limit)
|
||||||
|
COMPREPLY=()
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help -n --limit" -- "$cur")
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_gsync()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_profile_git_complete_remotes
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_gacp()
|
||||||
|
{
|
||||||
|
local cur prev
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
case "$prev" in
|
||||||
|
-m|--message)
|
||||||
|
COMPREPLY=()
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help -a --auto -m --message" -- "$cur")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_profile_git_complete_add_paths
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_greset()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help -x --with-ignored" -- "$cur")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_profile_git_complete_refs
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_gwip()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
|
||||||
|
else
|
||||||
|
COMPREPLY=()
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_gprune()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_profile_git_complete_refs
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_groot()
|
||||||
|
{
|
||||||
|
local cur
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help -g --go" -- "$cur")
|
||||||
|
}
|
||||||
|
|
||||||
|
_profile_git_register_completions()
|
||||||
|
{
|
||||||
|
complete -F _complete_gst gst
|
||||||
|
complete -F _complete_ggraph ggraph
|
||||||
|
complete -F _complete_gsync gsync
|
||||||
|
complete -F _complete_gacp gacp
|
||||||
|
complete -F _complete_greset greset
|
||||||
|
complete -F _complete_gwip gwip
|
||||||
|
complete -F _complete_gprune gprune
|
||||||
|
complete -F _complete_groot groot
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register completions only in interactive bash sessions.
|
||||||
|
if [[ $- == *i* && -n ${BASH_VERSION:-} ]]; then
|
||||||
|
_profile_git_load_completion_helpers >/dev/null 2>&1 || true
|
||||||
|
_profile_git_register_completions
|
||||||
|
fi
|
||||||
|
|
||||||
|
# EOF
|
||||||
54
profile.d/bash-completion/prompt-completion.sh
Executable file
54
profile.d/bash-completion/prompt-completion.sh
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Prompt helper completions for profile.d/prompt.sh shortcuts.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_profile_prompt_complete_theme_names()
|
||||||
|
{
|
||||||
|
local theme_dir="${PROMPT_THEME_DIR:-${MYPATH}/profile.d/themes}"
|
||||||
|
|
||||||
|
[[ -d "$theme_dir" ]] || return 0
|
||||||
|
|
||||||
|
local theme_file theme_names=""
|
||||||
|
for theme_file in "$theme_dir"/*.theme; do
|
||||||
|
[[ -f "$theme_file" ]] || continue
|
||||||
|
theme_names+=" ${theme_file##*/}"
|
||||||
|
theme_names="${theme_names%.theme}"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s\n" "$theme_names"
|
||||||
|
}
|
||||||
|
|
||||||
|
_complete_set_theme()
|
||||||
|
{
|
||||||
|
local cur prev
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
case "$prev" in
|
||||||
|
-h|--help|-l|--list)
|
||||||
|
COMPREPLY=()
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-p|--preview|-S|--save)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$cur" == -* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "-h --help -l --list -p --preview -S --save" -- "$cur")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$cur" == */* || "$cur" == .* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -f -X '!*.theme' -- "$cur")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$(_profile_prompt_complete_theme_names)" -- "$cur")
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $- == *i* && -n ${BASH_VERSION:-} ]]; then
|
||||||
|
complete -F _complete_set_theme set_theme
|
||||||
|
fi
|
||||||
|
|
||||||
|
# EOF
|
||||||
@@ -44,41 +44,49 @@
|
|||||||
# -n, --no-dir Never create a host directory
|
# -n, --no-dir Never create a host directory
|
||||||
utaz()
|
utaz()
|
||||||
{
|
{
|
||||||
|
local _ununzip
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_ununzip()
|
_ununzip()
|
||||||
{
|
{
|
||||||
unzip -o "$1" -d "$2" >/dev/null 2>&1
|
unzip -o "$1" -d "$2" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _untar
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_untar()
|
_untar()
|
||||||
{
|
{
|
||||||
tar -xf "$1" -C "$2"
|
tar -xf "$1" -C "$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _ungzip
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_ungzip()
|
_ungzip()
|
||||||
{
|
{
|
||||||
tar -xzf "$1" -C "$2"
|
tar -xzf "$1" -C "$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _unbzip2
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unbzip2()
|
_unbzip2()
|
||||||
{
|
{
|
||||||
tar -xjf "$1" -C "$2"
|
tar -xjf "$1" -C "$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _unxz
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unxz()
|
_unxz()
|
||||||
{
|
{
|
||||||
tar -xJf "$1" -C "$2"
|
tar -xJf "$1" -C "$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _unlzop
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unlzop()
|
_unlzop()
|
||||||
{
|
{
|
||||||
lzop -d "$1" -o "$2/$(basename "${1%.*}")"
|
lzop -d "$1" -o "$2/$(basename "${1%.*}")"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _unlzip
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unlzip()
|
_unlzip()
|
||||||
{
|
{
|
||||||
@@ -89,18 +97,21 @@ utaz()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _ununrar
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_ununrar()
|
_ununrar()
|
||||||
{
|
{
|
||||||
unrar x -o+ "$1" "$2/" >/dev/null 2>&1
|
unrar x -o+ "$1" "$2/" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _ununarj
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_ununarj()
|
_ununarj()
|
||||||
{
|
{
|
||||||
unarj e "$1" "$2/" >/dev/null 2>&1
|
unarj e "$1" "$2/" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _unlha
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unlha()
|
_unlha()
|
||||||
{
|
{
|
||||||
@@ -109,18 +120,21 @@ utaz()
|
|||||||
(cd "$2" && lha -x "../$1") >/dev/null 2>&1
|
(cd "$2" && lha -x "../$1") >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _ununace
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_ununace()
|
_ununace()
|
||||||
{
|
{
|
||||||
unace x "$1" "$2/" >/dev/null 2>&1
|
unace x "$1" "$2/" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _un7z
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_un7z()
|
_un7z()
|
||||||
{
|
{
|
||||||
7z x "$1" -o"$2/" >/dev/null 2>&1
|
7z x "$1" -o"$2/" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _unzstd
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unzstd()
|
_unzstd()
|
||||||
{
|
{
|
||||||
@@ -128,6 +142,7 @@ utaz()
|
|||||||
tar --zstd -xf "$1" -C "$2"
|
tar --zstd -xf "$1" -C "$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _uncpio
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_uncpio()
|
_uncpio()
|
||||||
{
|
{
|
||||||
@@ -135,6 +150,7 @@ utaz()
|
|||||||
(cd "$2" && cpio -id < "../$1") >/dev/null 2>&1
|
(cd "$2" && cpio -id < "../$1") >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _uncabextract
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_uncabextract()
|
_uncabextract()
|
||||||
{
|
{
|
||||||
@@ -142,6 +158,7 @@ utaz()
|
|||||||
cabextract "$1" -d "$2/" >/dev/null 2>&1
|
cabextract "$1" -d "$2/" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _undeb
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_undeb()
|
_undeb()
|
||||||
{
|
{
|
||||||
@@ -149,6 +166,7 @@ utaz()
|
|||||||
dpkg-deb -x "$1" "$2/" >/dev/null 2>&1
|
dpkg-deb -x "$1" "$2/" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _unrpm
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_unrpm()
|
_unrpm()
|
||||||
{
|
{
|
||||||
@@ -395,18 +413,34 @@ export -f utaz
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Compress directories or files into one or more archive
|
# Compress directories or files into one or more archive
|
||||||
# Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]
|
# Usage: taz [option] [--parallel=<n|auto>] [--format=<format>] [directory1 ... directoryN]
|
||||||
# Options:
|
# Options:
|
||||||
# -h, --help Display that help screen
|
# -h, --help Display that help screen
|
||||||
# -d, --delete Delete source file or directory after success
|
# -d, --delete Delete source file or directory after success
|
||||||
# -f, --format Chose archive format in the given list. If several format are
|
# -f, --format Chose archive format in the given list. If several format are
|
||||||
# given, the smalest is kept
|
# given, the smalest is kept
|
||||||
# -p, --parallel Number of threads to use (if allowed by underlying utility)
|
# -p, --parallel Number of threads to use, or 'auto' to use detected CPU count
|
||||||
# -v, --verbose Display progress where possible
|
# -v, --verbose Display progress where possible
|
||||||
# -q, --quiet Display less messages (only errors and warnings)
|
# -q, --quiet Display less messages (only errors and warnings)
|
||||||
# -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]
|
# -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]
|
||||||
taz()
|
taz()
|
||||||
{
|
{
|
||||||
|
# Resolve runtime CPU count for --parallel=auto.
|
||||||
|
local _taz_detect_cpus
|
||||||
|
_taz_detect_cpus()
|
||||||
|
{
|
||||||
|
local cpus=1
|
||||||
|
if command -v nproc >/dev/null 2>&1; then
|
||||||
|
cpus=$(nproc 2>/dev/null)
|
||||||
|
elif command -v getconf >/dev/null 2>&1; then
|
||||||
|
cpus=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ $cpus =~ ^[1-9][0-9]*$ ]] || cpus=1
|
||||||
|
printf "%s\n" "$cpus"
|
||||||
|
}
|
||||||
|
|
||||||
|
local _doxz
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_doxz()
|
_doxz()
|
||||||
{
|
{
|
||||||
@@ -427,6 +461,7 @@ taz()
|
|||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _dolz
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_dolz()
|
_dolz()
|
||||||
{
|
{
|
||||||
@@ -454,6 +489,7 @@ taz()
|
|||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _dogz
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_dogz()
|
_dogz()
|
||||||
{
|
{
|
||||||
@@ -476,11 +512,12 @@ taz()
|
|||||||
[[ $4 ]] && opt=('--verbose')
|
[[ $4 ]] && opt=('--verbose')
|
||||||
opt+=("$procopt")
|
opt+=("$procopt")
|
||||||
|
|
||||||
# Compresse au format bz2
|
# Compress with gzip
|
||||||
$command "${opt[@]}" --keep "-$3" "$1"
|
$command "${opt[@]}" --keep "-$3" "$1"
|
||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _dobz2
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_dobz2()
|
_dobz2()
|
||||||
{
|
{
|
||||||
@@ -508,6 +545,7 @@ taz()
|
|||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local _dolzo
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
_dolzo()
|
_dolzo()
|
||||||
{
|
{
|
||||||
@@ -520,7 +558,7 @@ taz()
|
|||||||
[[ $4 ]] && verb=('-v')
|
[[ $4 ]] && verb=('-v')
|
||||||
[[ $2 -gt 1 ]] && disp W "lzop doesn't support multithreading, falling back to 1 thread."
|
[[ $2 -gt 1 ]] && disp W "lzop doesn't support multithreading, falling back to 1 thread."
|
||||||
|
|
||||||
# Compresse au format lzo
|
# Compress with lzo
|
||||||
lzop "${verb[@]}" --keep "-$3" "$1"
|
lzop "${verb[@]}" --keep "-$3" "$1"
|
||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
@@ -537,13 +575,13 @@ taz()
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help)
|
-h|--help)
|
||||||
printf "taz: archive all files of a directory.\n\n"
|
printf "taz: archive all files of a directory.\n\n"
|
||||||
printf "Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]\n\n"
|
printf "Usage: taz [option] [--parallel=<n|auto>] [--format=<format>] [directory1 ... directoryN]\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\tDisplay that help screen\n"
|
printf "\t-h, --help\tDisplay that help screen\n"
|
||||||
printf "\t-d, --delete\tDelete source file or directory after success\n"
|
printf "\t-d, --delete\tDelete source file or directory after success\n"
|
||||||
printf "\t-f, --format\tChose archive format in the given list. If several format are"
|
printf "\t-f, --format\tChose archive format in the given list. If several format are\n"
|
||||||
printf "\t\t\tgiven, the smalest is kept\n"
|
printf "\t\t\tgiven, the smalest is kept\n"
|
||||||
printf "\t-p, --parallel\tNumber of threads to use (if allowed by underlying utility)\n"
|
printf "\t-p, --parallel\tNumber of threads, or 'auto' for runtime CPU count\n"
|
||||||
printf "\t-v, --verbose\tDisplay progress where possible\n"
|
printf "\t-v, --verbose\tDisplay progress where possible\n"
|
||||||
printf "\t-q, --quiet\tDisplay less messages (only errors and warnings)\n"
|
printf "\t-q, --quiet\tDisplay less messages (only errors and warnings)\n"
|
||||||
printf "\t-1, .., -9\tCompression level to use [1=fast/biggest, 9=slow/smallest]\n\n"
|
printf "\t-1, .., -9\tCompression level to use [1=fast/biggest, 9=slow/smallest]\n\n"
|
||||||
@@ -606,11 +644,20 @@ taz()
|
|||||||
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
|
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
|
||||||
|
|
||||||
[[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz}
|
[[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz}
|
||||||
[[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-1}
|
[[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-auto}
|
||||||
[[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6}
|
[[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6}
|
||||||
[[ $verbose -gt 1 && $quiet -gt 1 ]] &&
|
[[ $verbose -gt 1 && $quiet -gt 1 ]] &&
|
||||||
disp E "The --verbose and --quiet options can't be used together."
|
disp E "The --verbose and --quiet options can't be used together."
|
||||||
|
|
||||||
|
# Backward compatibility: 0 previously meant auto-detect.
|
||||||
|
[[ $nproc == 0 ]] && nproc=auto
|
||||||
|
if [[ $nproc == auto ]]; then
|
||||||
|
nproc=$(_taz_detect_cpus)
|
||||||
|
elif [[ ! $nproc =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
disp E "Invalid value for --parallel: '$nproc' (expected auto or a positive integer)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
for item in "${FILES[@]}"; do
|
for item in "${FILES[@]}"; do
|
||||||
local donetar=0
|
local donetar=0
|
||||||
disp I "Processing $item..."
|
disp I "Processing $item..."
|
||||||
|
|||||||
427
profile.d/conf.sh
Executable file
427
profile.d/conf.sh
Executable file
@@ -0,0 +1,427 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
|
||||||
|
# Protected by the BSD3 license. Please read bellow for details.
|
||||||
|
#
|
||||||
|
# * Redistribution and use in source and binary forms,
|
||||||
|
# * with or without modification, are permitted provided
|
||||||
|
# * that the following conditions are met:
|
||||||
|
# *
|
||||||
|
# * Redistributions of source code must retain the above
|
||||||
|
# * copyright notice, this list of conditions and the
|
||||||
|
# * following disclaimer.
|
||||||
|
# *
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# * copyright notice, this list of conditions and the following
|
||||||
|
# * disclaimer in the documentation and/or other materials
|
||||||
|
# * provided with the distribution.
|
||||||
|
# *
|
||||||
|
# * Neither the name of the copyright holder nor the names
|
||||||
|
# * of any other contributors may be used to endorse or
|
||||||
|
# * promote products derived from this software without
|
||||||
|
# * specific prior written permission.
|
||||||
|
# *
|
||||||
|
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# * OF SUCH DAMAGE.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Save or update a key=value pair in a section of the profile configuration file.
|
||||||
|
# The user configuration ($HOME/.profile.conf) is updated when it exists,
|
||||||
|
# otherwise the installation configuration ($PROFILE_CONF or $MYPATH/profile.conf)
|
||||||
|
# is used. The section header is created automatically when absent.
|
||||||
|
#
|
||||||
|
# Usage: conf_save <section> <key> <value>
|
||||||
|
# section : INI section name without brackets, e.g. "prompt" for [prompt]
|
||||||
|
# key : variable name to set (alphanumeric and underscore only)
|
||||||
|
# value : value to assign (may be empty)
|
||||||
|
conf_save()
|
||||||
|
{
|
||||||
|
if [[ $# -ne 3 ]]; then
|
||||||
|
disp E "Usage: conf_save <section> <key> <value>"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local section="$1" key="$2" value="$3"
|
||||||
|
|
||||||
|
if ! [[ "$section" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
|
||||||
|
disp E "conf_save: invalid section name '${section}' (alphanumeric and underscore only)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! [[ "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
|
||||||
|
disp E "conf_save: invalid key name '${key}' (alphanumeric and underscore only)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local conf_file
|
||||||
|
if [[ -f "$HOME/.profile.conf" ]]; then
|
||||||
|
conf_file="$HOME/.profile.conf"
|
||||||
|
else
|
||||||
|
conf_file="${PROFILE_CONF:-${MYPATH}/profile.conf}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local conf_dir="${conf_file%/*}"
|
||||||
|
[[ -d "$conf_dir" ]] || mkdir -p "$conf_dir" || {
|
||||||
|
disp E "conf_save: unable to create configuration directory: ${conf_dir}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -e "$conf_file" ]]; then
|
||||||
|
{
|
||||||
|
printf "[%s]\n" "$section"
|
||||||
|
printf "%s=%s\n" "$key" "$value"
|
||||||
|
} > "$conf_file" || {
|
||||||
|
disp E "conf_save: unable to write configuration file: ${conf_file}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmp_file="${conf_file}.tmp.$$"
|
||||||
|
awk -v sec="$section" -v key="$key" -v val="$value" '
|
||||||
|
BEGIN {
|
||||||
|
in_sec = 0
|
||||||
|
saw_sec = 0
|
||||||
|
wrote = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if ($0 ~ /^\[[^]]+\][[:space:]]*$/) {
|
||||||
|
if (in_sec && !wrote) {
|
||||||
|
print key "=" val
|
||||||
|
wrote = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($0 ~ ("^\\[" sec "\\][[:space:]]*$")) {
|
||||||
|
in_sec = 1
|
||||||
|
saw_sec = 1
|
||||||
|
} else {
|
||||||
|
in_sec = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
print
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_sec && $0 ~ ("^[[:space:]]*" key "[[:space:]]*=")) {
|
||||||
|
if (!wrote) {
|
||||||
|
print key "=" val
|
||||||
|
wrote = 1
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
print
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
if (in_sec && !wrote) {
|
||||||
|
print key "=" val
|
||||||
|
}
|
||||||
|
if (!saw_sec) {
|
||||||
|
print ""
|
||||||
|
print "[" sec "]"
|
||||||
|
print key "=" val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$conf_file" > "$tmp_file" || {
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
disp E "conf_save: unable to update configuration file: ${conf_file}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mv "$tmp_file" "$conf_file" || {
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
disp E "conf_save: unable to replace configuration file: ${conf_file}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export -f conf_save
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Display the profile configuration file, with optional section and key filters.
|
||||||
|
# The same file resolution as conf_save is used: $HOME/.profile.conf when
|
||||||
|
# present, otherwise $PROFILE_CONF or $MYPATH/profile.conf.
|
||||||
|
#
|
||||||
|
# Usage: conf_dump [options] [pattern]
|
||||||
|
# -s, --section NAME : Only display the given section
|
||||||
|
# -a, --all : Also show commented-out keys (default values, in white)
|
||||||
|
# pattern : Only display keys whose name contains this substring
|
||||||
|
#
|
||||||
|
# Output colours:
|
||||||
|
# green — key is explicitly set (uncommented) in the config file
|
||||||
|
# white — key is present but commented out (shows the default value)
|
||||||
|
conf_dump()
|
||||||
|
{
|
||||||
|
local section="" key_pattern="" show_all=0
|
||||||
|
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o hs:a --long help,section:,all -n 'conf_dump' -- "$@")
|
||||||
|
# shellcheck disable=SC2181
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"conf_dump --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "conf_dump: Display the profile configuration file, with optional filters.\n\n"
|
||||||
|
printf "Usage: conf_dump [options] [pattern]\n\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
|
printf "\t-s, --section NAME\tOnly display the given section\n"
|
||||||
|
printf "\t-a, --all\t\tAlso show commented-out keys (default values)\n\n"
|
||||||
|
printf "Arguments:\n"
|
||||||
|
printf "\tpattern\tOnly display keys whose name contains this substring\n\n"
|
||||||
|
printf "Output colours:\n"
|
||||||
|
printf "\t\033[1;32mgreen\033[0m — key is explicitly set (active)\n"
|
||||||
|
printf "\t\033[1;97mwhite\033[0m — key is commented out (default value)\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-s|--section)
|
||||||
|
section="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-a|--all)
|
||||||
|
show_all=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $# -gt 0 ]] && key_pattern="$1"
|
||||||
|
|
||||||
|
local conf_file
|
||||||
|
if [[ -f "$HOME/.profile.conf" ]]; then
|
||||||
|
conf_file="$HOME/.profile.conf"
|
||||||
|
else
|
||||||
|
conf_file="${PROFILE_CONF:-${MYPATH}/profile.conf}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$conf_file" ]]; then
|
||||||
|
disp E "conf_dump: configuration file not found: ${conf_file}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Colours are passed via ENVIRON to avoid awk -v escape interpretation.
|
||||||
|
_CONF_DUMP_SEC="${Blue:-}" \
|
||||||
|
_CONF_DUMP_KEY_ACTIVE="${BGreen:-}" \
|
||||||
|
_CONF_DUMP_KEY_DEFAULT="${BIWhite:-}" \
|
||||||
|
_CONF_DUMP_RST="${RESETCOL:-}" \
|
||||||
|
awk -v sec_filter="$section" -v key_filter="$key_pattern" -v show_all="$show_all" '
|
||||||
|
BEGIN {
|
||||||
|
c_sec = ENVIRON["_CONF_DUMP_SEC"]
|
||||||
|
c_active = ENVIRON["_CONF_DUMP_KEY_ACTIVE"]
|
||||||
|
c_default = ENVIRON["_CONF_DUMP_KEY_DEFAULT"]
|
||||||
|
c_rst = ENVIRON["_CONF_DUMP_RST"]
|
||||||
|
# Shell colour vars contain literal \e[…m strings; convert to
|
||||||
|
# the actual ESC byte so awk print emits real ANSI sequences.
|
||||||
|
gsub(/\\e/, "\033", c_sec)
|
||||||
|
gsub(/\\e/, "\033", c_active)
|
||||||
|
gsub(/\\e/, "\033", c_default)
|
||||||
|
gsub(/\\e/, "\033", c_rst)
|
||||||
|
in_target = 0
|
||||||
|
current_sec = ""
|
||||||
|
hdr_printed = 0
|
||||||
|
found = 0
|
||||||
|
# seen[sec:key] — tracks every key already printed to deduplicate
|
||||||
|
# commented entries and avoid re-showing an active key in white.
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
sub(/\r$/, "")
|
||||||
|
|
||||||
|
# Section header
|
||||||
|
if ($0 ~ /^\[[^]]+\][[:space:]]*$/) {
|
||||||
|
current_sec = $0
|
||||||
|
sub(/^\[/, "", current_sec)
|
||||||
|
sub(/\][[:space:]]*$/, "", current_sec)
|
||||||
|
in_target = (sec_filter == "" || current_sec == sec_filter)
|
||||||
|
hdr_printed = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_target) next
|
||||||
|
|
||||||
|
# Active (uncommented) key=value — always shown
|
||||||
|
if ($0 ~ /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) {
|
||||||
|
key = $0; sub(/[[:space:]]*=.*$/, "", key); sub(/^[[:space:]]*/, "", key)
|
||||||
|
val = $0; sub(/^[^=]*=/, "", val)
|
||||||
|
if (key_filter != "" && index(key, key_filter) == 0) next
|
||||||
|
if (!hdr_printed) {
|
||||||
|
if (found) print ""
|
||||||
|
print c_sec "[" current_sec "]" c_rst
|
||||||
|
hdr_printed = 1; found = 1
|
||||||
|
}
|
||||||
|
seen[current_sec ":" key] = 1
|
||||||
|
print " " c_active key c_rst "=" val
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
# Commented-out key=value — shown only with show_all.
|
||||||
|
# The value displayed is the live environment value (what load_conf
|
||||||
|
# actually exported), not the commented-out text which may be an
|
||||||
|
# example or stale documentation. Each key is shown at most once.
|
||||||
|
if (show_all && $0 ~ /^[[:space:]]*#[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) {
|
||||||
|
line = $0; sub(/^[[:space:]]*#[[:space:]]*/, "", line)
|
||||||
|
key = line; sub(/[[:space:]]*=.*$/, "", key); sub(/^[[:space:]]*/, "", key)
|
||||||
|
if (seen[current_sec ":" key]) next
|
||||||
|
seen[current_sec ":" key] = 1
|
||||||
|
if (key_filter != "" && index(key, key_filter) == 0) next
|
||||||
|
val = ENVIRON[key]
|
||||||
|
if (!hdr_printed) {
|
||||||
|
if (found) print ""
|
||||||
|
print c_sec "[" current_sec "]" c_rst
|
||||||
|
hdr_printed = 1; found = 1
|
||||||
|
}
|
||||||
|
print " " c_default key c_rst "=" val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$conf_file"
|
||||||
|
}
|
||||||
|
export -f conf_dump
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Set TERM to the best terminal capability available on this system.
|
||||||
|
# Called automatically at sourcing time.
|
||||||
|
#
|
||||||
|
# If TERM is already set to a specific value (not empty, not the sentinel
|
||||||
|
# "smart"), it is honoured as-is — standard Unix behaviour.
|
||||||
|
# If TERM is empty or set to "smart", terminal emulator hints are checked first
|
||||||
|
# (COLORTERM, TERM_PROGRAM, VTE_VERSION, WT_SESSION, TMUX, STY), then terminfo
|
||||||
|
# is probed in preference order: xterm-256color → xterm-color → xterm → vt100.
|
||||||
|
#
|
||||||
|
# Usage: term_set
|
||||||
|
term_set()
|
||||||
|
{
|
||||||
|
local _current="${TERM:-}"
|
||||||
|
|
||||||
|
# Specific value already set — nothing to do.
|
||||||
|
if [[ -n "$_current" && "$_current" != "smart" ]]; then
|
||||||
|
export TERM="$_current"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return true when terminfo has an entry for the given terminal type.
|
||||||
|
local _term_has
|
||||||
|
_term_has()
|
||||||
|
{
|
||||||
|
tput -T "$1" longname >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# True-color hint: COLORTERM=truecolor|24bit is set by the emulator.
|
||||||
|
local _truecolor=0
|
||||||
|
[[ "${COLORTERM:-}" == "truecolor" || "${COLORTERM:-}" == "24bit" ]] && _truecolor=1
|
||||||
|
|
||||||
|
local _candidate=""
|
||||||
|
|
||||||
|
# 1. Explicit truecolor hint set by modern terminal emulators.
|
||||||
|
if (( _truecolor )); then
|
||||||
|
local _t
|
||||||
|
for _t in xterm-direct xterm-256color; do
|
||||||
|
_term_has "$_t" && { _candidate="$_t"; break; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Terminal programme name hints.
|
||||||
|
if [[ -z "$_candidate" ]]; then
|
||||||
|
case "${TERM_PROGRAM:-}" in
|
||||||
|
iTerm.app)
|
||||||
|
if (( _truecolor )); then
|
||||||
|
_term_has "xterm-direct" && _candidate="xterm-direct"
|
||||||
|
fi
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color"
|
||||||
|
;;
|
||||||
|
WezTerm)
|
||||||
|
if (( _truecolor )); then
|
||||||
|
_term_has "xterm-direct" && _candidate="xterm-direct"
|
||||||
|
fi
|
||||||
|
if [[ -z "$_candidate" ]]; then
|
||||||
|
_term_has "wezterm" && _candidate="wezterm"
|
||||||
|
fi
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color"
|
||||||
|
;;
|
||||||
|
Hyper|vscode)
|
||||||
|
if (( _truecolor )); then
|
||||||
|
_term_has "xterm-direct" && _candidate="xterm-direct"
|
||||||
|
fi
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. VTE-based terminals (GNOME Terminal, Tilix, Xfce Terminal, …).
|
||||||
|
if [[ -z "$_candidate" && -n "${VTE_VERSION:-}" ]]; then
|
||||||
|
if (( _truecolor )); then
|
||||||
|
_term_has "vte-direct" && _candidate="vte-direct"
|
||||||
|
fi
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "vte-256color" && _candidate="vte-256color"
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Windows Terminal.
|
||||||
|
if [[ -z "$_candidate" && -n "${WT_SESSION:-}" ]]; then
|
||||||
|
if (( _truecolor )); then
|
||||||
|
_term_has "xterm-direct" && _candidate="xterm-direct"
|
||||||
|
fi
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "xterm-256color" && _candidate="xterm-256color"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. tmux — prefer *-direct when truecolor, then *-256color, then screen-256color.
|
||||||
|
if [[ -z "$_candidate" && -n "${TMUX:-}" ]]; then
|
||||||
|
if (( _truecolor )); then
|
||||||
|
_term_has "tmux-direct" && _candidate="tmux-direct"
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "screen-direct" && _candidate="screen-direct"
|
||||||
|
fi
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "tmux-256color" && _candidate="tmux-256color"
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "screen-256color" && _candidate="screen-256color"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. GNU screen — prefer screen-direct when truecolor, then screen-256color.
|
||||||
|
if [[ -z "$_candidate" && -n "${STY:-}" ]]; then
|
||||||
|
if (( _truecolor )); then
|
||||||
|
_term_has "screen-direct" && _candidate="screen-direct"
|
||||||
|
fi
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "screen-256color" && _candidate="screen-256color"
|
||||||
|
[[ -z "$_candidate" ]] && _term_has "screen" && _candidate="screen"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. Generic terminfo probe in preference order.
|
||||||
|
if [[ -z "$_candidate" ]]; then
|
||||||
|
local _t
|
||||||
|
for _t in xterm-256color xterm-color xterm vt100; do
|
||||||
|
_term_has "$_t" && { _candidate="$_t"; break; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset -f _term_has
|
||||||
|
|
||||||
|
export TERM="${_candidate:-vt100}"
|
||||||
|
}
|
||||||
|
export -f term_set
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
term_set
|
||||||
|
|
||||||
|
|
||||||
|
# EOF
|
||||||
@@ -128,52 +128,108 @@ export -f set_colors
|
|||||||
# D : debug (cyan)
|
# D : debug (cyan)
|
||||||
disp()
|
disp()
|
||||||
{
|
{
|
||||||
|
local _disp_print_wrapped
|
||||||
|
_disp_print_wrapped()
|
||||||
|
{
|
||||||
|
local prefix="$1"
|
||||||
|
local prefix_len="$2"
|
||||||
|
local target_fd="$3"
|
||||||
|
shift 3
|
||||||
|
local message="$*"
|
||||||
|
|
||||||
|
local cols="${COLUMNS:-}"
|
||||||
|
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
|
||||||
|
cols=$(tput cols 2>/dev/null)
|
||||||
|
fi
|
||||||
|
[[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80
|
||||||
|
|
||||||
|
local indent_len=0
|
||||||
|
[[ "$prefix_len" =~ ^[0-9]+$ && "$prefix_len" -gt 0 ]] && indent_len=$((prefix_len + 1))
|
||||||
|
|
||||||
|
local width=$((cols - indent_len))
|
||||||
|
(( width < 10 )) && width=10
|
||||||
|
|
||||||
|
local wrapped
|
||||||
|
wrapped=$(printf "%s" "$message" | fold -s -w "$width")
|
||||||
|
|
||||||
|
local first_line=1
|
||||||
|
local line
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
if (( first_line )); then
|
||||||
|
if [[ -n "$prefix" ]]; then
|
||||||
|
if [[ "$target_fd" -eq 2 ]]; then
|
||||||
|
printf "%b\n" "${prefix} ${line}${RESETCOL}" >&2
|
||||||
|
else
|
||||||
|
printf "%b\n" "${prefix} ${line}${RESETCOL}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "$target_fd" -eq 2 ]]; then
|
||||||
|
printf "%b\n" "${line}${RESETCOL}" >&2
|
||||||
|
else
|
||||||
|
printf "%b\n" "${line}${RESETCOL}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
first_line=0
|
||||||
|
else
|
||||||
|
if [[ "$target_fd" -eq 2 ]]; then
|
||||||
|
printf "%*s%b\n" "$indent_len" "" "${line}${RESETCOL}" >&2
|
||||||
|
else
|
||||||
|
printf "%*s%b\n" "$indent_len" "" "${line}${RESETCOL}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$wrapped"
|
||||||
|
}
|
||||||
|
|
||||||
# Handle NO_COLOR: disable colors if set
|
# Handle NO_COLOR: disable colors if set
|
||||||
local color_enabled=1
|
local color_enabled=1
|
||||||
[[ -n $NO_COLOR ]] && color_enabled=0
|
[[ -n $NO_COLOR ]] && color_enabled=0
|
||||||
|
|
||||||
case ${1^^} in
|
case ${1^^} in
|
||||||
"I")
|
"I")
|
||||||
|
local heads_plain="[ info ]"
|
||||||
if [[ $color_enabled -eq 1 ]]; then
|
if [[ $color_enabled -eq 1 ]]; then
|
||||||
local heads="[ ${IGreen}info${DEFAULTFG} ]"
|
local heads="[ ${IGreen}info${DEFAULTFG} ]"
|
||||||
else
|
else
|
||||||
local heads="[ info ]"
|
local heads="$heads_plain"
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
[[ -z $QUIET || $QUIET -ne 1 ]] && \
|
[[ -z $QUIET || $QUIET -ne 1 ]] && \
|
||||||
printf "%b\n" "${heads} $*${RESETCOL}"
|
_disp_print_wrapped "$heads" "${#heads_plain}" 1 "$*"
|
||||||
;;
|
;;
|
||||||
"W")
|
"W")
|
||||||
|
local heads_plain="[ Warning ]"
|
||||||
if [[ $color_enabled -eq 1 ]]; then
|
if [[ $color_enabled -eq 1 ]]; then
|
||||||
local heads="[ ${IYellow}Warning${DEFAULTFG} ]"
|
local heads="[ ${IYellow}Warning${DEFAULTFG} ]"
|
||||||
else
|
else
|
||||||
local heads="[ Warning ]"
|
local heads="$heads_plain"
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
printf "%b\n" "${heads} $*${RESETCOL}" >&2
|
_disp_print_wrapped "$heads" "${#heads_plain}" 2 "$*"
|
||||||
;;
|
;;
|
||||||
"E")
|
"E")
|
||||||
|
local heads_plain="[ ERROR ]"
|
||||||
if [[ $color_enabled -eq 1 ]]; then
|
if [[ $color_enabled -eq 1 ]]; then
|
||||||
local heads="[ ${IRed}ERROR${DEFAULTFG} ]"
|
local heads="[ ${IRed}ERROR${DEFAULTFG} ]"
|
||||||
else
|
else
|
||||||
local heads="[ ERROR ]"
|
local heads="$heads_plain"
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
printf "%b\n" "${heads} $*${RESETCOL}" >&2
|
_disp_print_wrapped "$heads" "${#heads_plain}" 2 "$*"
|
||||||
;;
|
;;
|
||||||
"D")
|
"D")
|
||||||
|
local heads_plain="[ debug ]"
|
||||||
if [[ $color_enabled -eq 1 ]]; then
|
if [[ $color_enabled -eq 1 ]]; then
|
||||||
local heads="[ ${ICyan}debug${DEFAULTFG} ]"
|
local heads="[ ${ICyan}debug${DEFAULTFG} ]"
|
||||||
else
|
else
|
||||||
local heads="[ debug ]"
|
local heads="$heads_plain"
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
[[ -n $DEBUG && $DEBUG -gt 1 ]] && \
|
[[ -n $DEBUG && $DEBUG -gt 1 ]] && \
|
||||||
printf "%b\n" "${heads} $*${RESETCOL}"
|
_disp_print_wrapped "$heads" "${#heads_plain}" 1 "$*"
|
||||||
;;
|
;;
|
||||||
* )
|
* )
|
||||||
[[ -z $QUIET || $QUIET -ne 1 ]] && \
|
[[ -z $QUIET || $QUIET -ne 1 ]] && \
|
||||||
printf "%b\n" "$*"
|
_disp_print_wrapped "" 0 1 "$*"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -181,6 +237,484 @@ export -f disp
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Render Markdown files with terminal formatting
|
||||||
|
# Usage: mdcat [file]
|
||||||
|
mdcat()
|
||||||
|
{
|
||||||
|
local _mdcat_style_inline
|
||||||
|
_mdcat_style_inline()
|
||||||
|
{
|
||||||
|
local text="$1"
|
||||||
|
local bold_on="" italic_on="" code_on="" style_off=""
|
||||||
|
local link_text_on="" link_url_on=""
|
||||||
|
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
bold_on=$'\e[1m'
|
||||||
|
italic_on=$'\e[3m'
|
||||||
|
code_on="${On_IBlack}${BIWhite}"
|
||||||
|
link_text_on=$'\e[4;96m'
|
||||||
|
link_url_on="$IBlack"
|
||||||
|
style_off="$RESETCOL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apply inline transforms in a safe order: code, links, then emphasis.
|
||||||
|
# This prevents emphasis parsing inside code spans or link URLs.
|
||||||
|
while [[ "$text" =~ \[([^][]+)\]\(([^()]+)\) ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local label="${BASH_REMATCH[1]}"
|
||||||
|
local url="${BASH_REMATCH[2]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
if [[ "$label" == "$url" ]]; then
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
text="${before}${link_text_on}${label}${style_off}${after}"
|
||||||
|
else
|
||||||
|
text="${before}${label}${after}"
|
||||||
|
fi
|
||||||
|
elif [[ -z $NO_COLOR ]]; then
|
||||||
|
text="${before}${link_text_on}${label}${style_off} ${link_url_on}(${url})${style_off}${after}"
|
||||||
|
else
|
||||||
|
text="${before}${label} (${url})${after}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Style bare URLs without re-matching the same URL forever.
|
||||||
|
# The prefix capture keeps progress monotonic and prevents freeze loops.
|
||||||
|
# Skip this transformation in NO_COLOR mode.
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
while [[ "$text" =~ (^|[[:space:]\(])(https?://[^[:space:]\)]+) ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local pre="${BASH_REMATCH[1]}"
|
||||||
|
local url="${BASH_REMATCH[2]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
text="${before}${pre}${link_text_on}${url}${style_off}${after}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [[ "$text" =~ \`([^\`]+)\` ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local val="${BASH_REMATCH[1]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
text="${before}${code_on}${val}${style_off}${after}"
|
||||||
|
done
|
||||||
|
|
||||||
|
while [[ "$text" =~ \*\*([^*]+)\*\* ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local val="${BASH_REMATCH[1]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
text="${before}${bold_on}${val}${style_off}${after}"
|
||||||
|
done
|
||||||
|
|
||||||
|
while [[ "$text" =~ __([^_]+)__ ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local val="${BASH_REMATCH[1]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
text="${before}${bold_on}${val}${style_off}${after}"
|
||||||
|
done
|
||||||
|
|
||||||
|
while [[ "$text" =~ \*([^*]+)\* ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local val="${BASH_REMATCH[1]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
text="${before}${italic_on}${val}${style_off}${after}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Match _italic_ only when underscores are outside word-like identifiers.
|
||||||
|
while [[ "$text" =~ (^|[[:space:][:punct:]])_([^[:space:]_][^_]*[^[:space:]_])_([[:space:][:punct:]]|$) ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local pre="${BASH_REMATCH[1]}"
|
||||||
|
local val="${BASH_REMATCH[2]}"
|
||||||
|
local post="${BASH_REMATCH[3]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
text="${before}${pre}${italic_on}${val}${style_off}${post}${after}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Unescape Markdown punctuation escapes, such as \<, \>, \_, and \*.
|
||||||
|
while [[ "$text" =~ \\([[:punct:]]) ]]; do
|
||||||
|
local match="${BASH_REMATCH[0]}"
|
||||||
|
local val="${BASH_REMATCH[1]}"
|
||||||
|
local before="${text%%"$match"*}"
|
||||||
|
local after="${text#*"$match"}"
|
||||||
|
text="${before}${val}${after}"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s\n" "$text"
|
||||||
|
}
|
||||||
|
|
||||||
|
local _mdcat_print_hr
|
||||||
|
_mdcat_print_hr()
|
||||||
|
{
|
||||||
|
local cols="${COLUMNS:-}"
|
||||||
|
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
|
||||||
|
cols=$(tput cols 2>/dev/null)
|
||||||
|
fi
|
||||||
|
[[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80
|
||||||
|
|
||||||
|
local hr
|
||||||
|
printf -v hr "%*s" "$cols" ""
|
||||||
|
hr="${hr// /-}"
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
printf "%b%s%b\n" "$IBlack" "$hr" "$RESETCOL"
|
||||||
|
else
|
||||||
|
printf "%s\n" "$hr"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
local _mdcat_print_code_block
|
||||||
|
_mdcat_print_code_block()
|
||||||
|
{
|
||||||
|
local lang="$1"
|
||||||
|
shift
|
||||||
|
local -a lines=("$@")
|
||||||
|
|
||||||
|
local cols="${COLUMNS:-}"
|
||||||
|
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
|
||||||
|
cols=$(tput cols 2>/dev/null)
|
||||||
|
fi
|
||||||
|
[[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80
|
||||||
|
|
||||||
|
local max_inner=$((cols - 4))
|
||||||
|
(( max_inner < 16 )) && max_inner=16
|
||||||
|
|
||||||
|
local width=16 line
|
||||||
|
for line in "${lines[@]}"; do
|
||||||
|
local line_len=${#line}
|
||||||
|
(( line_len > width )) && width=$line_len
|
||||||
|
done
|
||||||
|
(( width > max_inner )) && width=$max_inner
|
||||||
|
|
||||||
|
local border
|
||||||
|
printf -v border "+-%*s-+" "$width" ""
|
||||||
|
border="${border// /-}"
|
||||||
|
|
||||||
|
local frame_on="" code_on="" off=""
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
frame_on="$IBlack"
|
||||||
|
code_on="$BIWhite"
|
||||||
|
off="$RESETCOL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%b%s%b\n" "$frame_on" "$border" "$off"
|
||||||
|
if [[ -n "$lang" ]]; then
|
||||||
|
local tag="language: $lang"
|
||||||
|
local wrapped_tag
|
||||||
|
wrapped_tag=$(printf "%s" "$tag" | fold -s -w "$width")
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "$line" "$frame_on" "$off"
|
||||||
|
done <<< "$wrapped_tag"
|
||||||
|
printf "%b%s%b\n" "$frame_on" "$border" "$off"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#lines[@]} -eq 0 ]]; then
|
||||||
|
printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "" "$frame_on" "$off"
|
||||||
|
else
|
||||||
|
for line in "${lines[@]}"; do
|
||||||
|
local wrapped
|
||||||
|
wrapped=$(printf "%s" "$line" | fold -s -w "$width")
|
||||||
|
while IFS= read -r wline || [[ -n "$wline" ]]; do
|
||||||
|
printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "$wline" "$frame_on" "$off"
|
||||||
|
done <<< "$wrapped"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
printf "%b%s%b\n" "$frame_on" "$border" "$off"
|
||||||
|
}
|
||||||
|
|
||||||
|
local _mdcat_print_table
|
||||||
|
# That function is a bit slow, we need to try to optimize it
|
||||||
|
_mdcat_print_table()
|
||||||
|
{
|
||||||
|
local _mdcat_parse_table_row
|
||||||
|
_mdcat_parse_table_row()
|
||||||
|
{
|
||||||
|
local input="$1"
|
||||||
|
local line
|
||||||
|
line=$(printf '%s' "$input" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
|
||||||
|
line="${line#|}"
|
||||||
|
line="${line%|}"
|
||||||
|
|
||||||
|
# Parse Markdown cells using only '|' separators and preserve spaces inside cells.
|
||||||
|
local -a cells=()
|
||||||
|
local cell rest="$line"
|
||||||
|
while :; do
|
||||||
|
if [[ "$rest" == *'|'* ]]; then
|
||||||
|
cell="${rest%%|*}"
|
||||||
|
rest="${rest#*|}"
|
||||||
|
else
|
||||||
|
cell="$rest"
|
||||||
|
rest=""
|
||||||
|
fi
|
||||||
|
cell=$(printf '%s' "$cell" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
|
||||||
|
cells+=("$cell")
|
||||||
|
[[ -z "$rest" ]] && break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#cells[@]} -eq 0 ]]; then
|
||||||
|
printf '%s' ""
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local joined="${cells[0]}"
|
||||||
|
for ((j=1; j<${#cells[@]}; ++j)); do
|
||||||
|
joined+="$sep${cells[j]}"
|
||||||
|
done
|
||||||
|
printf '%s' "$joined"
|
||||||
|
}
|
||||||
|
|
||||||
|
local -a lines=("$@")
|
||||||
|
local -a table_rows=()
|
||||||
|
local -a col_widths=()
|
||||||
|
local i j ncols=0
|
||||||
|
local sep=$'\x1f'
|
||||||
|
|
||||||
|
# Parse header and data rows, skipping the Markdown separator row.
|
||||||
|
# Width is computed from visible text length with ANSI escapes stripped.
|
||||||
|
for ((i=0; i<${#lines[@]}; ++i)); do
|
||||||
|
(( i == 1 )) && continue
|
||||||
|
local parsed
|
||||||
|
parsed=$(_mdcat_parse_table_row "${lines[i]}")
|
||||||
|
table_rows+=("$parsed")
|
||||||
|
|
||||||
|
local -a row=()
|
||||||
|
IFS="$sep" read -r -a row <<< "$parsed"
|
||||||
|
(( ncols < ${#row[@]} )) && ncols=${#row[@]}
|
||||||
|
for ((j=0; j<${#row[@]}; ++j)); do
|
||||||
|
local vis
|
||||||
|
vis=$(_mdcat_style_inline "${row[j]}")
|
||||||
|
vis=$(printf '%b' "$vis" | sed -E 's/\x1B\[[0-9;]*[mK]//g')
|
||||||
|
local cell_len=${#vis}
|
||||||
|
[[ -z "${col_widths[j]}" ]] && col_widths[j]=0
|
||||||
|
(( col_widths[j] < cell_len )) && col_widths[j]=$cell_len
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Ensure all width slots are initialized before drawing borders.
|
||||||
|
for ((j=0; j<ncols; ++j)); do
|
||||||
|
[[ -z "${col_widths[j]}" ]] && col_widths[j]=0
|
||||||
|
done
|
||||||
|
|
||||||
|
# Draw top border
|
||||||
|
local border="+"
|
||||||
|
for ((j=0; j<ncols; ++j)); do
|
||||||
|
local w=$((col_widths[j]+2))
|
||||||
|
border+="$(printf '%*s' "$w" "" | tr ' ' '-')+"
|
||||||
|
done
|
||||||
|
printf "%b\n" "$IBlack$border$RESETCOL"
|
||||||
|
|
||||||
|
# Print header row
|
||||||
|
local -a header=()
|
||||||
|
IFS="$sep" read -r -a header <<< "${table_rows[0]}"
|
||||||
|
printf "%b|" "$IBlack"
|
||||||
|
for ((j=0; j<ncols; ++j)); do
|
||||||
|
local raw_cell="${header[j]:-}"
|
||||||
|
local styled_cell
|
||||||
|
styled_cell=$(_mdcat_style_inline "$raw_cell")
|
||||||
|
local visible
|
||||||
|
visible=$(printf '%b' "$styled_cell" | sed -E 's/\x1B\[[0-9;]*[mK]//g')
|
||||||
|
local pad=$((col_widths[j] - ${#visible}))
|
||||||
|
(( pad < 0 )) && pad=0
|
||||||
|
printf " %b%b%*s%b |" "$BBlue" "$styled_cell" "$pad" "" "$IBlack"
|
||||||
|
done
|
||||||
|
printf "%b\n" "$RESETCOL"
|
||||||
|
|
||||||
|
# Header separator
|
||||||
|
printf "%b|" "$IBlack"
|
||||||
|
for ((j=0; j<ncols; ++j)); do
|
||||||
|
printf " %s |" "$(printf '%*s' "${col_widths[j]}" "" | tr ' ' '-')"
|
||||||
|
done
|
||||||
|
printf "%b\n" "$RESETCOL"
|
||||||
|
|
||||||
|
# Print data rows
|
||||||
|
for ((i=1; i<${#table_rows[@]}; ++i)); do
|
||||||
|
local -a row=()
|
||||||
|
IFS="$sep" read -r -a row <<< "${table_rows[i]}"
|
||||||
|
printf "%b|" "$IBlack"
|
||||||
|
for ((j=0; j<ncols; ++j)); do
|
||||||
|
local raw_cell="${row[j]:-}"
|
||||||
|
local styled_cell
|
||||||
|
styled_cell=$(_mdcat_style_inline "$raw_cell")
|
||||||
|
local visible
|
||||||
|
visible=$(printf '%b' "$styled_cell" | sed -E 's/\x1B\[[0-9;]*[mK]//g')
|
||||||
|
local pad=$((col_widths[j] - ${#visible}))
|
||||||
|
(( pad < 0 )) && pad=0
|
||||||
|
printf " %b%b%*s%b |" "$RESETCOL" "$styled_cell" "$pad" "" "$IBlack"
|
||||||
|
done
|
||||||
|
printf "%b\n" "$RESETCOL"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Draw bottom border
|
||||||
|
printf "%b\n" "$IBlack$border$RESETCOL"
|
||||||
|
}
|
||||||
|
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o h --long help -n 'mdcat' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"mdcat --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "mdcat: Render a Markdown file with terminal formatting.\n"
|
||||||
|
printf "Usage: mdcat [file]\n"
|
||||||
|
printf "If no file is provided, mdcat reads from standard input.\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid option, use \"mdcat --help\" to display options list"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $# -gt 1 ]]; then
|
||||||
|
disp E "Too many arguments. Usage: mdcat [file]"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local input_file=""
|
||||||
|
if [[ $# -eq 1 ]]; then
|
||||||
|
input_file="$1"
|
||||||
|
if [[ ! -f "$input_file" ]]; then
|
||||||
|
disp E "File not found: $input_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ ! -r "$input_file" ]]; then
|
||||||
|
disp E "File is not readable: $input_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
elif [[ -t 0 ]]; then
|
||||||
|
disp E "No input provided. Usage: mdcat [file] or: cat file.md | mdcat"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local in_code=0 code_lang="" raw line
|
||||||
|
local -a code_lines=()
|
||||||
|
local in_table=0
|
||||||
|
local -a table_lines=()
|
||||||
|
while IFS= read -r raw || [[ -n "$raw" ]]; do
|
||||||
|
line="${raw%$'\r'}"
|
||||||
|
|
||||||
|
# Table detection: line with |, next line with | and ---
|
||||||
|
if [[ $in_table -eq 0 && "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ ]]; then
|
||||||
|
local next
|
||||||
|
IFS= read -r next || true
|
||||||
|
# Accept: | --- | --- | or |:---|---:| etc.
|
||||||
|
if [[ "$next" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then
|
||||||
|
in_table=1
|
||||||
|
table_lines=("$line" "$next")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ $in_table -eq 1 ]]; then
|
||||||
|
# Accept table row if it starts and ends with |
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ && ! "$line" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then
|
||||||
|
table_lines+=("$line")
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
_mdcat_print_table "${table_lines[@]}"
|
||||||
|
in_table=0
|
||||||
|
table_lines=()
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $in_code -eq 1 ]]; then
|
||||||
|
if [[ "$line" =~ ^\`\`\` ]]; then
|
||||||
|
_mdcat_print_code_block "$code_lang" "${code_lines[@]}"
|
||||||
|
in_code=0
|
||||||
|
code_lang=""
|
||||||
|
code_lines=()
|
||||||
|
else
|
||||||
|
code_lines+=("$line")
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^\`\`\`[[:space:]]*([^[:space:]]*) ]]; then
|
||||||
|
in_code=1
|
||||||
|
code_lang="${BASH_REMATCH[1]}"
|
||||||
|
code_lines=()
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^(#{1,6})[[:space:]]+(.*)$ ]]; then
|
||||||
|
local lvl=${#BASH_REMATCH[1]}
|
||||||
|
local title="${BASH_REMATCH[2]}"
|
||||||
|
local h_on=""
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
case "$lvl" in
|
||||||
|
1) h_on="$BBlue" ;;
|
||||||
|
2) h_on="$BCyan" ;;
|
||||||
|
3) h_on="$BGreen" ;;
|
||||||
|
*) h_on="$BIWhite" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
printf "%b%s%b\n" "$h_on" "$title" "$RESETCOL"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*([\-*_])[[:space:]]*\1[[:space:]]*\1[\-*_[:space:]]*$ ]]; then
|
||||||
|
_mdcat_print_hr
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^([[:space:]]*)\>[[:space:]]?(.*)$ ]]; then
|
||||||
|
local quote="${BASH_REMATCH[2]}"
|
||||||
|
quote=$(_mdcat_style_inline "$quote")
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
printf "%s%b|%b %b\n" "${BASH_REMATCH[1]}" "$ICyan" "$RESETCOL" "$quote"
|
||||||
|
else
|
||||||
|
printf "%s| %b\n" "${BASH_REMATCH[1]}" "$quote"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^([[:space:]]*)[-+*][[:space:]]+(.*)$ ]]; then
|
||||||
|
local item="${BASH_REMATCH[2]}"
|
||||||
|
item=$(_mdcat_style_inline "$item")
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
printf "%s%b*%b %b\n" "${BASH_REMATCH[1]}" "$IGreen" "$RESETCOL" "$item"
|
||||||
|
else
|
||||||
|
printf "%s* %b\n" "${BASH_REMATCH[1]}" "$item"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^([[:space:]]*)([0-9]+)\.[[:space:]]+(.*)$ ]]; then
|
||||||
|
local nitem="${BASH_REMATCH[3]}"
|
||||||
|
nitem=$(_mdcat_style_inline "$nitem")
|
||||||
|
if [[ -z $NO_COLOR ]]; then
|
||||||
|
printf "%s%b%s.%b %b\n" "${BASH_REMATCH[1]}" "$IGreen" "${BASH_REMATCH[2]}" "$RESETCOL" "$nitem"
|
||||||
|
else
|
||||||
|
printf "%s%s. %b\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "$nitem"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%b\n" "$(_mdcat_style_inline "$line")"
|
||||||
|
done < "${input_file:-/dev/stdin}"
|
||||||
|
|
||||||
|
if [[ $in_code -eq 1 ]]; then
|
||||||
|
_mdcat_print_code_block "$code_lang" "${code_lines[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
export -f mdcat
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
# Load disp section variables
|
# Load disp section variables
|
||||||
load_conf disp
|
load_conf disp
|
||||||
set_colors
|
set_colors
|
||||||
|
|||||||
@@ -471,6 +471,12 @@ file_stats()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Minimum/maximum size filters (evaluated in bytes)
|
# Minimum/maximum size filters (evaluated in bytes)
|
||||||
|
if [[ -n "$min_size" || -n "$max_size" ]]; then
|
||||||
|
if ! command -v numfmt >/dev/null 2>&1; then
|
||||||
|
disp E "file_stats: --min/--max require 'numfmt' (GNU coreutils). Please install it."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
if [[ -n "$min_size" ]]; then
|
if [[ -n "$min_size" ]]; then
|
||||||
find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c)
|
find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c)
|
||||||
fi
|
fi
|
||||||
@@ -637,7 +643,25 @@ findbig()
|
|||||||
find_args+=(-type f)
|
find_args+=(-type f)
|
||||||
|
|
||||||
# Logic: find files, print size and path, sort numeric reverse, take N
|
# Logic: find files, print size and path, sort numeric reverse, take N
|
||||||
|
local _fd
|
||||||
|
_fd=$(command -v fd 2>/dev/null || command -v fdfind 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -n "$_fd" ]]; then
|
||||||
|
local _fd_args=(--follow --no-ignore --hidden --absolute-path -t f)
|
||||||
|
(( one_fs )) && _fd_args+=(--one-file-system)
|
||||||
|
_fd_args+=(. "$dir")
|
||||||
if (( details )); then
|
if (( details )); then
|
||||||
|
"$_fd" "${_fd_args[@]}" --exec stat --printf="%s %n\n" 2>/dev/null \
|
||||||
|
| sort -rn | head -n "$limit" \
|
||||||
|
| while IFS= read -r line; do
|
||||||
|
local path="${line#* }"
|
||||||
|
ls -ld -- "$path"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
"$_fd" "${_fd_args[@]}" --exec stat --printf="%s %n\n" 2>/dev/null \
|
||||||
|
| sort -rn | head -n "$limit"
|
||||||
|
fi
|
||||||
|
elif (( details )); then
|
||||||
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit" |
|
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit" |
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
local path="${line#* }"
|
local path="${line#* }"
|
||||||
@@ -713,7 +737,27 @@ findzero()
|
|||||||
(( one_fs )) && find_args+=("-xdev")
|
(( one_fs )) && find_args+=("-xdev")
|
||||||
|
|
||||||
# Execution logic
|
# Execution logic
|
||||||
|
local _fd
|
||||||
|
_fd=$(command -v fd 2>/dev/null || command -v fdfind 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -n "$_fd" ]]; then
|
||||||
|
local _fd_args=(--follow --no-ignore --hidden --absolute-path -t f --size -1b)
|
||||||
|
(( one_fs )) && _fd_args+=(--one-file-system)
|
||||||
|
_fd_args+=(. "$dir")
|
||||||
if (( delete )); then
|
if (( delete )); then
|
||||||
|
disp W "Deleting empty files in $dir..."
|
||||||
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
printf "%s\n" "$f"
|
||||||
|
rm -f -- "$f"
|
||||||
|
done
|
||||||
|
elif (( details )); then
|
||||||
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
ls -ls -- "$f"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
"$_fd" "${_fd_args[@]}" 2>/dev/null
|
||||||
|
fi
|
||||||
|
elif (( delete )); then
|
||||||
disp W "Deleting empty files in $dir..."
|
disp W "Deleting empty files in $dir..."
|
||||||
find "${find_args[@]}" -delete -print
|
find "${find_args[@]}" -delete -print
|
||||||
elif (( details )); then
|
elif (( details )); then
|
||||||
@@ -787,7 +831,29 @@ finddead()
|
|||||||
(( one_fs )) && find_args+=("-xdev")
|
(( one_fs )) && find_args+=("-xdev")
|
||||||
|
|
||||||
# Execution logic
|
# Execution logic
|
||||||
|
local _fd
|
||||||
|
_fd=$(command -v fd 2>/dev/null || command -v fdfind 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -n "$_fd" ]]; then
|
||||||
|
# fd -t l lists all symlinks; post-filter broken ones (target unreachable via -e)
|
||||||
|
local _fd_args=(--no-ignore --hidden --absolute-path -t l)
|
||||||
|
(( one_fs )) && _fd_args+=(--one-file-system)
|
||||||
|
_fd_args+=(. "$dir")
|
||||||
if (( delete )); then
|
if (( delete )); then
|
||||||
|
disp W "Deleting dead symlinks in $dir..."
|
||||||
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
[[ -e "$f" ]] || { printf "%s\n" "$f"; rm -f -- "$f"; }
|
||||||
|
done
|
||||||
|
elif (( details )); then
|
||||||
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
[[ -e "$f" ]] || ls -ls -- "$f"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
[[ -e "$f" ]] || printf "%s\n" "$f"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
elif (( delete )); then
|
||||||
disp W "Deleting dead symlinks in $dir..."
|
disp W "Deleting dead symlinks in $dir..."
|
||||||
find "${find_args[@]}" -delete -print
|
find "${find_args[@]}" -delete -print
|
||||||
elif (( details )); then
|
elif (( details )); then
|
||||||
|
|||||||
374
profile.d/fun.sh
374
profile.d/fun.sh
@@ -94,6 +94,11 @@ busy()
|
|||||||
delay_s=$(awk "BEGIN{
|
delay_s=$(awk "BEGIN{
|
||||||
printf \"%.3f\", $delay_ms / 1000 }")
|
printf \"%.3f\", $delay_ms / 1000 }")
|
||||||
|
|
||||||
|
command -v hexdump >/dev/null 2>&1 || {
|
||||||
|
disp E "busy: 'hexdump' is required but not installed (util-linux or bsdmainutils)."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Monitor /dev/urandom
|
# Monitor /dev/urandom
|
||||||
(
|
(
|
||||||
hexdump -C < /dev/urandom | grep -iF --line-buffered "$pattern" | \
|
hexdump -C < /dev/urandom | grep -iF --line-buffered "$pattern" | \
|
||||||
@@ -108,6 +113,375 @@ busy()
|
|||||||
wait "$sub_pid" 2>/dev/null
|
wait "$sub_pid" 2>/dev/null
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
export -f busy
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Simulate a long and complex compilation process
|
||||||
|
# Usage: fake_compile [options]
|
||||||
|
# Options:
|
||||||
|
# --min-delay=<ms> : minimum delay between output lines (milliseconds, default: 40)
|
||||||
|
# --max-delay=<ms> : maximum delay between output lines (milliseconds, default: 150)
|
||||||
|
# --lang=<lang> : source language preset: c (default), cpp, java, python, random
|
||||||
|
# --errors : inject fake compilation errors at the end of the build
|
||||||
|
fake_compile()
|
||||||
|
{
|
||||||
|
local min_ms="${FAKE_COMPILE_DEFAULT_MIN_DELAY:-40}" \
|
||||||
|
max_ms="${FAKE_COMPILE_DEFAULT_MAX_DELAY:-150}" \
|
||||||
|
lang="${FAKE_COMPILE_DEFAULT_LANG:-c}" with_errors=0
|
||||||
|
|
||||||
|
local PARSED
|
||||||
|
# Short: h, n:, x:, l:, e
|
||||||
|
# Long: help, min-delay:, max-delay:, lang:, errors
|
||||||
|
PARSED=$(getopt -o hn:x:l:e --long help,min-delay:,max-delay:,lang:,errors -n 'fake_compile' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"fake_compile --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "fake_compile: Simulate a complex compilation process.\n\n"
|
||||||
|
printf "Usage: fake_compile [options]\n\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-h, --help\t\t\tDisplay this help screen\n"
|
||||||
|
printf "\t-n, --min-delay MS\t\tMin delay between lines in milliseconds (default: 40)\n"
|
||||||
|
printf "\t-x, --max-delay MS\t\tMax delay between lines in milliseconds (default: 150)\n"
|
||||||
|
printf "\t-l, --lang LANG\t\t\tLanguage preset: c, cpp, java, python, random (default: c)\n"
|
||||||
|
printf "\t-e, --errors\t\t\tInject fake compilation errors at the end\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-n|--min-delay)
|
||||||
|
min_ms="$2"
|
||||||
|
if ! [[ "$min_ms" =~ ^[0-9]+$ ]]; then
|
||||||
|
disp E "Invalid min-delay: must be an integer (milliseconds)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-x|--max-delay)
|
||||||
|
max_ms="$2"
|
||||||
|
if ! [[ "$max_ms" =~ ^[0-9]+$ ]]; then
|
||||||
|
disp E "Invalid max-delay: must be an integer (milliseconds)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-l|--lang)
|
||||||
|
lang="$2"
|
||||||
|
case "$lang" in
|
||||||
|
c|cpp|java|python|random) ;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid lang: must be one of: c, cpp, java, python, random."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-e|--errors)
|
||||||
|
with_errors=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid option: $1"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $min_ms -gt $max_ms ]]; then
|
||||||
|
disp E "min-delay ($min_ms) must be <= max-delay ($max_ms)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
rand_sleep() {
|
||||||
|
local r=$(( min_ms + RANDOM % (max_ms - min_ms + 1) ))
|
||||||
|
sleep "$(awk "BEGIN{ printf \"%.3f\", $r / 1000 }")"
|
||||||
|
}
|
||||||
|
|
||||||
|
c_files=( "main" "utils" "parser" "lexer" "codegen" "optimizer"
|
||||||
|
"allocator" "scheduler" "resolver" "runtime" "buffer"
|
||||||
|
"hashmap" "io" "net" "crypto" "compress" )
|
||||||
|
cpp_files=( "Application" "Controller" "AbstractFactory" "Singleton"
|
||||||
|
"Observer" "Strategy" "Builder" "Facade" "Proxy"
|
||||||
|
"Iterator" "Decorator" "CommandDispatcher" "EventLoop"
|
||||||
|
"MemoryPool" "ThreadSafe" )
|
||||||
|
java_files=( "Application" "Service" "Repository" "Controller" "Entity"
|
||||||
|
"Configuration" "SecurityConfig" "DataSourceConfig"
|
||||||
|
"RestTemplate" "ExceptionHandler" "Validator" )
|
||||||
|
python_files=( "setup" "config" "utils" "models" "views" "serializers"
|
||||||
|
"migrations/0001_initial" "migrations/0002_auto" "tests"
|
||||||
|
"signals" "admin" "apps" "urls" "wsgi" "celery" "tasks" )
|
||||||
|
|
||||||
|
warnings=(
|
||||||
|
"warning: implicit declaration of function"
|
||||||
|
"warning: unused variable [-Wunused-variable]"
|
||||||
|
"warning: comparison between signed and unsigned integer expressions"
|
||||||
|
"warning: suggest parentheses around operand of '!'"
|
||||||
|
"warning: format '%d' expects 'int', but argument has type 'long int'"
|
||||||
|
"warning: control reaches end of non-void function [-Wreturn-type]"
|
||||||
|
"warning: deprecated conversion from string literal to 'char*'"
|
||||||
|
"warning: address of local variable taken"
|
||||||
|
)
|
||||||
|
errors=(
|
||||||
|
"error: 'NULL' was not declared in this scope"
|
||||||
|
"error: expected ';' before '}' token"
|
||||||
|
"error: undefined reference to 'main'"
|
||||||
|
"error: too few arguments to function"
|
||||||
|
"error: invalid use of incomplete type"
|
||||||
|
)
|
||||||
|
|
||||||
|
case "$lang" in
|
||||||
|
c) files=("${c_files[@]}"); ext=".c" ;;
|
||||||
|
cpp) files=("${cpp_files[@]}"); ext=".cpp" ;;
|
||||||
|
java) files=("${java_files[@]}"); ext=".java" ;;
|
||||||
|
python) files=("${python_files[@]}"); ext=".py" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if [[ "$lang" == "random" ]]; then
|
||||||
|
_langs=("c" "cpp" "java" "python")
|
||||||
|
_pick="${_langs[$((RANDOM % 4))]}"
|
||||||
|
case "$_pick" in
|
||||||
|
c) files=("${c_files[@]}"); ext=".c" ;;
|
||||||
|
cpp) files=("${cpp_files[@]}"); ext=".cpp" ;;
|
||||||
|
java) files=("${java_files[@]}"); ext=".java" ;;
|
||||||
|
python) files=("${python_files[@]}"); ext=".py" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
total=${#files[@]}
|
||||||
|
i=0
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
i=$(( i + 1 ))
|
||||||
|
warn_count=$(( RANDOM % 3 ))
|
||||||
|
printf "[ %2d/%2d ] Compiling %s%s ...\n" "$i" "$total" "$f" "$ext"
|
||||||
|
rand_sleep
|
||||||
|
w=0
|
||||||
|
while [[ $w -lt $warn_count ]]; do
|
||||||
|
wline="${warnings[$((RANDOM % ${#warnings[@]}))]}"
|
||||||
|
printf " %s%s:%d:%d: %s\n" \
|
||||||
|
"$f" "$ext" "$(( RANDOM % 200 + 1 ))" "$(( RANDOM % 80 + 1 ))" "$wline"
|
||||||
|
rand_sleep
|
||||||
|
w=$(( w + 1 ))
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $with_errors -eq 1 ]]; then
|
||||||
|
for eline in "${errors[@]}"; do
|
||||||
|
ef="${files[$((RANDOM % ${#files[@]}))]}"
|
||||||
|
printf " %s%s:%d:%d: %s\n" \
|
||||||
|
"$ef" "$ext" "$(( RANDOM % 200 + 1 ))" "$(( RANDOM % 80 + 1 ))" "$eline"
|
||||||
|
rand_sleep
|
||||||
|
done
|
||||||
|
printf "\nBuild FAILED: %d error(s), %d warning(s)\n" \
|
||||||
|
"${#errors[@]}" "$(( RANDOM % 20 + 5 ))"
|
||||||
|
else
|
||||||
|
printf "\nBuild SUCCEEDED: 0 error(s), %d warning(s)\n" \
|
||||||
|
"$(( RANDOM % 15 + 2 ))"
|
||||||
|
fi
|
||||||
|
printf "\n"
|
||||||
|
done
|
||||||
|
) & local sub_pid=$!
|
||||||
|
|
||||||
|
IFS= read -r -n 1 -s _ </dev/tty
|
||||||
|
kill -- -"$sub_pid" 2>/dev/null || kill "$sub_pid" 2>/dev/null
|
||||||
|
wait "$sub_pid" 2>/dev/null
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f fake_compile
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Simulate a dramatic hacking sequence
|
||||||
|
# Usage: hack [options]
|
||||||
|
# Options:
|
||||||
|
# --target=<ip> : target IP address (default: random)
|
||||||
|
# --min-delay=<ms> : minimum delay between output lines (milliseconds, default: 60)
|
||||||
|
# --max-delay=<ms> : maximum delay between output lines (milliseconds, default: 250)
|
||||||
|
hack()
|
||||||
|
{
|
||||||
|
local min_ms="${HACK_DEFAULT_MIN_DELAY:-60}" \
|
||||||
|
max_ms="${HACK_DEFAULT_MAX_DELAY:-250}" \
|
||||||
|
target=""
|
||||||
|
|
||||||
|
local PARSED
|
||||||
|
# Short: h, t:, n:, x:
|
||||||
|
# Long: help, target:, min-delay:, max-delay:
|
||||||
|
PARSED=$(getopt -o ht:n:x: --long help,target:,min-delay:,max-delay: -n 'hack' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"hack --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "hack: Simulate a dramatic hacking sequence.\n\n"
|
||||||
|
printf "Usage: hack [options]\n\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-h, --help\t\t\tDisplay this help screen\n"
|
||||||
|
printf "\t-t, --target IP\t\t\tTarget IP address (default: random)\n"
|
||||||
|
printf "\t-n, --min-delay MS\t\tMin delay between output lines in milliseconds (default: 60)\n"
|
||||||
|
printf "\t-x, --max-delay MS\t\tMax delay between output lines in milliseconds (default: 250)\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-t|--target)
|
||||||
|
target="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-n|--min-delay)
|
||||||
|
min_ms="$2"
|
||||||
|
if ! [[ "$min_ms" =~ ^[0-9]+$ ]]; then
|
||||||
|
disp E "Invalid min-delay: must be an integer (milliseconds)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-x|--max-delay)
|
||||||
|
max_ms="$2"
|
||||||
|
if ! [[ "$max_ms" =~ ^[0-9]+$ ]]; then
|
||||||
|
disp E "Invalid max-delay: must be an integer (milliseconds)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid option: $1"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $min_ms -gt $max_ms ]]; then
|
||||||
|
disp E "min-delay ($min_ms) must be <= max-delay ($max_ms)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
rand_sleep() {
|
||||||
|
local r=$(( min_ms + RANDOM % (max_ms - min_ms + 1) ))
|
||||||
|
sleep "$(awk "BEGIN{ printf \"%.3f\", $r / 1000 }")"
|
||||||
|
}
|
||||||
|
rand_ip() { printf '%d.%d.%d.%d\n' \
|
||||||
|
$(( RANDOM%223+1 )) $(( RANDOM%255 )) \
|
||||||
|
$(( RANDOM%255 )) $(( RANDOM%254+1 )); }
|
||||||
|
rand_mac() { printf '%02x:%02x:%02x:%02x:%02x:%02x\n' \
|
||||||
|
$(( RANDOM%256 )) $(( RANDOM%256 )) $(( RANDOM%256 )) \
|
||||||
|
$(( RANDOM%256 )) $(( RANDOM%256 )) $(( RANDOM%256 )); }
|
||||||
|
rand_hash() { printf '%04x%04x%04x%04x%04x%04x%04x%04x' \
|
||||||
|
$RANDOM $RANDOM $RANDOM $RANDOM \
|
||||||
|
$RANDOM $RANDOM $RANDOM $RANDOM; }
|
||||||
|
|
||||||
|
ports=( 22 80 443 3306 5432 6379 8080 8443 27017 )
|
||||||
|
services=( "ssh" "http" "https" "mysql" "postgresql" "redis" "http-alt" "https-alt" "mongodb" )
|
||||||
|
cve_ids=( "CVE-2024-3094" "CVE-2023-44487" "CVE-2024-6387" "CVE-2021-44228" "CVE-2022-0847" )
|
||||||
|
os_list=( "Linux 5.15.x" "Linux 6.1.x" "Ubuntu 22.04 LTS" "Debian 12" "CentOS Stream 9" )
|
||||||
|
users=( "root" "admin" "www-data" "postgres" "redis" "deploy" )
|
||||||
|
passwords=( "password123" "admin2024" "letmein!" "Sup3rS3cr3t" "qwerty" "123456" )
|
||||||
|
fixed_target="$target"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
[[ -z "$fixed_target" ]] && target="$(rand_ip)" || target="$fixed_target"
|
||||||
|
|
||||||
|
printf "[*] Initializing attack sequence against %s\n" "$target"
|
||||||
|
rand_sleep
|
||||||
|
|
||||||
|
# Phase 1 — port scan
|
||||||
|
printf "[*] Starting port scan...\n"
|
||||||
|
rand_sleep
|
||||||
|
open_ports=()
|
||||||
|
for idx in "${!ports[@]}"; do
|
||||||
|
if (( RANDOM % 3 != 0 )); then
|
||||||
|
printf " %-6s open %s\n" "${ports[$idx]}/tcp" "${services[$idx]}"
|
||||||
|
open_ports+=( "${ports[$idx]}/${services[$idx]}" )
|
||||||
|
rand_sleep
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf "[+] %d open port(s) found.\n" "${#open_ports[@]}"
|
||||||
|
rand_sleep
|
||||||
|
|
||||||
|
# Phase 2 — OS fingerprinting
|
||||||
|
printf "[*] OS fingerprinting...\n"
|
||||||
|
rand_sleep
|
||||||
|
printf "[+] Target OS: %s (MAC: %s)\n" \
|
||||||
|
"${os_list[$((RANDOM % ${#os_list[@]}))]}" "$(rand_mac)"
|
||||||
|
rand_sleep
|
||||||
|
|
||||||
|
# Phase 3 — CVE check
|
||||||
|
printf "[*] Checking known vulnerabilities...\n"
|
||||||
|
rand_sleep
|
||||||
|
vuln_count=$(( RANDOM % 3 + 1 ))
|
||||||
|
v=0
|
||||||
|
while [[ $v -lt $vuln_count ]]; do
|
||||||
|
printf "[!] Potential vulnerability: %s\n" "${cve_ids[$((RANDOM % ${#cve_ids[@]}))]}"
|
||||||
|
rand_sleep
|
||||||
|
v=$(( v + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Phase 4 — exploit
|
||||||
|
printf "[*] Loading exploit module...\n"; rand_sleep
|
||||||
|
printf "[*] Bypassing firewall rules...\n"; rand_sleep
|
||||||
|
printf "[*] Injecting payload"
|
||||||
|
dots=0
|
||||||
|
while [[ $dots -lt 6 ]]; do
|
||||||
|
printf "."
|
||||||
|
rand_sleep
|
||||||
|
dots=$(( dots + 1 ))
|
||||||
|
done
|
||||||
|
printf "\n"
|
||||||
|
printf "[+] Shell obtained on %s\n" "$target"
|
||||||
|
rand_sleep
|
||||||
|
|
||||||
|
# Phase 5 — hash dumping
|
||||||
|
printf "[*] Dumping password hashes...\n"
|
||||||
|
rand_sleep
|
||||||
|
for u in "${users[@]}"; do
|
||||||
|
printf " %-12s : \$6\$%s\n" "$u" "$(rand_hash)"
|
||||||
|
rand_sleep
|
||||||
|
done
|
||||||
|
|
||||||
|
# Phase 6 — cracking
|
||||||
|
printf "[*] Cracking hashes (wordlist: rockyou.txt)...\n"
|
||||||
|
rand_sleep
|
||||||
|
cracked=$(( RANDOM % ${#users[@]} + 1 ))
|
||||||
|
c=0
|
||||||
|
while [[ $c -lt $cracked ]]; do
|
||||||
|
printf "[+] Cracked: %-12s -> %s\n" \
|
||||||
|
"${users[$c]}" "${passwords[$((RANDOM % ${#passwords[@]}))]}"
|
||||||
|
rand_sleep
|
||||||
|
c=$(( c + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "\n[+] -------- ACCESS GRANTED -------- [+]\n"
|
||||||
|
printf "[*] Cleaning logs on %s...\n" "$target"
|
||||||
|
rand_sleep
|
||||||
|
printf "[+] Done. Have a nice day.\n"
|
||||||
|
printf "\n"
|
||||||
|
done
|
||||||
|
) & local sub_pid=$!
|
||||||
|
|
||||||
|
IFS= read -r -n 1 -s _ </dev/tty
|
||||||
|
kill -- -"$sub_pid" 2>/dev/null || kill "$sub_pid" 2>/dev/null
|
||||||
|
wait "$sub_pid" 2>/dev/null
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f hack
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
656
profile.d/git.sh
Normal file
656
profile.d/git.sh
Normal file
@@ -0,0 +1,656 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
|
||||||
|
# Protected by the BSD3 license. Please read bellow for details.
|
||||||
|
#
|
||||||
|
# * Redistribution and use in source and binary forms,
|
||||||
|
# * with or without modification, are permitted provided
|
||||||
|
# * that the following conditions are met:
|
||||||
|
# *
|
||||||
|
# * Redistributions of source code must retain the above
|
||||||
|
# * copyright notice, this list of conditions and the
|
||||||
|
# * following disclaimer.
|
||||||
|
# *
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# * copyright notice, this list of conditions and the following
|
||||||
|
# * disclaimer in the documentation and/or other materials
|
||||||
|
# * provided with the distribution.
|
||||||
|
# *
|
||||||
|
# * Neither the name of the copyright holder nor the names
|
||||||
|
# * of any other contributors may be used to endorse or
|
||||||
|
# * promote products derived from this software without
|
||||||
|
# * specific prior written permission.
|
||||||
|
# *
|
||||||
|
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# * OF SUCH DAMAGE.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Built-in defaults (can be overridden from [git] section in profile.conf)
|
||||||
|
: "${GIT_MAIN_BRANCH:=main}"
|
||||||
|
: "${GIT_DEFAULT_REMOTE:=origin}"
|
||||||
|
: "${GIT_WIP_PREFIX:=wip}"
|
||||||
|
: "${GIT_GACP_AUTO_ADD:=1}"
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Internal helper: ensure git is available and cwd is a git worktree
|
||||||
|
_git_require_repo()
|
||||||
|
{
|
||||||
|
if ! command -v git >/dev/null 2>&1; then
|
||||||
|
disp E "git command not found."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
disp E "Current directory is not inside a git repository."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Internal helper: return default branch from remote HEAD, fallback to config
|
||||||
|
_git_default_branch()
|
||||||
|
{
|
||||||
|
local remote="${1:-$GIT_DEFAULT_REMOTE}"
|
||||||
|
local head
|
||||||
|
|
||||||
|
head=$(git symbolic-ref --quiet --short "refs/remotes/${remote}/HEAD" 2>/dev/null) || true
|
||||||
|
if [[ -n $head ]]; then
|
||||||
|
printf "%s\n" "${head#"${remote}"/}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s\n" "$GIT_MAIN_BRANCH"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Display compact git status + branch tracking information
|
||||||
|
# Usage: gst [path]
|
||||||
|
gst()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o h --long help -n 'gst' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"gst --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "gst: Display short git status and branch tracking info.\n"
|
||||||
|
printf "Usage: gst [path]\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"gst --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local target="${1:-.}"
|
||||||
|
git -C "$target" status --short --branch
|
||||||
|
}
|
||||||
|
export -f gst
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Show a readable commit graph
|
||||||
|
# Usage: ggraph [-n limit]
|
||||||
|
ggraph()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o hn: --long help,limit: -n 'ggraph' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"ggraph --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
local limit=30
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "ggraph: Display decorated git history graph.\n"
|
||||||
|
printf "Usage: ggraph [-n limit]\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-n, --limit\tNumber of commits to display (default: 30)\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-n|--limit)
|
||||||
|
limit="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"ggraph --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $limit =~ ^[0-9]+$ ]] || {
|
||||||
|
disp E "Invalid limit: must be a positive integer."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_require_repo || return 1
|
||||||
|
git log --graph --decorate --oneline --all --max-count="$limit"
|
||||||
|
}
|
||||||
|
export -f ggraph
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Sync current branch with remote (fetch + rebase)
|
||||||
|
# Usage: gsync [remote]
|
||||||
|
gsync()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o h --long help -n 'gsync' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"gsync --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "gsync: Fetch and rebase current branch onto its remote tracking branch.\n"
|
||||||
|
printf "Usage: gsync [remote]\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"gsync --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
_git_require_repo || return 1
|
||||||
|
|
||||||
|
local remote="${1:-$GIT_DEFAULT_REMOTE}"
|
||||||
|
local branch upstream
|
||||||
|
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
||||||
|
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
|
||||||
|
|
||||||
|
disp I "Fetching from $remote..."
|
||||||
|
git fetch --prune "$remote" || return 1
|
||||||
|
|
||||||
|
if [[ -z $upstream ]]; then
|
||||||
|
disp W "No upstream configured for $branch, skipping rebase."
|
||||||
|
disp I "Set one with: git branch --set-upstream-to ${remote}/${branch} ${branch}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
disp I "Rebasing $branch onto $upstream..."
|
||||||
|
git rebase "$upstream"
|
||||||
|
}
|
||||||
|
export -f gsync
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Add, commit, and push changes with automatic pull/rebase if needed
|
||||||
|
# Usage: gacp -m "message" [file1 file2 ...]
|
||||||
|
gacp()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o ham: --long help,auto,message: -n 'gacp' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"gacp --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
local msg="" auto_add="$GIT_GACP_AUTO_ADD"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "gacp: Run git add, git commit, and git push in one command.\n"
|
||||||
|
printf "Usage: gacp [-a] -m \"message\" [file1 file2 ...]\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-a, --auto\tAutomatically add all modified files (git add -A)\n"
|
||||||
|
printf "\t-m, --message\tCommit message (mandatory)\n"
|
||||||
|
printf "\n"
|
||||||
|
printf "If files are provided, only those paths are added (-a is ignored).\n"
|
||||||
|
printf "If no file is provided and -a is active, all changes are added with git add -A.\n"
|
||||||
|
printf "Default for -a can be set via GIT_GACP_AUTO_ADD in profile.conf.\n"
|
||||||
|
printf "If the remote branch moved forward, gacp pulls with rebase before pushing.\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-a|--auto)
|
||||||
|
auto_add=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-m|--message)
|
||||||
|
msg="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"gacp --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
_git_require_repo || return 1
|
||||||
|
|
||||||
|
if [[ -z $msg ]]; then
|
||||||
|
disp E "Missing commit message. Use -m or --message."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local branch upstream remote tracking_branch behind counts
|
||||||
|
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
||||||
|
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
|
||||||
|
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
auto_add=0
|
||||||
|
disp I "Adding selected paths..."
|
||||||
|
git add -- "$@" || return 1
|
||||||
|
elif [[ $auto_add -eq 1 ]]; then
|
||||||
|
disp I "Adding all changes..."
|
||||||
|
git add -A || return 1
|
||||||
|
else
|
||||||
|
disp E "No files specified. Use -a/--auto or provide file paths."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
disp W "No staged changes to commit."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
disp I "Creating commit..."
|
||||||
|
git commit -m "$msg" || return 1
|
||||||
|
|
||||||
|
if [[ -n $upstream ]]; then
|
||||||
|
remote="${upstream%%/*}"
|
||||||
|
tracking_branch="${upstream#*/}"
|
||||||
|
else
|
||||||
|
remote="$GIT_DEFAULT_REMOTE"
|
||||||
|
tracking_branch="$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
disp I "Fetching from $remote..."
|
||||||
|
git fetch --prune "$remote" || return 1
|
||||||
|
|
||||||
|
if git rev-parse --verify --quiet "refs/remotes/${remote}/${tracking_branch}" >/dev/null; then
|
||||||
|
counts=$(git rev-list --left-right --count HEAD..."${remote}/${tracking_branch}" 2>/dev/null) || return 1
|
||||||
|
read -r _ behind <<< "$counts"
|
||||||
|
if [[ ${behind:-0} -gt 0 ]]; then
|
||||||
|
disp I "Remote branch is ahead, rebasing before push..."
|
||||||
|
git pull --rebase "$remote" "$tracking_branch" || return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $upstream ]]; then
|
||||||
|
disp I "Pushing to $upstream..."
|
||||||
|
git push || return 1
|
||||||
|
else
|
||||||
|
disp I "Pushing and setting upstream to ${remote}/${branch}..."
|
||||||
|
git push -u "$remote" "$branch" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
disp I "gacp complete."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f gacp
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Reset local branch to exact upstream state (stash local changes first)
|
||||||
|
# Usage: greset [target]
|
||||||
|
greset()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o hx --long help,with-ignored -n 'greset' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"greset --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
local clean_ignored=0
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "greset: Reset current branch to upstream, stashing local changes first.\n"
|
||||||
|
printf "Usage: greset [target]\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-x, --with-ignored\tAlso remove ignored files (git clean -fdx)\n"
|
||||||
|
printf "\n"
|
||||||
|
printf "Default target is current branch upstream (@{u}).\n"
|
||||||
|
printf "If no upstream exists, fallback target is <remote>/<branch>.\n"
|
||||||
|
printf "This command stashes local modifications (tracked + untracked),\n"
|
||||||
|
printf "drops local unpushed commits by hard-reset, and cleans untracked files.\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-x|--with-ignored)
|
||||||
|
clean_ignored=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"greset --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
_git_require_repo || return 1
|
||||||
|
|
||||||
|
local branch upstream target remote old_head stash_msg stash_out stash_created=0 dropped=0
|
||||||
|
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
||||||
|
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
|
||||||
|
target="$1"
|
||||||
|
|
||||||
|
if [[ -z $target ]]; then
|
||||||
|
if [[ -n $upstream ]]; then
|
||||||
|
target="$upstream"
|
||||||
|
else
|
||||||
|
remote="$GIT_DEFAULT_REMOTE"
|
||||||
|
target="${remote}/${branch}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $remote ]]; then
|
||||||
|
remote="${target%%/*}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
old_head=$(git rev-parse HEAD 2>/dev/null) || return 1
|
||||||
|
|
||||||
|
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then
|
||||||
|
stash_msg="greset:${branch}:$(date +'%Y-%m-%d %H:%M:%S')"
|
||||||
|
disp I "Stashing local changes as '$stash_msg'..."
|
||||||
|
stash_out=$(git stash push -u -m "$stash_msg" 2>&1) || {
|
||||||
|
disp E "Failed to stash local changes."
|
||||||
|
printf "%s\n" "$stash_out"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
[[ $stash_out != "No local changes to save"* ]] && stash_created=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
disp I "Fetching from $remote..."
|
||||||
|
git fetch --prune "$remote" || return 1
|
||||||
|
|
||||||
|
if ! git rev-parse --verify --quiet "$target" >/dev/null; then
|
||||||
|
disp E "Target '$target' does not exist."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dropped=$(git rev-list --count "${target}..${old_head}" 2>/dev/null || printf "0")
|
||||||
|
|
||||||
|
disp W "Hard-resetting $branch to $target..."
|
||||||
|
git reset --hard "$target" || return 1
|
||||||
|
|
||||||
|
if (( clean_ignored )); then
|
||||||
|
git clean -fdx || return 1
|
||||||
|
else
|
||||||
|
git clean -fd || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( stash_created )); then
|
||||||
|
disp I "Local changes were stashed. Use 'git stash list' and 'git stash pop' when needed."
|
||||||
|
fi
|
||||||
|
disp I "greset complete. Dropped local-only commits: $dropped"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f greset
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Create a quick WIP commit for local checkpointing
|
||||||
|
# Usage: gwip [message]
|
||||||
|
gwip()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o h --long help -n 'gwip' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"gwip --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "gwip: Create a local checkpoint commit with all tracked/untracked changes.\n"
|
||||||
|
printf "Usage: gwip [message]\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"gwip --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
_git_require_repo || return 1
|
||||||
|
|
||||||
|
local msg
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
msg="$*"
|
||||||
|
else
|
||||||
|
msg="$GIT_WIP_PREFIX: $(date +'%Y-%m-%d %H:%M:%S')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git add -A || return 1
|
||||||
|
git commit -m "$msg"
|
||||||
|
}
|
||||||
|
export -f gwip
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Delete merged local branches (except protected branches)
|
||||||
|
# Usage: gprune [-D|--deep] [main-branch]
|
||||||
|
#
|
||||||
|
# Default mode: deletes branches already merged into <main-branch> locally
|
||||||
|
# (git branch --merged).
|
||||||
|
# Deep mode (-D/--deep): additionally deletes branches whose tracking remote
|
||||||
|
# has disappeared (remote pruned after a merge request / pull request merge).
|
||||||
|
# Those branches are detected via `git fetch --prune` + remote-tracking gone.
|
||||||
|
# This is the common case when the MR was merged upstream and the remote
|
||||||
|
# branch was deleted by the forge. Deletion uses `git branch -D` (force)
|
||||||
|
# because the local branch has no merged ancestor that git can verify locally.
|
||||||
|
gprune()
|
||||||
|
{
|
||||||
|
local PARSED deep=0
|
||||||
|
PARSED=$(getopt -o hD --long help,deep -n 'gprune' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"gprune --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "gprune: Delete local branches already merged into main branch.\n\n"
|
||||||
|
printf "Usage: gprune [-D|--deep] [main-branch]\n\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-D, --deep\tAlso delete branches whose upstream was removed\n"
|
||||||
|
printf "\t\t\t(remote deleted after MR/PR merge). Uses 'git branch -D'.\n"
|
||||||
|
printf "\t-h, --help\tDisplay this help screen\n\n"
|
||||||
|
printf "Arguments:\n"
|
||||||
|
printf "\tmain-branch\tBase branch to check merges against (default: auto-detected)\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-D|--deep)
|
||||||
|
deep=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"gprune --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
_git_require_repo || return 1
|
||||||
|
|
||||||
|
local base="${1:-$(_git_default_branch "$GIT_DEFAULT_REMOTE")}" current deleted=0
|
||||||
|
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
||||||
|
|
||||||
|
# ── Standard mode: branches locally merged into base ──────────────────────
|
||||||
|
disp I "Pruning branches merged into $base..."
|
||||||
|
|
||||||
|
while IFS= read -r b; do
|
||||||
|
[[ -z $b ]] && continue
|
||||||
|
[[ $b == "$current" ]] && continue
|
||||||
|
[[ $b == "$base" ]] && continue
|
||||||
|
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
|
||||||
|
git branch -d "$b" >/dev/null 2>&1 && {
|
||||||
|
printf "Deleted (merged): %s\n" "$b"
|
||||||
|
((deleted++))
|
||||||
|
}
|
||||||
|
done < <(git branch --merged "$base" | sed -E 's/^\*?\s*//')
|
||||||
|
|
||||||
|
# ── Deep mode: branches whose remote tracking ref was deleted upstream ─────
|
||||||
|
if (( deep )); then
|
||||||
|
disp I "Deep mode: pruning remote-tracking refs, then checking for gone branches..."
|
||||||
|
git fetch --prune --quiet
|
||||||
|
|
||||||
|
while IFS= read -r b; do
|
||||||
|
[[ -z $b ]] && continue
|
||||||
|
[[ $b == "$current" ]] && continue
|
||||||
|
[[ $b == "$base" ]] && continue
|
||||||
|
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
|
||||||
|
# Verify the upstream is truly gone (not just unset).
|
||||||
|
local upstream
|
||||||
|
upstream=$(git rev-parse --abbrev-ref "${b}@{upstream}" 2>/dev/null)
|
||||||
|
[[ -z "$upstream" ]] && continue # no tracking branch at all — skip
|
||||||
|
# If the remote ref still exists, skip (not deleted upstream).
|
||||||
|
git show-ref --verify --quiet "refs/remotes/$upstream" 2>/dev/null && continue
|
||||||
|
git branch -D "$b" >/dev/null 2>&1 && {
|
||||||
|
printf "Deleted (gone upstream): %s\n" "$b"
|
||||||
|
((deleted++))
|
||||||
|
}
|
||||||
|
done < <(git branch -vv | sed -E 's/^\*?\s*//' | awk '/: gone]/ {print $1}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
(( deleted == 0 )) && disp I "No branches to delete."
|
||||||
|
}
|
||||||
|
export -f gprune
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Print repository root path
|
||||||
|
# Usage: groot
|
||||||
|
groot()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o hg --long help,go -n 'groot' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"groot --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
local do_go=0
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "groot: Display the absolute path of the current repository root.\n"
|
||||||
|
printf "Usage: groot [-g|--go]\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-g, --go\tChange current directory to repository root\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-g|--go)
|
||||||
|
do_go=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"groot --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
_git_require_repo || return 1
|
||||||
|
local root
|
||||||
|
root=$(git rev-parse --show-toplevel) || return 1
|
||||||
|
|
||||||
|
if (( do_go )); then
|
||||||
|
cd "$root" || {
|
||||||
|
disp E "Failed to move to repository root: $root"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s\n" "$root"
|
||||||
|
}
|
||||||
|
export -f groot
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
load_conf git
|
||||||
|
|
||||||
|
# EOF
|
||||||
@@ -36,38 +36,65 @@
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Display list of commands and general informations
|
# Display list of commands and general informations
|
||||||
# Usage: help
|
# Usage: help [command]
|
||||||
help()
|
help()
|
||||||
{
|
{
|
||||||
|
# If a command name is given, delegate to its --help output.
|
||||||
|
if [[ $# -gt 0 && "$1" != "--help" && "$1" != "-h" ]]; then
|
||||||
|
local cmd="$1"
|
||||||
|
if declare -F "$cmd" >/dev/null 2>&1 || command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
"$cmd" --help
|
||||||
|
else
|
||||||
|
disp E "Unknown command: $cmd"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC2154 # color code in disp.sh
|
# shellcheck disable=SC2154 # color code in disp.sh
|
||||||
# shellcheck disable=SC2059 # printf format is a color variable
|
# shellcheck disable=SC2059 # printf format is a color variable
|
||||||
printf "${BIWhite}Welcome to your profile! Here is a list of available commands:${DEFAULTCOL}\n\n"
|
printf "${BIWhite}Welcome to your profile! Here is a list of available commands:${DEFAULTCOL}\n\n"
|
||||||
printf "busy\t\tMonitor /dev/urandom for a hex pattern — look busy\n"
|
printf "busy\t\tMonitor /dev/urandom for a hex pattern — look busy\n"
|
||||||
printf "check_updates\tCheck for new versions of profile\n"
|
printf "check_updates\tCheck for new versions of profile\n"
|
||||||
printf "clean\t\tErase backup files in given directories, optionally recursive\n"
|
printf "clean\t\tErase backup files in given directories, optionally recursive\n"
|
||||||
|
printf "conf_dump\tDisplay the profile configuration file\n"
|
||||||
|
printf "conf_save\tSave or update a key=value pair in a profile configuration section\n"
|
||||||
printf "disp\t\tDisplay formatted info/warning/error/debug messages\n"
|
printf "disp\t\tDisplay formatted info/warning/error/debug messages\n"
|
||||||
printf "dwl\t\tDownload a URL using curl, wget, or fetch transparently\n"
|
printf "dwl\t\tDownload a URL using curl, wget, or fetch transparently\n"
|
||||||
printf "expandlist\tExpand glob expressions into a quoted, separated list\n"
|
printf "expandlist\tExpand glob expressions into a quoted, separated list\n"
|
||||||
|
printf "fake_compile\tSimulate a long compilation process — look busy\n"
|
||||||
printf "file_stats\tDisplay file size statistics for a path\n"
|
printf "file_stats\tDisplay file size statistics for a path\n"
|
||||||
printf "findbig\t\tFind the biggest files in the given or current directory\n"
|
printf "findbig\t\tFind the biggest files in the given or current directory\n"
|
||||||
printf "finddead\tFind dead symbolic links in the given or current directory\n"
|
printf "finddead\tFind dead symbolic links in the given or current directory\n"
|
||||||
printf "findzero\tFind empty files in the given or current directory\n"
|
printf "findzero\tFind empty files in the given or current directory\n"
|
||||||
|
printf "gacp\t\tAdd, commit and push changes (auto-pull if needed)\n"
|
||||||
printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n"
|
printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n"
|
||||||
|
printf "ggraph\t\tDisplay decorated git history graph\n"
|
||||||
|
printf "gprune\t\tDelete local branches already merged, or after remote deletion (MR / PR)\n"
|
||||||
|
printf "greset\t\tReset branch to upstream (stash local, drop local commits)\n"
|
||||||
|
printf "groot\t\tDisplay repository root path (or cd to it with -g)\n"
|
||||||
|
printf "gsync\t\tFetch and rebase current branch onto upstream\n"
|
||||||
|
printf "gst\t\tDisplay short git status and branch tracking info\n"
|
||||||
|
printf "gwip\t\tCreate a quick WIP checkpoint commit\n"
|
||||||
printf "gpid\t\tGive the list of PIDs matching the given process name(s)\n"
|
printf "gpid\t\tGive the list of PIDs matching the given process name(s)\n"
|
||||||
|
printf "hack\t\tSimulate a dramatic hacking sequence — look dangerous\n"
|
||||||
printf "isipv4\t\tTell if the given parameter is a valid IPv4 address\n"
|
printf "isipv4\t\tTell if the given parameter is a valid IPv4 address\n"
|
||||||
printf "isipv6\t\tTell if the given parameter is a valid IPv6 address\n"
|
printf "isipv6\t\tTell if the given parameter is a valid IPv6 address\n"
|
||||||
printf "ku\t\tKill all processes owned by the given user name or ID\n"
|
printf "ku\t\tKill all processes owned by the given user name or ID\n"
|
||||||
printf "matrix\t\tConsole screensaver with Matrix-style digital rain (binary, kana, ascii charset)\n"
|
printf "matrix\t\tConsole screensaver with Matrix-style digital rain (binary, kana, ascii charset)\n"
|
||||||
printf "mcd\t\tCreate a directory and immediately move into it\n"
|
printf "mcd\t\tCreate a directory and immediately move into it\n"
|
||||||
|
printf "mdcat\t\tRender Markdown files in terminal with colors and code frames\n"
|
||||||
printf "meteo\t\tDisplay weather forecast for the configured or given city\n"
|
printf "meteo\t\tDisplay weather forecast for the configured or given city\n"
|
||||||
printf "myextip\t\tGet information about your public IP address\n"
|
printf "myextip\t\tGet information about your public IP address\n"
|
||||||
printf "pkgs\t\tSearch for a pattern in installed package names (dpkg/rpm, supports -i)\n"
|
printf "pkgf\t\tFind which installed package owns a given file (distro-aware)\n"
|
||||||
|
printf "pkgs\t\tSearch for a pattern in installed package names (distro-aware)\n"
|
||||||
printf "ppg\t\tLook for the given pattern in running processes\n"
|
printf "ppg\t\tLook for the given pattern in running processes\n"
|
||||||
printf "ppn\t\tList processes matching an exact command name\n"
|
printf "ppn\t\tList processes matching an exact command name\n"
|
||||||
printf "ppu\t\tList processes owned by a specific user\n"
|
printf "ppu\t\tList processes owned by a specific user\n"
|
||||||
printf "profile_upgrade\tUpgrade profile to the latest version (git pull or archive)\n"
|
printf "profile_upgrade\tUpgrade profile to the latest version (git pull or archive)\n"
|
||||||
printf "pwdscore\tCalculate the strength score of a given password\n"
|
printf "pwdscore\tCalculate the strength score of a given password\n"
|
||||||
printf "rain\t\tConsole screensaver with falling-rain effect (multiple color themes)\n"
|
printf "rain\t\tConsole screensaver with falling-rain effect (multiple color themes)\n"
|
||||||
|
printf "rainbow\t\tFull-screen rainbow screensaver using background colors only\n"
|
||||||
printf "rmhost\t\tRemove host (name and IP) from SSH known_hosts; supports --all-users as root\n"
|
printf "rmhost\t\tRemove host (name and IP) from SSH known_hosts; supports --all-users as root\n"
|
||||||
printf "rmspc\t\tReplace spaces in filenames with underscores (or a custom character)\n"
|
printf "rmspc\t\tReplace spaces in filenames with underscores (or a custom character)\n"
|
||||||
printf "setlocale\tSet console locale to any installed locale\n"
|
printf "setlocale\tSet console locale to any installed locale\n"
|
||||||
@@ -78,11 +105,12 @@ help()
|
|||||||
printf "showinfo\tDisplay welcome banner and system information (figlet + neofetch/fastfetch)\n"
|
printf "showinfo\tDisplay welcome banner and system information (figlet + neofetch/fastfetch)\n"
|
||||||
printf "ssr\t\tSSH into a server as root, forwarding extra ssh options\n"
|
printf "ssr\t\tSSH into a server as root, forwarding extra ssh options\n"
|
||||||
printf "taz\t\tCompress files and directories into a chosen archive format\n"
|
printf "taz\t\tCompress files and directories into a chosen archive format\n"
|
||||||
|
printf "term_set\tSet TERM to the best available terminal capability (auto-detect or honour config)\n"
|
||||||
printf "urlencode\tURL-encode a string\n"
|
printf "urlencode\tURL-encode a string\n"
|
||||||
printf "utaz\t\tSmartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm)\n"
|
printf "utaz\t\tSmartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm)\n"
|
||||||
printf "ver\t\tDisplay the installed profile version\n\n"
|
printf "ver\t\tDisplay the installed profile version\n\n"
|
||||||
|
|
||||||
printf "\nPlease use <command> --help to obtain usage details.\n"
|
printf "\nPlease use <command> --help or help <command> to obtain usage details.\n"
|
||||||
}
|
}
|
||||||
export -f help
|
export -f help
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -127,6 +127,228 @@ export -f meteo
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Display a system information block using only /proc and /sys — no external tools.
|
||||||
|
# Usage: pinfo
|
||||||
|
pinfo()
|
||||||
|
{
|
||||||
|
local _row
|
||||||
|
_row() {
|
||||||
|
printf " %b%-12s%b %b%s%b\n" "$_lbl" "$1" "$_rst" "$_val" "$2" "$_rst"
|
||||||
|
}
|
||||||
|
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o h --long help -n 'pinfo' -- "$@")
|
||||||
|
# shellcheck disable=SC2181
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"pinfo --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "pinfo: Display system information from /proc and /sys (no external tools required).\n"
|
||||||
|
printf "Usage: pinfo\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"pinfo --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Hostname ---
|
||||||
|
local hostname_str
|
||||||
|
if [[ -r /proc/sys/kernel/hostname ]]; then
|
||||||
|
read -r hostname_str < /proc/sys/kernel/hostname
|
||||||
|
else
|
||||||
|
hostname_str="${HOSTNAME:-unknown}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- OS release (security: parsed line by line, never sourced) ---
|
||||||
|
local os_name="Unknown" os_version=""
|
||||||
|
if [[ -r /etc/os-release ]]; then
|
||||||
|
local _osr_key _osr_val
|
||||||
|
while IFS='=' read -r _osr_key _osr_val; do
|
||||||
|
_osr_val="${_osr_val//\"/}"
|
||||||
|
case "$_osr_key" in
|
||||||
|
NAME) os_name="$_osr_val" ;;
|
||||||
|
VERSION_ID) os_version="$_osr_val" ;;
|
||||||
|
esac
|
||||||
|
done < /etc/os-release
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Kernel release ---
|
||||||
|
local kernel_str="unknown"
|
||||||
|
[[ -r /proc/sys/kernel/osrelease ]] && read -r kernel_str < /proc/sys/kernel/osrelease
|
||||||
|
|
||||||
|
# --- Architecture (set by bash at startup from the ELF interpreter) ---
|
||||||
|
local arch_str="${HOSTTYPE:-unknown}"
|
||||||
|
|
||||||
|
# --- Uptime (seconds from /proc/uptime) ---
|
||||||
|
local uptime_str="unknown"
|
||||||
|
if [[ -r /proc/uptime ]]; then
|
||||||
|
local _upraw
|
||||||
|
read -r _upraw _ < /proc/uptime
|
||||||
|
local _upsec="${_upraw%%.*}"
|
||||||
|
local _days=$(( _upsec / 86400 ))
|
||||||
|
local _hours=$(( (_upsec % 86400) / 3600 ))
|
||||||
|
local _mins=$(( (_upsec % 3600) / 60 ))
|
||||||
|
local _secs=$(( _upsec % 60 ))
|
||||||
|
uptime_str=""
|
||||||
|
(( _days > 0 )) && uptime_str+="${_days}d "
|
||||||
|
(( _hours > 0 || _days > 0 )) && uptime_str+="${_hours}h "
|
||||||
|
uptime_str+="${_mins}m ${_secs}s"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Load average ---
|
||||||
|
local load_str="unknown"
|
||||||
|
if [[ -r /proc/loadavg ]]; then
|
||||||
|
local _l1 _l5 _l15
|
||||||
|
read -r _l1 _l5 _l15 _ < /proc/loadavg
|
||||||
|
load_str="${_l1} ${_l5} ${_l15} (1/5/15 min)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- CPU model and logical/physical core count (pure bash) ---
|
||||||
|
local cpu_model="unknown" cpu_threads=0
|
||||||
|
local -A _seen_cores=()
|
||||||
|
local _cur_phys="" _cpu_line
|
||||||
|
if [[ -r /proc/cpuinfo ]]; then
|
||||||
|
while IFS= read -r _cpu_line; do
|
||||||
|
case "$_cpu_line" in
|
||||||
|
"model name"*|"Model name"*|"Hardware"*)
|
||||||
|
[[ "$cpu_model" == "unknown" ]] && cpu_model="${_cpu_line#*: }"
|
||||||
|
;;
|
||||||
|
"processor"*)
|
||||||
|
(( cpu_threads++ ))
|
||||||
|
;;
|
||||||
|
"physical id"*)
|
||||||
|
_cur_phys="${_cpu_line#*: }"
|
||||||
|
;;
|
||||||
|
"core id"*)
|
||||||
|
_seen_cores["${_cur_phys}:${_cpu_line#*: }"]=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < /proc/cpuinfo
|
||||||
|
fi
|
||||||
|
local cpu_cores="${#_seen_cores[@]}"
|
||||||
|
(( cpu_cores == 0 )) && cpu_cores=$cpu_threads
|
||||||
|
|
||||||
|
# --- Memory (pure bash, /proc/meminfo, values in kB) ---
|
||||||
|
local mem_total=0 mem_available=0 swap_total=0 swap_free=0
|
||||||
|
if [[ -r /proc/meminfo ]]; then
|
||||||
|
local _mkey _mval _munit
|
||||||
|
while read -r _mkey _mval _munit; do
|
||||||
|
case "${_mkey%:}" in
|
||||||
|
MemTotal) mem_total="$_mval" ;;
|
||||||
|
MemAvailable) mem_available="$_mval" ;;
|
||||||
|
SwapTotal) swap_total="$_mval" ;;
|
||||||
|
SwapFree) swap_free="$_mval" ;;
|
||||||
|
esac
|
||||||
|
done < /proc/meminfo
|
||||||
|
fi
|
||||||
|
local mem_total_mib=$(( mem_total / 1024 ))
|
||||||
|
local mem_used_mib=$(( (mem_total - mem_available) / 1024 ))
|
||||||
|
local swap_total_mib=$(( swap_total / 1024 ))
|
||||||
|
local swap_used_mib=$(( (swap_total - swap_free) / 1024 ))
|
||||||
|
|
||||||
|
# --- Process count (glob over numeric /proc entries) ---
|
||||||
|
local proc_count=0 _pdir
|
||||||
|
for _pdir in /proc/[0-9]*/; do
|
||||||
|
[[ -d "$_pdir" ]] && (( proc_count++ ))
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Shell and terminal ---
|
||||||
|
local shell_str="${BASH:-bash} ${BASH_VERSION%\(*}"
|
||||||
|
local term_str="${TERM:-unknown}"
|
||||||
|
|
||||||
|
# --- GPU (no external tools required; sysfs first, then /proc/bus/pci) ---
|
||||||
|
local -a gpu_list=()
|
||||||
|
local _gdir _gname _gline
|
||||||
|
# Preferred: sysfs drm — each card has a device/vendor+device pair readable
|
||||||
|
# without root. The human-readable name comes from the uevent file.
|
||||||
|
for _gdir in /sys/class/drm/card[0-9]*/device; do
|
||||||
|
[[ -d "$_gdir" ]] || continue
|
||||||
|
_gname=""
|
||||||
|
# Try uevent: contains PCI_ID and sometimes DRIVER
|
||||||
|
if [[ -r "$_gdir/uevent" ]]; then
|
||||||
|
local _uev_driver="" _uev_pci=""
|
||||||
|
local _uev_line
|
||||||
|
while IFS='=' read -r _uev_key _uev_val; do
|
||||||
|
case "$_uev_key" in
|
||||||
|
DRIVER) _uev_driver="$_uev_val" ;;
|
||||||
|
PCI_ID) _uev_pci="$_uev_val" ;;
|
||||||
|
esac
|
||||||
|
done < "$_gdir/uevent"
|
||||||
|
[[ -n "$_uev_pci" ]] && _gname="PCI ${_uev_pci}"
|
||||||
|
[[ -n "$_uev_driver" ]] && _gname+=" (${_uev_driver})"
|
||||||
|
fi
|
||||||
|
# Better: label file written by driver (e.g. amdgpu, i915)
|
||||||
|
if [[ -r "$_gdir/label" ]]; then
|
||||||
|
read -r _gname < "$_gdir/label"
|
||||||
|
elif [[ -r "$_gdir/../label" ]]; then
|
||||||
|
read -r _gname < "$_gdir/../label"
|
||||||
|
fi
|
||||||
|
# Better still: product name from hwmon or power supply description
|
||||||
|
# Try modalias vendor/device text via /sys/.../subsystem_device not always human
|
||||||
|
# Last resort: readable model via drm connector name
|
||||||
|
[[ -z "$_gname" ]] && _gname="unknown GPU"
|
||||||
|
gpu_list+=("$_gname")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Fallback: scan /proc/bus/pci/devices — field 1 is vendor:device hex, field 14 is name
|
||||||
|
if (( ${#gpu_list[@]} == 0 )) && [[ -r /proc/bus/pci/devices ]]; then
|
||||||
|
while IFS=$'\t' read -r _pci_bus _pci_id _pci_irq _rest _pci_name; do
|
||||||
|
# Display class is in the high 16 bits of field 2 (vendor:device word)
|
||||||
|
# /proc/bus/pci/devices col 1 is busdevfn, col 2 is vendorID<<16|deviceID
|
||||||
|
# The class 0x03xx is "Display controller / VGA compatible"
|
||||||
|
case "$_pci_name" in
|
||||||
|
*VGA*|*Display*|*3D*|*GPU*|*Graphics*|*Radeon*|*GeForce*|*Intel*Iris*|*Intel*UHD*|*Intel*HD*Graphics*)
|
||||||
|
gpu_list+=("$_pci_name")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < /proc/bus/pci/devices
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Render ---
|
||||||
|
local _lbl="${BIWhite:-}" _val="${ICyan:-}" _rst="${DEFAULTCOL:-}"
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
_row "Hostname" "$hostname_str"
|
||||||
|
_row "OS" "${os_name}${os_version:+ $os_version}"
|
||||||
|
_row "Kernel" "$kernel_str"
|
||||||
|
_row "Arch" "$arch_str"
|
||||||
|
_row "Uptime" "$uptime_str"
|
||||||
|
_row "Load avg" "$load_str"
|
||||||
|
_row "CPU" "$cpu_model"
|
||||||
|
_row "Cores" "${cpu_cores} physical, ${cpu_threads} logical"
|
||||||
|
if (( ${#gpu_list[@]} > 0 )); then
|
||||||
|
local _gi
|
||||||
|
for _gi in "${!gpu_list[@]}"; do
|
||||||
|
local _gpu_lbl="GPU"
|
||||||
|
(( ${#gpu_list[@]} > 1 )) && _gpu_lbl="GPU $(( _gi + 1 ))"
|
||||||
|
_row "$_gpu_lbl" "${gpu_list[$_gi]}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
_row "Memory" "${mem_used_mib} MiB used / ${mem_total_mib} MiB total"
|
||||||
|
if (( swap_total > 0 )); then
|
||||||
|
_row "Swap" "${swap_used_mib} MiB used / ${swap_total_mib} MiB total"
|
||||||
|
fi
|
||||||
|
_row "Processes" "$proc_count"
|
||||||
|
_row "Shell" "$shell_str"
|
||||||
|
_row "Terminal" "$term_str"
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
export -f pinfo
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Display system general information
|
# Display system general information
|
||||||
# Usage: showinfo
|
# Usage: showinfo
|
||||||
@@ -179,16 +401,7 @@ showinfo()
|
|||||||
elif command -v fastfetch >/dev/null 2>&1; then
|
elif command -v fastfetch >/dev/null 2>&1; then
|
||||||
fastfetch
|
fastfetch
|
||||||
else
|
else
|
||||||
(
|
pinfo "$@"
|
||||||
if [[ -s /etc/os-release ]]; then
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
. /etc/os-release
|
|
||||||
printf "%s %s\n" "$NAME" "$VERSION"
|
|
||||||
else
|
|
||||||
cat /proc/version
|
|
||||||
fi
|
|
||||||
printf "Uptime: %s\n" "$(uptime -p)"
|
|
||||||
)
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
export -f showinfo
|
export -f showinfo
|
||||||
|
|||||||
@@ -36,27 +36,69 @@
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Download a resource using curl, wget, or fetch.
|
# Download a resource using curl, wget, or fetch.
|
||||||
# Usage: dwl <url> [output_file]
|
# Usage: dwl [-t <seconds>] [-r|--resume] <url> [output_file]
|
||||||
dwl()
|
dwl()
|
||||||
{
|
{
|
||||||
|
local timeout=""
|
||||||
|
local resume=0
|
||||||
|
|
||||||
|
case "${DWL_DEFAULT_RESUME,,}" in
|
||||||
|
1|true|yes|on)
|
||||||
|
resume=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Parse leading options before the URL.
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--help|-h)
|
--help|-h)
|
||||||
echo "Usage: dwl <url> [output_file]"
|
echo "Usage: dwl [-t <seconds>|--timeout <seconds>] [-r|--resume] [--no-resume] <url> [output_file]"
|
||||||
echo "Downloads a resource using curl, wget, or fetch."
|
echo "Downloads a resource using curl, wget, or fetch."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Arguments:"
|
echo "Arguments:"
|
||||||
|
echo " -t, --timeout Maximum time in seconds to wait for the transfer."
|
||||||
|
echo " -r, --resume Resume an interrupted download when possible (file output only)."
|
||||||
|
echo " --no-resume Disable resume mode even if enabled in config."
|
||||||
echo " url The full URL to download (http/https/ftp)."
|
echo " url The full URL to download (http/https/ftp)."
|
||||||
echo " output_file (Optional) Path to save the file. If omitted, prints to stdout."
|
echo " output_file (Optional) Path to save the file. If omitted, prints to stdout."
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
"")
|
-t|--timeout)
|
||||||
echo "Error: URL argument is missing." >&2
|
[[ -z "${2:-}" || ! "${2:-}" =~ ^[0-9]+$ ]] && {
|
||||||
echo "Try 'get_resource --help' for usage." >&2
|
echo "Error: --timeout requires a positive integer argument." >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
timeout="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-r|--resume)
|
||||||
|
resume=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-resume)
|
||||||
|
resume=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Error: Unknown option '$1'. Try 'dwl --help'." >&2
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
case "$1" in
|
case "${1:-}" in
|
||||||
|
"")
|
||||||
|
echo "Error: URL argument is missing." >&2
|
||||||
|
echo "Try 'dwl --help' for usage." >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
http://*|https://*|ftp://*) ;;
|
http://*|https://*|ftp://*) ;;
|
||||||
*)
|
*)
|
||||||
echo "Error: '$1' does not look like a valid URL. Must start with http://, https://, or ftp://" >&2
|
echo "Error: '$1' does not look like a valid URL. Must start with http://, https://, or ftp://" >&2
|
||||||
@@ -65,35 +107,49 @@ dwl()
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local output="$2"
|
local output="${2:-}"
|
||||||
|
|
||||||
# Honour preferred tool from configuration; fall back to auto-detection.
|
# Honour preferred tool from configuration; fall back to auto-detection.
|
||||||
local preferred="${DWL_PREFERRED_TOOL:-}"
|
local preferred="${DWL_PREFERRED_TOOL:-}"
|
||||||
|
|
||||||
_try_curl()
|
_try_curl()
|
||||||
{
|
{
|
||||||
|
local args=(-sL)
|
||||||
|
[[ -n "$timeout" ]] && args+=(--max-time "$timeout" --connect-timeout "$timeout")
|
||||||
if [[ -z "$output" ]]; then
|
if [[ -z "$output" ]]; then
|
||||||
curl -sL "$url"
|
if (( resume == 1 )); then
|
||||||
|
echo "Warning: --resume requires an output file; ignoring resume mode for stdout." >&2
|
||||||
|
fi
|
||||||
|
curl "${args[@]}" "$url"
|
||||||
else
|
else
|
||||||
curl -sL -o "$output" "$url"
|
(( resume == 1 )) && args+=(-C -)
|
||||||
|
curl "${args[@]}" -o "$output" "$url"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_try_wget()
|
_try_wget()
|
||||||
{
|
{
|
||||||
|
local args=(-q)
|
||||||
|
[[ -n "$timeout" ]] && args+=(--timeout="$timeout")
|
||||||
|
(( resume == 1 )) && args+=(-c)
|
||||||
if [[ -z "$output" ]]; then
|
if [[ -z "$output" ]]; then
|
||||||
wget -qO- "$url"
|
wget "${args[@]}" -O- "$url"
|
||||||
else
|
else
|
||||||
wget -q -O "$output" "$url"
|
wget "${args[@]}" -O "$output" "$url"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_try_fetch()
|
_try_fetch()
|
||||||
{
|
{
|
||||||
|
local args=()
|
||||||
|
[[ -n "$timeout" ]] && args+=(-T "$timeout")
|
||||||
|
if (( resume == 1 )); then
|
||||||
|
echo "Warning: resume mode is not supported with fetch; continuing without resume." >&2
|
||||||
|
fi
|
||||||
if [[ -z "$output" ]]; then
|
if [[ -z "$output" ]]; then
|
||||||
fetch -o - "$url"
|
fetch "${args[@]}" -o - "$url"
|
||||||
else
|
else
|
||||||
fetch -o "$output" "$url"
|
fetch "${args[@]}" -o "$output" "$url"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
# checking available binaries in a fixed priority order.
|
# checking available binaries in a fixed priority order.
|
||||||
# Echoes one of: apt dnf yum zypper pacman apk portage xbps nix
|
# Echoes one of: apt dnf yum zypper pacman apk portage xbps nix
|
||||||
# Returns 1 if no known package manager could be identified.
|
# Returns 1 if no known package manager could be identified.
|
||||||
_get_pkgmgr()
|
get_pkgmgr()
|
||||||
{
|
{
|
||||||
local distro_id="" distro_like=""
|
local distro_id="" distro_like=""
|
||||||
if [[ -r /etc/os-release ]]; then
|
if [[ -r /etc/os-release ]]; then
|
||||||
@@ -56,30 +56,48 @@ _get_pkgmgr()
|
|||||||
for id in $distro_id $distro_like; do
|
for id in $distro_id $distro_like; do
|
||||||
case "${id,,}" in
|
case "${id,,}" in
|
||||||
debian|ubuntu|linuxmint|raspbian|pop|kali|elementary|zorin|neon|parrot)
|
debian|ubuntu|linuxmint|raspbian|pop|kali|elementary|zorin|neon|parrot)
|
||||||
echo "apt"; return 0 ;;
|
echo "apt"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
fedora)
|
fedora)
|
||||||
echo "dnf"; return 0 ;;
|
echo "dnf"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
rhel|centos|rocky|almalinux|ol|scientific|amzn)
|
rhel|centos|rocky|almalinux|ol|scientific|amzn)
|
||||||
command -v dnf >/dev/null 2>&1 && { echo "dnf"; return 0; }
|
command -v dnf >/dev/null 2>&1 && { echo "dnf"; return 0; }
|
||||||
echo "yum"; return 0 ;;
|
echo "yum"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
opensuse*|sles|sled)
|
opensuse*|sles|sled)
|
||||||
echo "zypper"; return 0 ;;
|
echo "zypper"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
arch|manjaro|endeavouros|garuda|artix|cachyos)
|
arch|manjaro|endeavouros|garuda|artix|cachyos)
|
||||||
echo "pacman"; return 0 ;;
|
echo "pacman"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
alpine)
|
alpine)
|
||||||
echo "apk"; return 0 ;;
|
echo "apk"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
gentoo)
|
gentoo)
|
||||||
echo "portage"; return 0 ;;
|
echo "portage"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
void)
|
void)
|
||||||
echo "xbps"; return 0 ;;
|
echo "xbps"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
nixos)
|
nixos)
|
||||||
echo "nix"; return 0 ;;
|
echo "nix"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Fallback: check for binaries in priority order.
|
# Fallback: check for binaries in priority order.
|
||||||
local bin
|
local bin
|
||||||
for bin in apt-get dnf yum zypper pacman apk emerge xbps-install nix-env; do
|
for bin in apt apt-get dnf yum zypper pacman apk emerge xbps-install nix-env; do
|
||||||
command -v "$bin" >/dev/null 2>&1 && {
|
command -v "$bin" >/dev/null 2>&1 && {
|
||||||
case "$bin" in
|
case "$bin" in
|
||||||
apt-get) echo "apt" ;;
|
apt-get) echo "apt" ;;
|
||||||
@@ -94,7 +112,7 @@ _get_pkgmgr()
|
|||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
export -f _get_pkgmgr
|
export -f get_pkgmgr
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -150,7 +168,7 @@ pkgs()
|
|||||||
(( ignore_case )) && grep_opt="-i"
|
(( ignore_case )) && grep_opt="-i"
|
||||||
|
|
||||||
local pkgmgr
|
local pkgmgr
|
||||||
pkgmgr=$(_get_pkgmgr) || {
|
pkgmgr=$(get_pkgmgr) || {
|
||||||
disp E "No usable package manager could be detected on this system."
|
disp E "No usable package manager could be detected on this system."
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
@@ -176,6 +194,84 @@ export -f pkgs
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Find which installed package a file belongs to.
|
||||||
|
# Usage: pkgf <file>
|
||||||
|
pkgf()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
PARSED=$(getopt -o h --long help -n 'pkgf' -- "$@")
|
||||||
|
# shellcheck disable=SC2181
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"pkgf --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "pkgf: Find which installed package owns a given file.\n\n"
|
||||||
|
printf "Usage: pkgf [options] <file>\n\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-h, --help\tDisplay this help screen\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid option: $1"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local file="$1"
|
||||||
|
[[ -z "$file" ]] && {
|
||||||
|
disp E "Please specify a file path."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local pkgmgr
|
||||||
|
pkgmgr=$(get_pkgmgr) || {
|
||||||
|
disp E "No usable package manager could be detected on this system."
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$pkgmgr" in
|
||||||
|
apt)
|
||||||
|
dpkg -S "$file"
|
||||||
|
;;
|
||||||
|
dnf|yum|zypper)
|
||||||
|
rpm -qf "$file"
|
||||||
|
;;
|
||||||
|
pacman)
|
||||||
|
pacman -Qo "$file"
|
||||||
|
;;
|
||||||
|
apk)
|
||||||
|
apk info --who-owns "$file"
|
||||||
|
;;
|
||||||
|
portage)
|
||||||
|
qfile "$file"
|
||||||
|
;;
|
||||||
|
xbps)
|
||||||
|
xbps-query -o "$file"
|
||||||
|
;;
|
||||||
|
nix)
|
||||||
|
nix-locate "$file"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Package manager '$pkgmgr' is not supported by pkgf."
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
export -f pkgf
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
load_conf "packages"
|
load_conf "packages"
|
||||||
|
|
||||||
# EOF
|
# EOF
|
||||||
|
|||||||
@@ -39,13 +39,36 @@
|
|||||||
# Usage: ppg <string>
|
# Usage: ppg <string>
|
||||||
ppg()
|
ppg()
|
||||||
{
|
{
|
||||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
local PARSED
|
||||||
|
|
||||||
|
PARSED=$(getopt -o h --long help -n 'ppg' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"ppg --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
printf "ppg: Search processes matching the given string.\n\n"
|
printf "ppg: Search processes matching the given string.\n\n"
|
||||||
printf "Usage: ppg <string>\n\n"
|
printf "Usage: ppg <string>\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\t\tDisplay this help screen\n"
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
return 0
|
return 0
|
||||||
fi
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"ppg --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
if [[ -z "$1" ]]; then
|
if [[ -z "$1" ]]; then
|
||||||
disp E "Usage: ppg <string>"
|
disp E "Usage: ppg <string>"
|
||||||
return 1
|
return 1
|
||||||
@@ -81,13 +104,36 @@ export -f ppg
|
|||||||
# Usage: ppu <username>
|
# Usage: ppu <username>
|
||||||
ppu()
|
ppu()
|
||||||
{
|
{
|
||||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
local PARSED
|
||||||
|
|
||||||
|
PARSED=$(getopt -o h --long help -n 'ppu' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"ppu --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
printf "ppu: List processes owned by a specific user.\n\n"
|
printf "ppu: List processes owned by a specific user.\n\n"
|
||||||
printf "Usage: ppu <username>\n\n"
|
printf "Usage: ppu <username>\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\t\tDisplay this help screen\n"
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
return 0
|
return 0
|
||||||
fi
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"ppu --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
if [[ -z "$1" ]]; then
|
if [[ -z "$1" ]]; then
|
||||||
disp E "Usage: ppu <username>"
|
disp E "Usage: ppu <username>"
|
||||||
return 1
|
return 1
|
||||||
@@ -106,13 +152,36 @@ export -f ppu
|
|||||||
# Usage: ppn <command_name>
|
# Usage: ppn <command_name>
|
||||||
ppn()
|
ppn()
|
||||||
{
|
{
|
||||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
local PARSED
|
||||||
|
|
||||||
|
PARSED=$(getopt -o h --long help -n 'ppn' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"ppn --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
printf "ppn: List processes by exact command name (no path/parameters).\n\n"
|
printf "ppn: List processes by exact command name (no path/parameters).\n\n"
|
||||||
printf "Usage: ppn <command_name>\n\n"
|
printf "Usage: ppn <command_name>\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\t\tDisplay this help screen\n"
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
return 0
|
return 0
|
||||||
fi
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"ppn --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
if [[ -z "$1" ]]; then
|
if [[ -z "$1" ]]; then
|
||||||
disp E "Usage: ppn <command_name>"
|
disp E "Usage: ppn <command_name>"
|
||||||
return 1
|
return 1
|
||||||
@@ -130,16 +199,39 @@ export -f ppn
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Get PID list of the given process name
|
# Get PID list of the given process name
|
||||||
# Usage: ppid <process_name [process_name2 ...]>
|
# Usage: gpid <process_name [process_name2 ...]>
|
||||||
gpid()
|
gpid()
|
||||||
{
|
{
|
||||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
local PARSED
|
||||||
|
|
||||||
|
PARSED=$(getopt -o h --long help -n 'gpid' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"gpid --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
printf "gpid: Get PID list of the given process name.\n\n"
|
printf "gpid: Get PID list of the given process name.\n\n"
|
||||||
printf "Usage: gpid <process_name [process_name2 ...]>\n\n"
|
printf "Usage: gpid <process_name [process_name2 ...]>\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\t\tDisplay this help screen\n"
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
return 0
|
return 0
|
||||||
fi
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"gpid --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
if [[ -z "$1" ]]; then
|
if [[ -z "$1" ]]; then
|
||||||
disp E "Usage: gpid <process_name [process_name2 ...]>"
|
disp E "Usage: gpid <process_name [process_name2 ...]>"
|
||||||
return 1
|
return 1
|
||||||
@@ -190,23 +282,119 @@ export -f gpid
|
|||||||
# Usage: ku <username1 [username2 ...]>
|
# Usage: ku <username1 [username2 ...]>
|
||||||
ku()
|
ku()
|
||||||
{
|
{
|
||||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
local PARSED
|
||||||
printf "ku: Kill all processes owned by the given users.\n\n"
|
local dry_run=0
|
||||||
printf "Usage: ku <username1 [username2 ...]>\n\n"
|
local -a signal_opt=()
|
||||||
printf "Options:\n"
|
|
||||||
printf "\t-h, --help\t\tDisplay this help screen\n"
|
PARSED=$(getopt -o hns: --long help,dry-run,signal: -n 'ku' -- "$@")
|
||||||
return 0
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
fi
|
if [[ $? -ne 0 ]]; then
|
||||||
if [[ -z "$1" ]]; then
|
disp E "Invalid options, use \"ku --help\" to display usage."
|
||||||
disp E "Usage: ku <username1 [username2 ...]>"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "ku: Kill all processes owned by the given users.\n\n"
|
||||||
|
printf "Usage: ku [options] <username1 [username2 ...]>\n\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
|
printf "\t-n, --dry-run\t\tDisplay commands without executing them\n"
|
||||||
|
printf "\t-s, --signal SIG\tSignal to send (overrides KU_DEFAULT_SIGNAL)\n"
|
||||||
|
printf "\t --signal=SIG\tSame as above\n"
|
||||||
|
printf "\t -SIG / -NUM\t\tSignal format compatible with kill\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-n|--dry-run)
|
||||||
|
dry_run=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-s|--signal)
|
||||||
|
signal_opt=(-s "$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
signal_opt=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Accept kill-style signal forms not handled by getopt, before usernames.
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-[0-9]*|-SIG*|-[[:alpha:]]*)
|
||||||
|
signal_opt=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
disp E "Unknown option: $1, use \"ku --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$1" ]]; then
|
||||||
|
disp E "Usage: ku [options] <username1 [username2 ...]>"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local u
|
||||||
for u in "$@"; do
|
for u in "$@"; do
|
||||||
if ! id "$u" >/dev/null 2>&1; then
|
if ! id "$u" >/dev/null 2>&1; then
|
||||||
disp E "User '$u' does not exist."
|
disp E "User '$u' does not exist."
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
killall ${KU_DEFAULT_SIGNAL:+-${KU_DEFAULT_SIGNAL}} -u "$u"
|
local cmd
|
||||||
|
|
||||||
|
# killall (psmisc) preferred; fall back to pkill (procps-ng).
|
||||||
|
if command -v killall >/dev/null 2>&1; then
|
||||||
|
cmd=(killall)
|
||||||
|
if [[ ${#signal_opt[@]} -gt 0 ]]; then
|
||||||
|
cmd+=("${signal_opt[@]}")
|
||||||
|
elif [[ -n "${KU_DEFAULT_SIGNAL:-}" ]]; then
|
||||||
|
cmd+=("-${KU_DEFAULT_SIGNAL}")
|
||||||
|
fi
|
||||||
|
cmd+=(-u "$u")
|
||||||
|
elif command -v pkill >/dev/null 2>&1; then
|
||||||
|
cmd=(pkill)
|
||||||
|
# Translate killall's -s SIGNAME form to pkill's -SIGNAME form.
|
||||||
|
if [[ ${#signal_opt[@]} -eq 2 && "${signal_opt[0]}" == "-s" ]]; then
|
||||||
|
cmd+=("-${signal_opt[1]}")
|
||||||
|
elif [[ ${#signal_opt[@]} -gt 0 ]]; then
|
||||||
|
cmd+=("${signal_opt[@]}")
|
||||||
|
elif [[ -n "${KU_DEFAULT_SIGNAL:-}" ]]; then
|
||||||
|
cmd+=("-${KU_DEFAULT_SIGNAL}")
|
||||||
|
fi
|
||||||
|
cmd+=(-u "$u")
|
||||||
|
else
|
||||||
|
disp E "ku: neither 'killall' (psmisc) nor 'pkill' (procps) is available."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( dry_run )); then
|
||||||
|
printf "DRY-RUN: "
|
||||||
|
printf "%q " "${cmd[@]}"
|
||||||
|
printf "\n"
|
||||||
|
else
|
||||||
|
"${cmd[@]}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@@ -219,32 +407,109 @@ export -f ku
|
|||||||
# Usage: kt <pid> [kill_options]
|
# Usage: kt <pid> [kill_options]
|
||||||
kt()
|
kt()
|
||||||
{
|
{
|
||||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
local PARSED
|
||||||
|
local dry_run=0
|
||||||
|
local -a pre_kill_opts=()
|
||||||
|
|
||||||
|
PARSED=$(getopt -o hns: --long help,dry-run,signal: -n 'kt' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"kt --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
printf "kt: Kill all children of a process then the process (kill tree).\n\n"
|
printf "kt: Kill all children of a process then the process (kill tree).\n\n"
|
||||||
printf "Usage: kt <pid> [kill_options]\n\n"
|
printf "Usage: kt [options] <pid> [kill_options]\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\t\tDisplay this help screen\n"
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
|
printf "\t-n, --dry-run\t\tDisplay kill commands without executing them\n"
|
||||||
|
printf "\t-s, --signal SIG\tSignal to send to process tree\n"
|
||||||
|
printf "\t --signal=SIG\tSame as above\n"
|
||||||
|
printf "\t -SIG / -NUM\t\tSignal format compatible with kill\n"
|
||||||
return 0
|
return 0
|
||||||
fi
|
;;
|
||||||
|
-n|--dry-run)
|
||||||
|
dry_run=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-s|--signal)
|
||||||
|
pre_kill_opts+=(-s "$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
pre_kill_opts+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Accept kill-style signal forms not handled by getopt, before the PID.
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-[0-9]*|-SIG*|-[[:alpha:]]*)
|
||||||
|
pre_kill_opts+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
disp E "Unknown option: $1, use \"kt --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
if [[ -z "$1" ]]; then
|
if [[ -z "$1" ]]; then
|
||||||
disp E "Usage: kt <pid>"
|
disp E "Usage: kt [options] <pid> [kill_options]"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local parent_pid="$1"
|
local parent_pid="$1"
|
||||||
shift
|
shift
|
||||||
|
|
||||||
|
local -a kill_opts=("${pre_kill_opts[@]}" "$@")
|
||||||
|
|
||||||
if [[ "$parent_pid" == "0" || "$parent_pid" == "1" ]]; then
|
if [[ "$parent_pid" == "0" || "$parent_pid" == "1" ]]; then
|
||||||
disp E "Safety abort: Refusing to kill PID $parent_pid (system critical)."
|
disp E "Safety abort: Refusing to kill PID $parent_pid (system critical)."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local children_pids
|
local children_pids
|
||||||
children_pids=$(pgrep -P "$parent_pid")
|
children_pids=$(pgrep -P "$parent_pid" 2>/dev/null || true)
|
||||||
|
|
||||||
|
local pid
|
||||||
for pid in $children_pids; do
|
for pid in $children_pids; do
|
||||||
kt "$pid" "$@" || break
|
if (( dry_run )); then
|
||||||
|
kt --dry-run "$pid" "${kill_opts[@]}" || break
|
||||||
|
else
|
||||||
|
kt "$pid" "${kill_opts[@]}" || break
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
kill "$@" "$parent_pid"
|
|
||||||
|
if (( dry_run )); then
|
||||||
|
local cmd=(kill "${kill_opts[@]}" "$parent_pid")
|
||||||
|
printf "DRY-RUN: "
|
||||||
|
printf "%q " "${cmd[@]}"
|
||||||
|
printf "\n"
|
||||||
|
else
|
||||||
|
kill "${kill_opts[@]}" "$parent_pid"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
export -f kt
|
export -f kt
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ load_theme()
|
|||||||
[PROMPT_COLOR_ERR_BG]=1 [PROMPT_COLOR_ERR_FG]=1 [PROMPT_COLOR_ERR_MARK]=1
|
[PROMPT_COLOR_ERR_BG]=1 [PROMPT_COLOR_ERR_FG]=1 [PROMPT_COLOR_ERR_MARK]=1
|
||||||
[PROMPT_COLOR_ROOT_FG]=1 [PROMPT_COLOR_USER_FG]=1
|
[PROMPT_COLOR_ROOT_FG]=1 [PROMPT_COLOR_USER_FG]=1
|
||||||
[PROMPT_COLOR_DIR_FG]=1
|
[PROMPT_COLOR_DIR_FG]=1
|
||||||
|
[PROMPT_COLOR_CTX_FG]=1
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---- Colour variable names exported by disp.sh --------------------------
|
# ---- Colour variable names exported by disp.sh --------------------------
|
||||||
@@ -168,6 +169,7 @@ load_theme()
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Dynamically switch the prompt theme for the current shell session.
|
# Dynamically switch the prompt theme for the current shell session.
|
||||||
# Calls load_theme to apply the new colour values immediately, then updates
|
# Calls load_theme to apply the new colour values immediately, then updates
|
||||||
@@ -178,27 +180,86 @@ load_theme()
|
|||||||
set_theme()
|
set_theme()
|
||||||
{
|
{
|
||||||
local theme_dir="${PROMPT_THEME_DIR:-${MYPATH}/profile.d/themes}"
|
local theme_dir="${PROMPT_THEME_DIR:-${MYPATH}/profile.d/themes}"
|
||||||
|
local preview=0
|
||||||
|
local save=0
|
||||||
|
local list_only=0
|
||||||
|
local theme_name=""
|
||||||
|
|
||||||
# -- help mode -----------------------------------------------------------
|
while [[ $# -gt 0 ]]; do
|
||||||
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
printf "set_theme: Switch the prompt colour theme for the current shell session.\n\n"
|
printf "set_theme: Switch the prompt colour theme for the current shell session.\n\n"
|
||||||
printf "Usage: set_theme [options] [theme]\n\n"
|
printf "Usage: set_theme [options] [theme]\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf " -h, --help Display this help screen\n"
|
printf "\t-h, --help\tDisplay this help screen\n"
|
||||||
printf " -l, --list List available themes (default when no argument is given)\n\n"
|
printf "\t-l, --list\tList available themes (default when no argument is given)\n"
|
||||||
|
printf "\t-p, --preview\tPreview a theme without applying it\n"
|
||||||
|
printf "\t-S, --save\tSave theme to configuration\n\n"
|
||||||
printf "Arguments:\n"
|
printf "Arguments:\n"
|
||||||
printf " theme Bare theme name (e.g. 'dark') or an explicit path to a .theme file.\n"
|
printf "\ttheme \tBare theme name (e.g. 'dark') or an explicit path to a .theme file.\n"
|
||||||
printf " Themes are searched in: %s\n" "$theme_dir"
|
printf "\t \tThemes are searched in: %s\n" "$theme_dir"
|
||||||
printf " Override with PROMPT_THEME_DIR in profile.conf [prompt].\n\n"
|
printf "\t \tOverride with PROMPT_THEME_DIR in profile.conf [prompt].\n\n"
|
||||||
printf "Examples:\n"
|
printf "Examples:\n"
|
||||||
printf " set_theme — list available themes\n"
|
printf "\tset_theme \t— list available themes\n"
|
||||||
printf " set_theme dark — apply the dark theme\n"
|
printf "\tset_theme dark \t— apply the dark theme\n"
|
||||||
printf " set_theme ~/my.theme — apply a theme by path\n"
|
printf "\tset_theme -p dark \t— preview the dark theme\n"
|
||||||
|
printf "\tset_theme -S \t— save current theme in config\n"
|
||||||
|
printf "\tset_theme -S dark \t— apply and save the dark theme\n"
|
||||||
|
printf "\tset_theme ~/my.theme\t— apply a theme by path\n"
|
||||||
return 0
|
return 0
|
||||||
|
;;
|
||||||
|
-l|--list)
|
||||||
|
list_only=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-p|--preview)
|
||||||
|
preview=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-S|--save)
|
||||||
|
save=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
disp E "Unknown option: $1, use \"set_theme --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -n "$theme_name" ]]; then
|
||||||
|
disp E "Too many arguments. Usage: set_theme [options] [theme]"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
theme_name="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
if [[ -n "$theme_name" ]]; then
|
||||||
|
disp E "Too many arguments. Usage: set_theme [options] [theme]"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
theme_name="$1"
|
||||||
|
shift
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -- list mode -----------------------------------------------------------
|
# -- list mode -----------------------------------------------------------
|
||||||
if [[ $# -eq 0 || "$1" == "-l" || "$1" == "--list" ]]; then
|
if (( list_only )) || [[ -z "$theme_name" && $preview -eq 0 ]]; then
|
||||||
|
if (( save )); then
|
||||||
|
if [[ -n "${PROMPT_THEME:-}" ]]; then
|
||||||
|
conf_save "prompt" "PROMPT_THEME" "$PROMPT_THEME" || return 1
|
||||||
|
disp I "Saved current prompt theme '$PROMPT_THEME' to configuration."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
disp E "No active theme to save. Apply a theme first or pass one with -S."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
printf "Available themes in %s:\n" "$theme_dir"
|
printf "Available themes in %s:\n" "$theme_dir"
|
||||||
local f name
|
local f name
|
||||||
for f in "$theme_dir"/*.theme; do
|
for f in "$theme_dir"/*.theme; do
|
||||||
@@ -214,14 +275,77 @@ set_theme()
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if (( preview )) && [[ -z "$theme_name" ]]; then
|
||||||
|
disp E "--preview requires a theme argument."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( preview && save )); then
|
||||||
|
disp E "--preview and --save cannot be used together."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -- preview mode --------------------------------------------------------
|
||||||
|
if (( preview )); then
|
||||||
|
local old_theme="${PROMPT_THEME:-}"
|
||||||
|
local -a old_prompt_color_vars=()
|
||||||
|
local _v
|
||||||
|
for _v in ${!PROMPT_COLOR_@}; do
|
||||||
|
old_prompt_color_vars+=("$_v=${!_v}")
|
||||||
|
done
|
||||||
|
|
||||||
|
set_colors
|
||||||
|
load_theme "$theme_name" || {
|
||||||
|
set_colors
|
||||||
|
if [[ -n "$old_theme" ]]; then
|
||||||
|
load_theme "$old_theme" || true
|
||||||
|
export PROMPT_THEME="$old_theme"
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "Preview for theme '%s':\n" "$theme_name"
|
||||||
|
printf "%b [ 13:37:00 ] %b%b [ 0 %b• (5s) %b] %b user@host%b [ git:main +1/-0 ] %b\n" \
|
||||||
|
"${PROMPT_COLOR_TIME_FG:-}${PROMPT_COLOR_TIME_BG:-}" "${DEFAULTCOL:-}" \
|
||||||
|
"${PROMPT_COLOR_OK_FG:-}${PROMPT_COLOR_BAR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_OK_MARK:-}${PROMPT_COLOR_BAR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_OK_FG:-}${PROMPT_COLOR_BAR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_USER_FG:-}${PROMPT_COLOR_BAR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_CTX_FG:-}${PROMPT_COLOR_BAR_BG:-}" "${DEFAULTCOL:-}"
|
||||||
|
printf "%b [ 13:37:00 ] %b%b [ 0 %b• (5s) %b] %b user@host%b [ git:main +1/-0 ] %b\n" \
|
||||||
|
"${PROMPT_COLOR_TIME_FG:-}${PROMPT_COLOR_TIME_BG:-}" "${DEFAULTCOL:-}" \
|
||||||
|
"${PROMPT_COLOR_OK_FG:-}${PROMPT_COLOR_ERR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_ERR_MARK:-}${PROMPT_COLOR_ERR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_OK_FG:-}${PROMPT_COLOR_ERR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_USER_FG:-}${PROMPT_COLOR_BAR_BG:-}" \
|
||||||
|
"${PROMPT_COLOR_CTX_FG:-}${PROMPT_COLOR_BAR_BG:-}" "${DEFAULTCOL:-}"
|
||||||
|
printf "%b/path/to/dir \$ %b\n" \
|
||||||
|
"${PROMPT_COLOR_DIR_FG:-}" "${DEFAULTCOL:-}"
|
||||||
|
|
||||||
|
set_colors
|
||||||
|
if [[ -n "$old_theme" ]]; then
|
||||||
|
load_theme "$old_theme" || true
|
||||||
|
export PROMPT_THEME="$old_theme"
|
||||||
|
fi
|
||||||
|
for _v in "${old_prompt_color_vars[@]}"; do
|
||||||
|
export "${_v%%=*}=${_v#*=}"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
# -- apply mode ----------------------------------------------------------
|
# -- apply mode ----------------------------------------------------------
|
||||||
local theme_name="$1"
|
|
||||||
|
|
||||||
# Reset colours to defaults before loading the new theme
|
# Reset colours to defaults before loading the new theme
|
||||||
set_colors
|
set_colors
|
||||||
load_theme "$theme_name" || return 1
|
load_theme "$theme_name" || return 1
|
||||||
|
|
||||||
export PROMPT_THEME="$theme_name"
|
export PROMPT_THEME="$theme_name"
|
||||||
|
if (( save )); then
|
||||||
|
conf_save "prompt" "PROMPT_THEME" "$theme_name" || return 1
|
||||||
|
disp I "Prompt theme set to $theme_name and saved to configuration."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
disp I "Prompt theme set to $theme_name."
|
disp I "Prompt theme set to $theme_name."
|
||||||
}
|
}
|
||||||
export -f set_theme
|
export -f set_theme
|
||||||
@@ -285,6 +409,103 @@ function timer_stop
|
|||||||
# command, the elapsed time of the last command, and the current user and host.
|
# command, the elapsed time of the last command, and the current user and host.
|
||||||
set_prompt()
|
set_prompt()
|
||||||
{
|
{
|
||||||
|
local _prompt_git_segment
|
||||||
|
_prompt_git_segment()
|
||||||
|
{
|
||||||
|
# Fast path: skip git lookup when feature is disabled.
|
||||||
|
[[ "${PROMPT_SHOW_GIT:-1}" == "0" ]] && return 0
|
||||||
|
|
||||||
|
local branch
|
||||||
|
branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || \
|
||||||
|
branch=$(git rev-parse --short HEAD 2>/dev/null) || return 0
|
||||||
|
|
||||||
|
local dirty="" sync="" timed_out=0
|
||||||
|
local _git_timeout="${PROMPT_GIT_TIMEOUT:-2}"
|
||||||
|
|
||||||
|
# Build a timeout wrapper if the 'timeout' command is available;
|
||||||
|
# fall back to direct execution on systems that lack it.
|
||||||
|
local -a _tw=()
|
||||||
|
command -v timeout >/dev/null 2>&1 && _tw=(timeout "$_git_timeout")
|
||||||
|
|
||||||
|
if [[ "${PROMPT_SHOW_GIT_STATUS:-1}" != "0" ]]; then
|
||||||
|
local _ec
|
||||||
|
|
||||||
|
# Dirty check — working tree
|
||||||
|
"${_tw[@]}" git diff --no-ext-diff --quiet --ignore-submodules -- 2>/dev/null
|
||||||
|
_ec=$?
|
||||||
|
if [[ $_ec -eq 124 ]]; then
|
||||||
|
timed_out=1
|
||||||
|
elif [[ $_ec -ne 0 ]]; then
|
||||||
|
dirty="*"
|
||||||
|
else
|
||||||
|
# Dirty check — index
|
||||||
|
"${_tw[@]}" git diff --cached --no-ext-diff --quiet --ignore-submodules -- 2>/dev/null
|
||||||
|
_ec=$?
|
||||||
|
if [[ $_ec -eq 124 ]]; then
|
||||||
|
timed_out=1
|
||||||
|
elif [[ $_ec -ne 0 ]]; then
|
||||||
|
dirty="*"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $timed_out -eq 0 ]]; then
|
||||||
|
local counts ahead behind
|
||||||
|
counts=$("${_tw[@]}" git rev-list --left-right --count "@{upstream}...HEAD" 2>/dev/null)
|
||||||
|
_ec=$?
|
||||||
|
if [[ $_ec -eq 124 ]]; then
|
||||||
|
timed_out=1
|
||||||
|
elif [[ "$counts" =~ ^([0-9]+)[[:space:]]+([0-9]+)$ ]]; then
|
||||||
|
behind="${BASH_REMATCH[1]}"
|
||||||
|
ahead="${BASH_REMATCH[2]}"
|
||||||
|
if [[ "$ahead" -gt 0 || "$behind" -gt 0 ]]; then
|
||||||
|
sync=" +${ahead}/-${behind}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $timed_out -eq 1 ]]; then
|
||||||
|
printf "%s" "git:${branch}?"
|
||||||
|
else
|
||||||
|
printf "%s" "git:${branch}${dirty}${sync}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
local _prompt_conda_env
|
||||||
|
_prompt_conda_env()
|
||||||
|
{
|
||||||
|
[[ "${PROMPT_SHOW_CONDA:-1}" == "0" ]] && return 0
|
||||||
|
[[ -z "${CONDA_DEFAULT_ENV:-}" ]] && return 0
|
||||||
|
printf "%s" "conda:${CONDA_DEFAULT_ENV}"
|
||||||
|
}
|
||||||
|
|
||||||
|
local _prompt_venv_env
|
||||||
|
_prompt_venv_env()
|
||||||
|
{
|
||||||
|
[[ "${PROMPT_SHOW_VENV:-1}" == "0" ]] && return 0
|
||||||
|
[[ -n "${CONDA_DEFAULT_ENV:-}" ]] && return 0
|
||||||
|
[[ -z "${VIRTUAL_ENV:-}" ]] && return 0
|
||||||
|
printf "%s" "venv:${VIRTUAL_ENV##*/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
local _prompt_session_markers
|
||||||
|
_prompt_session_markers()
|
||||||
|
{
|
||||||
|
[[ "${PROMPT_SHOW_SESSION:-1}" == "0" ]] && return 0
|
||||||
|
|
||||||
|
local -a tags=()
|
||||||
|
[[ -n "${SSH_CONNECTION:-}" ]] && tags+=("ssh")
|
||||||
|
[[ -n "${TMUX:-}" ]] && tags+=("tmux")
|
||||||
|
[[ -n "${STY:-}" ]] && tags+=("screen")
|
||||||
|
[[ ${#tags[@]} -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
local out="${tags[0]}" i
|
||||||
|
for ((i=1; i<${#tags[@]}; ++i)); do
|
||||||
|
out+="+${tags[i]}"
|
||||||
|
done
|
||||||
|
printf "%s" "$out"
|
||||||
|
}
|
||||||
|
|
||||||
local Last_Command=$? # Must come first!
|
local Last_Command=$? # Must come first!
|
||||||
local FancyX='\342\234\227'
|
local FancyX='\342\234\227'
|
||||||
local Checkmark='\342\234\223'
|
local Checkmark='\342\234\223'
|
||||||
@@ -301,6 +522,7 @@ set_prompt()
|
|||||||
local _root_fg="${PROMPT_COLOR_ROOT_FG:-$Red}"
|
local _root_fg="${PROMPT_COLOR_ROOT_FG:-$Red}"
|
||||||
local _user_fg="${PROMPT_COLOR_USER_FG:-$BGreen}"
|
local _user_fg="${PROMPT_COLOR_USER_FG:-$BGreen}"
|
||||||
local _dir_fg="${PROMPT_COLOR_DIR_FG:-$ICyan}"
|
local _dir_fg="${PROMPT_COLOR_DIR_FG:-$ICyan}"
|
||||||
|
local _ctx_fg="${PROMPT_COLOR_CTX_FG:-$BIYellow}"
|
||||||
|
|
||||||
# Begin with time (cursor-save is non-printing; all ANSI sequences wrapped
|
# Begin with time (cursor-save is non-printing; all ANSI sequences wrapped
|
||||||
# in \[...\] so bash does not count them toward the visible line width).
|
# in \[...\] so bash does not count them toward the visible line width).
|
||||||
@@ -330,6 +552,30 @@ set_prompt()
|
|||||||
else
|
else
|
||||||
PS1+="\[${_user_fg}${_bar_bg}\] \\u@\\h"
|
PS1+="\[${_user_fg}${_bar_bg}\] \\u@\\h"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Optional context segment appended at the end of the top bar.
|
||||||
|
local _git_seg _conda_env _venv_env _session_tags _ctx=""
|
||||||
|
_git_seg="$(_prompt_git_segment)"
|
||||||
|
_conda_env="$(_prompt_conda_env)"
|
||||||
|
_venv_env="$(_prompt_venv_env)"
|
||||||
|
_session_tags="$(_prompt_session_markers)"
|
||||||
|
|
||||||
|
local _ctx_disp
|
||||||
|
_ctx_disp()
|
||||||
|
{
|
||||||
|
[[ -n "$1" ]] && {
|
||||||
|
[[ -n "$_ctx" ]] && _ctx+=" | $1" || _ctx="$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ctx_disp "$_git_seg"
|
||||||
|
_ctx_disp "$_conda_env"
|
||||||
|
_ctx_disp "$_venv_env"
|
||||||
|
_ctx_disp "$_session_tags"
|
||||||
|
if [[ -n "$_ctx" ]]; then
|
||||||
|
PS1+="\[${_ctx_fg}${_bar_bg}\] [ ${_ctx} ]"
|
||||||
|
fi
|
||||||
|
|
||||||
PS1+="\[\e[K\e[u\]\[$RESETCOL\]\n"
|
PS1+="\[\e[K\e[u\]\[$RESETCOL\]\n"
|
||||||
# Print the working directory and prompt marker, then reset colour.
|
# Print the working directory and prompt marker, then reset colour.
|
||||||
PS1+="\[${_dir_fg}\]\\w \\\$\[$RESETCOL\] "
|
PS1+="\[${_dir_fg}\]\\w \\\$\[$RESETCOL\] "
|
||||||
|
|||||||
@@ -42,6 +42,34 @@ _rain_build_colors()
|
|||||||
local base_color="$1"
|
local base_color="$1"
|
||||||
RAIN_ENGINE_COLORS=()
|
RAIN_ENGINE_COLORS=()
|
||||||
|
|
||||||
|
local use_truecolor=0
|
||||||
|
# term_set() already sets TERM=*-direct when truecolor is available;
|
||||||
|
# honour COLORTERM as a belt-and-suspenders fallback.
|
||||||
|
[[ "$TERM" == *direct* || "${COLORTERM:-}" == "truecolor" || "${COLORTERM:-}" == "24bit" ]] && use_truecolor=1
|
||||||
|
|
||||||
|
if (( use_truecolor )); then
|
||||||
|
# 24-bit gradient from a near-black shade to a vivid hue.
|
||||||
|
# 20 steps provide smooth depth variation across simultaneous drops.
|
||||||
|
local steps=20
|
||||||
|
local r1 g1 b1 r2 g2 b2
|
||||||
|
case "${base_color}" in
|
||||||
|
green) r1=0; g1=12; b1=2; r2=50; g2=255; b2=90 ;;
|
||||||
|
blue) r1=0; g1=5; b1=25; r2=60; g2=140; b2=255 ;;
|
||||||
|
red) r1=15; g1=0; b1=0; r2=255; g2=40; b2=40 ;;
|
||||||
|
yellow) r1=25; g1=18; b1=0; r2=255; g2=240; b2=50 ;;
|
||||||
|
cyan) r1=0; g1=18; b1=18; r2=50; g2=255; b2=240 ;;
|
||||||
|
*) r1=40; g1=40; b1=45; r2=220; g2=220; b2=255 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local i r g b
|
||||||
|
for ((i = 0; i < steps; i++)); do
|
||||||
|
r=$(( r1 + (r2 - r1) * i / (steps - 1) ))
|
||||||
|
g=$(( g1 + (g2 - g1) * i / (steps - 1) ))
|
||||||
|
b=$(( b1 + (b2 - b1) * i / (steps - 1) ))
|
||||||
|
RAIN_ENGINE_COLORS+=("\e[38;2;${r};${g};${b}m")
|
||||||
|
done
|
||||||
|
else
|
||||||
|
# Fallback: 256-colour palettes.
|
||||||
case $base_color in
|
case $base_color in
|
||||||
green)
|
green)
|
||||||
for i in {22..28} {34..40} {46..48}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
|
for i in {22..28} {34..40} {46..48}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
|
||||||
@@ -57,6 +85,7 @@ _rain_build_colors()
|
|||||||
RAIN_ENGINE_COLORS=("\e[37m" "\e[37;1m")
|
RAIN_ENGINE_COLORS=("\e[37m" "\e[37;1m")
|
||||||
for i in {244..255}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
|
for i in {244..255}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
|
||||||
esac
|
esac
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_rain_build_chars()
|
_rain_build_chars()
|
||||||
@@ -110,12 +139,77 @@ _rain_normalize_speed()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_rain_normalize_density()
|
||||||
|
{
|
||||||
|
local raw_density="$1"
|
||||||
|
|
||||||
|
if [[ ! "$raw_density" =~ ^[0-9]+$ || "$raw_density" -lt 1 ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s" "$raw_density"
|
||||||
|
}
|
||||||
|
|
||||||
|
_rainbow_build_palette()
|
||||||
|
{
|
||||||
|
local palette_width="$1"
|
||||||
|
local use_truecolor="$2"
|
||||||
|
local x=0 wheel_pos=0 band=0 blend=0
|
||||||
|
local red=0 green=0 blue=0
|
||||||
|
|
||||||
|
RAINBOW_BG_PALETTE=()
|
||||||
|
|
||||||
|
if (( palette_width < 1 )); then
|
||||||
|
RAINBOW_BG_PALETTE+=("\e[41m")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( use_truecolor )); then
|
||||||
|
for ((x = 0; x < palette_width; x++)); do
|
||||||
|
wheel_pos=$((x * 1536 / palette_width))
|
||||||
|
band=$((wheel_pos / 256))
|
||||||
|
blend=$((wheel_pos % 256))
|
||||||
|
|
||||||
|
case "$band" in
|
||||||
|
0)
|
||||||
|
red=255; green=$blend; blue=0
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
red=$((255 - blend)); green=255; blue=0
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
red=0; green=255; blue=$blend
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
red=0; green=$((255 - blend)); blue=255
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
red=$blend; green=0; blue=255
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
red=255; green=0; blue=$((255 - blend))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
RAINBOW_BG_PALETTE+=("\e[48;2;${red};${green};${blue}m")
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local ansi_palette=(41 101 43 103 42 102 46 106 44 104 45 105)
|
||||||
|
local ansi_count=${#ansi_palette[@]}
|
||||||
|
|
||||||
|
for ((x = 0; x < palette_width; x++)); do
|
||||||
|
RAINBOW_BG_PALETTE+=("\e[${ansi_palette[x * ansi_count / palette_width]}m")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
_rain_engine()
|
_rain_engine()
|
||||||
{
|
{
|
||||||
local step_duration="$1"
|
local step_duration="$1"
|
||||||
local base_color="$2"
|
local base_color="$2"
|
||||||
local mode="$3"
|
local mode="$3"
|
||||||
local charset="$4"
|
local charset="$4"
|
||||||
|
local density_override="$5"
|
||||||
|
|
||||||
command -v tput >/dev/null 2>&1 || {
|
command -v tput >/dev/null 2>&1 || {
|
||||||
disp E "The program 'tput' is required but not installed."
|
disp E "The program 'tput' is required but not installed."
|
||||||
@@ -175,6 +269,10 @@ _rain_engine()
|
|||||||
frame_sleep="$step_duration"
|
frame_sleep="$step_duration"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
if [[ -n "$density_override" ]]; then
|
||||||
|
max_rain_width="$density_override"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
do_exit()
|
do_exit()
|
||||||
@@ -251,7 +349,11 @@ _rain_engine()
|
|||||||
if ((num_rains < max_rain_width)) && ((100 * RANDOM / 32768 < new_rain_odd)); then
|
if ((num_rains < max_rain_width)) && ((100 * RANDOM / 32768 < new_rain_odd)); then
|
||||||
rain_drop="${rain_chars[rain_tab * RANDOM / 32768]}"
|
rain_drop="${rain_chars[rain_tab * RANDOM / 32768]}"
|
||||||
drop_color="${rain_colors[rain_color_tab * RANDOM / 32768]}"
|
drop_color="${rain_colors[rain_color_tab * RANDOM / 32768]}"
|
||||||
|
if [[ "$mode" == "matrix" ]]; then
|
||||||
|
drop_length=$((max_rain_height * RANDOM / 32768 + 2))
|
||||||
|
else
|
||||||
drop_length=$((max_rain_height * RANDOM / 32768 + 1))
|
drop_length=$((max_rain_height * RANDOM / 32768 + 1))
|
||||||
|
fi
|
||||||
X=$((term_width * RANDOM / 32768 + 1))
|
X=$((term_width * RANDOM / 32768 + 1))
|
||||||
Y=$((1 - drop_length))
|
Y=$((1 - drop_length))
|
||||||
rains=("${rains[@]}" "$X" "$Y" "$rain_drop" "$drop_color" "$drop_length")
|
rains=("${rains[@]}" "$X" "$Y" "$rain_drop" "$drop_color" "$drop_length")
|
||||||
@@ -273,15 +375,17 @@ _rain_engine()
|
|||||||
# Usage: rain [OPTIONS]
|
# Usage: rain [OPTIONS]
|
||||||
rain()
|
rain()
|
||||||
{
|
{
|
||||||
|
local _rain_show_usage
|
||||||
_rain_show_usage()
|
_rain_show_usage()
|
||||||
{
|
{
|
||||||
printf "Usage: rain [OPTIONS]\n"
|
printf "Usage: rain [OPTIONS]\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-s, --speed NUM Set speed value (default: 5 => 0.050s).\n"
|
printf "\t-s, --speed NUM\tSet speed value (default: 5 => 0.050s).\n"
|
||||||
printf "\t Values >=1 use a /100 scale (5 => 0.05s).\n"
|
printf "\t\t\t\tValues >=1 use a /100 scale (5 => 0.05s).\n"
|
||||||
printf "\t Values <1 are interpreted as raw seconds.\n"
|
printf "\t\t\t\tValues <1 are interpreted as raw seconds.\n"
|
||||||
printf "\t-c, --color COLOR Set the color theme (default: white).\n"
|
printf "\t-d, --density NUM\tMaximum number of simultaneous falling elements.\n"
|
||||||
printf "\t-h, --help Display this help message and exit.\n\n"
|
printf "\t-c, --color COLOR\tSet the color theme (default: white).\n"
|
||||||
|
printf "\t-h, --help\t\tDisplay this help message and exit.\n\n"
|
||||||
printf "Available Colors:\n"
|
printf "Available Colors:\n"
|
||||||
printf "\t\e[32mgreen\e[0m\t: Matrix-like green shades\n"
|
printf "\t\e[32mgreen\e[0m\t: Matrix-like green shades\n"
|
||||||
printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n"
|
printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n"
|
||||||
@@ -296,6 +400,11 @@ rain()
|
|||||||
local step_duration
|
local step_duration
|
||||||
step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.050
|
step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.050
|
||||||
local base_color="${RAIN_DEFAULT_COLOR:-white}"
|
local base_color="${RAIN_DEFAULT_COLOR:-white}"
|
||||||
|
local density_override="${RAIN_DEFAULT_DENSITY:-}"
|
||||||
|
|
||||||
|
if [[ -n "$density_override" ]]; then
|
||||||
|
density_override=$(_rain_normalize_density "$density_override") || density_override=""
|
||||||
|
fi
|
||||||
|
|
||||||
while [[ "$#" -gt 0 ]]; do
|
while [[ "$#" -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -323,6 +432,20 @@ rain()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
-d|--density)
|
||||||
|
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
|
||||||
|
density_override=$(_rain_normalize_density "$2") || {
|
||||||
|
disp E "--density requires a positive integer value."
|
||||||
|
_rain_show_usage
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
disp E "--density requires a positive integer value."
|
||||||
|
_rain_show_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
_rain_show_usage
|
_rain_show_usage
|
||||||
return 0
|
return 0
|
||||||
@@ -340,7 +463,7 @@ rain()
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
_rain_engine "$step_duration" "$base_color" "rain" ""
|
_rain_engine "$step_duration" "$base_color" "rain" "" "$density_override"
|
||||||
}
|
}
|
||||||
export -f rain
|
export -f rain
|
||||||
|
|
||||||
@@ -349,16 +472,18 @@ export -f rain
|
|||||||
# Usage: matrix [OPTIONS]
|
# Usage: matrix [OPTIONS]
|
||||||
matrix()
|
matrix()
|
||||||
{
|
{
|
||||||
|
local _matrix_show_usage
|
||||||
_matrix_show_usage()
|
_matrix_show_usage()
|
||||||
{
|
{
|
||||||
printf "Usage: matrix [OPTIONS]\n"
|
printf "Usage: matrix [OPTIONS]\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-s, --speed NUM Set speed value (default: 3.5 => 0.035s).\n"
|
printf "\t-s, --speed NUM\tSet speed value (default: 3.5 => 0.035s).\n"
|
||||||
printf "\t Values >=1 use a /100 scale (3.5 => 0.035s).\n"
|
printf "\t\t\t\tValues >=1 use a /100 scale (3.5 => 0.035s).\n"
|
||||||
printf "\t Values <1 are interpreted as raw seconds.\n"
|
printf "\t\t\t\tValues <1 are interpreted as raw seconds.\n"
|
||||||
printf "\t-c, --color COLOR Set color theme (default: green).\n"
|
printf "\t-d, --density NUM\tMaximum number of simultaneous falling elements.\n"
|
||||||
printf "\t-C, --charset SET Character set: binary, kana, ascii (default: binary).\n"
|
printf "\t-c, --color COLOR\tSet color theme (default: green).\n"
|
||||||
printf "\t-h, --help Display this help message and exit.\n\n"
|
printf "\t-C, --charset SET\tCharacter set: binary, kana, ascii (default: binary).\n"
|
||||||
|
printf "\t-h, --help\t\tDisplay this help message and exit.\n\n"
|
||||||
printf "Example: matrix -C kana -c green --speed 2\n"
|
printf "Example: matrix -C kana -c green --speed 2\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,6 +492,11 @@ matrix()
|
|||||||
step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.035
|
step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.035
|
||||||
local base_color="${MATRIX_DEFAULT_COLOR:-green}"
|
local base_color="${MATRIX_DEFAULT_COLOR:-green}"
|
||||||
local charset="${MATRIX_DEFAULT_CHARSET:-binary}"
|
local charset="${MATRIX_DEFAULT_CHARSET:-binary}"
|
||||||
|
local density_override="${MATRIX_DEFAULT_DENSITY:-}"
|
||||||
|
|
||||||
|
if [[ -n "$density_override" ]]; then
|
||||||
|
density_override=$(_rain_normalize_density "$density_override") || density_override=""
|
||||||
|
fi
|
||||||
|
|
||||||
while [[ "$#" -gt 0 ]]; do
|
while [[ "$#" -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -412,6 +542,20 @@ matrix()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
-d|--density)
|
||||||
|
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
|
||||||
|
density_override=$(_rain_normalize_density "$2") || {
|
||||||
|
disp E "--density requires a positive integer value."
|
||||||
|
_matrix_show_usage
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
disp E "--density requires a positive integer value."
|
||||||
|
_matrix_show_usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
_matrix_show_usage
|
_matrix_show_usage
|
||||||
return 0
|
return 0
|
||||||
@@ -429,10 +573,134 @@ matrix()
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
_rain_engine "$step_duration" "$base_color" "matrix" "$charset"
|
_rain_engine "$step_duration" "$base_color" "matrix" "$charset" "$density_override"
|
||||||
}
|
}
|
||||||
export -f matrix
|
export -f matrix
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Full-screen rainbow screensaver
|
||||||
|
# Usage: rainbow [OPTIONS]
|
||||||
|
rainbow()
|
||||||
|
{
|
||||||
|
local PARSED
|
||||||
|
|
||||||
|
PARSED=$(getopt -o hs: --long help,speed: -n 'rainbow' -- "$@")
|
||||||
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
disp E "Invalid options, use \"rainbow --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _raw_speed="${RAINBOW_DEFAULT_SPEED:-4}"
|
||||||
|
local frame_sleep
|
||||||
|
frame_sleep=$(_rain_normalize_speed "$_raw_speed") || frame_sleep=0.040
|
||||||
|
|
||||||
|
eval set -- "$PARSED"
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
printf "Usage: rainbow [OPTIONS]\n"
|
||||||
|
printf "Options:\n"
|
||||||
|
printf "\t-s, --speed NUM\tSet horizontal color shift speed (default: 4 => 0.040s).\n"
|
||||||
|
printf "\t\t\t\tValues >=1 use a /100 scale (4 => 0.04s).\n"
|
||||||
|
printf "\t\t\t\tValues <1 are interpreted as raw seconds.\n"
|
||||||
|
printf "\t-h, --help\t\tDisplay this help message and exit.\n\n"
|
||||||
|
printf "The rainbow fills the whole terminal with background colors only.\n"
|
||||||
|
printf "It uses truecolor when supported, otherwise ANSI bright backgrounds.\n"
|
||||||
|
printf "Press q to quit.\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-s|--speed)
|
||||||
|
frame_sleep=$(_rain_normalize_speed "$2") || {
|
||||||
|
disp E "--speed requires a numeric value."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
disp E "Invalid options, use \"rainbow --help\" to display usage."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
command -v tput >/dev/null 2>&1 || {
|
||||||
|
disp E "The program 'tput' is required but not installed."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local exit_st=0
|
||||||
|
local term_height=0 term_width=0
|
||||||
|
local use_truecolor=0
|
||||||
|
local offset=0 row=0 col=0 palette_width=0 idx=0
|
||||||
|
local row_line="" ch=""
|
||||||
|
|
||||||
|
sigwinch()
|
||||||
|
{
|
||||||
|
term_width=$(tput cols)
|
||||||
|
term_height=$(tput lines)
|
||||||
|
((term_width < 1)) && term_width=1
|
||||||
|
((term_height < 1)) && term_height=1
|
||||||
|
|
||||||
|
if [[ "$TERM" == *direct* || "${COLORTERM:-}" == "truecolor" || "${COLORTERM:-}" == "24bit" ]]; then
|
||||||
|
use_truecolor=1
|
||||||
|
else
|
||||||
|
use_truecolor=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
palette_width=$term_width
|
||||||
|
_rainbow_build_palette "$palette_width" "$use_truecolor"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_exit()
|
||||||
|
{
|
||||||
|
exit_st=1
|
||||||
|
}
|
||||||
|
|
||||||
|
trap do_exit TERM INT
|
||||||
|
trap sigwinch WINCH
|
||||||
|
stty -echo
|
||||||
|
printf "\e[?25l"
|
||||||
|
printf "\e[2J"
|
||||||
|
|
||||||
|
sigwinch
|
||||||
|
while ((exit_st <= 0)); do
|
||||||
|
read -r -n 1 -t "$frame_sleep" ch
|
||||||
|
case "$ch" in
|
||||||
|
q|Q)
|
||||||
|
do_exit
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
row_line=""
|
||||||
|
for ((col = 0; col < term_width; col++)); do
|
||||||
|
idx=$((col - offset))
|
||||||
|
while ((idx < 0)); do
|
||||||
|
((idx += palette_width))
|
||||||
|
done
|
||||||
|
idx=$((idx % palette_width))
|
||||||
|
row_line+="${RAINBOW_BG_PALETTE[idx]} "
|
||||||
|
done
|
||||||
|
row_line+=$'\e[0m'
|
||||||
|
|
||||||
|
for ((row = 1; row <= term_height; row++)); do
|
||||||
|
printf "\e[%d;1H%b" "$row" "$row_line"
|
||||||
|
done
|
||||||
|
|
||||||
|
((offset = (offset + 1) % palette_width))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "\e[0m\e[2J\e[H"
|
||||||
|
printf "\e[?25h"
|
||||||
|
stty echo
|
||||||
|
trap - TERM INT
|
||||||
|
trap - WINCH
|
||||||
|
}
|
||||||
|
export -f rainbow
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
load_conf "rain"
|
load_conf "rain"
|
||||||
|
|||||||
@@ -52,10 +52,13 @@ rmhost()
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help)
|
-h|--help)
|
||||||
printf "rmhost: Remove host/IP from known_hosts files.\n\n"
|
printf "rmhost: Remove host/IP from known_hosts files.\n\n"
|
||||||
printf "Usage: rmhost [--all-users] <hostname|ip> [hostname2|ip2 ...]\n\n"
|
printf "Usage: rmhost [--all-users] <pattern|ip> [pattern2|ip2 ...]\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf " -a, --all-users Remove entries from all local users when run as root\n"
|
printf " -a, --all-users Remove entries from all local users when run as root\n"
|
||||||
printf " -h, --help Display this help screen\n"
|
printf " -h, --help Display this help screen\n\n"
|
||||||
|
printf "Wildcards:\n"
|
||||||
|
printf " Glob patterns (*, ?, [...]) are expanded against unhashed known_hosts entries.\n"
|
||||||
|
printf " Hashed entries (prefixed with |1|) are never matched by wildcards.\n"
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
-a|--all-users)
|
-a|--all-users)
|
||||||
@@ -106,6 +109,50 @@ rmhost()
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for target in "$@"; do
|
for target in "$@"; do
|
||||||
|
# Wildcard: expand glob pattern against unhashed known_hosts entries
|
||||||
|
if [[ "$target" == *['*?[']* ]]; then
|
||||||
|
local -a _matched=()
|
||||||
|
local _wf _wl _wfield _whost _wmatch
|
||||||
|
local -a _wentries
|
||||||
|
for _wf in "${known_hosts_files[@]}"; do
|
||||||
|
[[ -f "$_wf" ]] || continue
|
||||||
|
while IFS= read -r _wl; do
|
||||||
|
[[ -z "$_wl" || "$_wl" == '#'* || "$_wl" == '|'* ]] && continue
|
||||||
|
_wfield="${_wl%% *}"
|
||||||
|
IFS=',' read -ra _wentries <<< "$_wfield"
|
||||||
|
for _whost in "${_wentries[@]}"; do
|
||||||
|
# Strip [host]:port notation to get the bare name for matching
|
||||||
|
if [[ "$_whost" == '['*']:'* ]]; then
|
||||||
|
_wmatch="${_whost#[}"
|
||||||
|
_wmatch="${_wmatch%%]:*}"
|
||||||
|
else
|
||||||
|
_wmatch="$_whost"
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC2053
|
||||||
|
[[ "$_wmatch" == $target ]] && _matched+=("$_whost")
|
||||||
|
done
|
||||||
|
done < "$_wf"
|
||||||
|
done
|
||||||
|
|
||||||
|
mapfile -t _matched < <(printf '%s\n' "${_matched[@]}" | sort -u)
|
||||||
|
|
||||||
|
if [[ ${#_matched[@]} -eq 0 ]]; then
|
||||||
|
disp W "No known_hosts entries match pattern '$target'."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _key _known_hosts_file
|
||||||
|
for _key in "${_matched[@]}"; do
|
||||||
|
for _known_hosts_file in "${known_hosts_files[@]}"; do
|
||||||
|
disp I "Removing '$_key' from $_known_hosts_file..."
|
||||||
|
if ! ssh-keygen -R "$_key" -f "$_known_hosts_file" >/dev/null 2>&1; then
|
||||||
|
disp W "No known_hosts entry found for '$_key' in '$_known_hosts_file'."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
local hst="$target"
|
local hst="$target"
|
||||||
local ip=""
|
local ip=""
|
||||||
local v4=1
|
local v4=1
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ PROMPT_COLOR_ERR_MARK="$IYellow" # golden X
|
|||||||
PROMPT_COLOR_ROOT_FG="$IRed" # red for root
|
PROMPT_COLOR_ROOT_FG="$IRed" # red for root
|
||||||
PROMPT_COLOR_USER_FG="$IBlue" # electric blue for user
|
PROMPT_COLOR_USER_FG="$IBlue" # electric blue for user
|
||||||
PROMPT_COLOR_DIR_FG="$ICyan" # teal path
|
PROMPT_COLOR_DIR_FG="$ICyan" # teal path
|
||||||
|
PROMPT_COLOR_CTX_FG="$IYellow" # context segment (git/conda)
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ PROMPT_COLOR_ERR_MARK="$Yellow" # yellow X (warning intent)
|
|||||||
PROMPT_COLOR_ROOT_FG="$Red" # Adwaita red for root
|
PROMPT_COLOR_ROOT_FG="$Red" # Adwaita red for root
|
||||||
PROMPT_COLOR_USER_FG="$BBlue" # darker bold blue — readable on blue bar
|
PROMPT_COLOR_USER_FG="$BBlue" # darker bold blue — readable on blue bar
|
||||||
PROMPT_COLOR_DIR_FG="$IGreen" # Adwaita green for path
|
PROMPT_COLOR_DIR_FG="$IGreen" # Adwaita green for path
|
||||||
|
PROMPT_COLOR_CTX_FG="$BIWhite" # context segment (git/conda)
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ PROMPT_COLOR_ERR_MARK="$BIYellow" # X mark colour on failure
|
|||||||
PROMPT_COLOR_ROOT_FG="$BIRed" # Username colour when root
|
PROMPT_COLOR_ROOT_FG="$BIRed" # Username colour when root
|
||||||
PROMPT_COLOR_USER_FG="$ICyan" # Username@host colour for normal users
|
PROMPT_COLOR_USER_FG="$ICyan" # Username@host colour for normal users
|
||||||
PROMPT_COLOR_DIR_FG="$IPurple" # Working directory colour
|
PROMPT_COLOR_DIR_FG="$IPurple" # Working directory colour
|
||||||
|
PROMPT_COLOR_CTX_FG="$BIYellow" # Context segment (git/conda)
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ PROMPT_COLOR_ERR_MARK="$BYellow" # X mark colour on failure
|
|||||||
PROMPT_COLOR_ROOT_FG="$Red" # Username colour when root
|
PROMPT_COLOR_ROOT_FG="$Red" # Username colour when root
|
||||||
PROMPT_COLOR_USER_FG="$Green" # Username@host colour for normal users
|
PROMPT_COLOR_USER_FG="$Green" # Username@host colour for normal users
|
||||||
PROMPT_COLOR_DIR_FG="$ICyan" # Working directory colour
|
PROMPT_COLOR_DIR_FG="$ICyan" # Working directory colour
|
||||||
|
PROMPT_COLOR_CTX_FG="$BIYellow" # Context segment (git/conda)
|
||||||
|
|||||||
@@ -33,3 +33,4 @@ PROMPT_COLOR_ERR_MARK="$BYellow" # X mark on failure (BIYellow → BYellow, l
|
|||||||
PROMPT_COLOR_ROOT_FG="$Red" # Username when root (BIRed → Red)
|
PROMPT_COLOR_ROOT_FG="$Red" # Username when root (BIRed → Red)
|
||||||
PROMPT_COLOR_USER_FG="$Blue" # Username@host normal user (ICyan → Blue)
|
PROMPT_COLOR_USER_FG="$Blue" # Username@host normal user (ICyan → Blue)
|
||||||
PROMPT_COLOR_DIR_FG="$Purple" # Working directory (IPurple → Purple)
|
PROMPT_COLOR_DIR_FG="$Purple" # Working directory (IPurple → Purple)
|
||||||
|
PROMPT_COLOR_CTX_FG="$BBlack" # Context segment (git/conda)
|
||||||
|
|||||||
@@ -62,3 +62,4 @@ PROMPT_COLOR_ERR_MARK="$BBlack" # bold black X
|
|||||||
PROMPT_COLOR_ROOT_FG="$BIWhite" # bold bright white for root warning
|
PROMPT_COLOR_ROOT_FG="$BIWhite" # bold bright white for root warning
|
||||||
PROMPT_COLOR_USER_FG="$IWhite" # bright white for normal user
|
PROMPT_COLOR_USER_FG="$IWhite" # bright white for normal user
|
||||||
PROMPT_COLOR_DIR_FG="$White" # standard white for path
|
PROMPT_COLOR_DIR_FG="$White" # standard white for path
|
||||||
|
PROMPT_COLOR_CTX_FG="$BIWhite" # context segment (git/conda)
|
||||||
|
|||||||
@@ -43,3 +43,4 @@ PROMPT_COLOR_ERR_MARK="$IRed" # hot pink X
|
|||||||
PROMPT_COLOR_ROOT_FG="$IRed" # hot pink for root
|
PROMPT_COLOR_ROOT_FG="$IRed" # hot pink for root
|
||||||
PROMPT_COLOR_USER_FG="$IYellow" # orange-yellow for user
|
PROMPT_COLOR_USER_FG="$IYellow" # orange-yellow for user
|
||||||
PROMPT_COLOR_DIR_FG="$ICyan" # electric cyan for path
|
PROMPT_COLOR_DIR_FG="$ICyan" # electric cyan for path
|
||||||
|
PROMPT_COLOR_CTX_FG="$IYellow" # context segment (git/conda)
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ PROMPT_COLOR_ERR_MARK="$IYellow" # yellow X
|
|||||||
PROMPT_COLOR_ROOT_FG="$IRed" # red for root
|
PROMPT_COLOR_ROOT_FG="$IRed" # red for root
|
||||||
PROMPT_COLOR_USER_FG="$BIPurple" # bold vivid purple for user
|
PROMPT_COLOR_USER_FG="$BIPurple" # bold vivid purple for user
|
||||||
PROMPT_COLOR_DIR_FG="$ICyan" # electric cyan path
|
PROMPT_COLOR_DIR_FG="$ICyan" # electric cyan path
|
||||||
|
PROMPT_COLOR_CTX_FG="$IYellow" # context segment (git/conda)
|
||||||
|
|||||||
@@ -125,3 +125,4 @@ PROMPT_COLOR_ERR_MARK="\e[38;2;253;246;227m" # Base3 — X mark (bright on red
|
|||||||
PROMPT_COLOR_ROOT_FG="\e[38;2;220;50;47m" # Red — root warning
|
PROMPT_COLOR_ROOT_FG="\e[38;2;220;50;47m" # Red — root warning
|
||||||
PROMPT_COLOR_USER_FG="\e[38;2;42;161;152m" # Cyan — normal user
|
PROMPT_COLOR_USER_FG="\e[38;2;42;161;152m" # Cyan — normal user
|
||||||
PROMPT_COLOR_DIR_FG="\e[38;2;38;139;210m" # Blue — working directory
|
PROMPT_COLOR_DIR_FG="\e[38;2;38;139;210m" # Blue — working directory
|
||||||
|
PROMPT_COLOR_CTX_FG="\e[38;2;181;137;0m" # Yellow — context segment (git/conda)
|
||||||
|
|||||||
@@ -120,3 +120,4 @@ PROMPT_COLOR_ERR_MARK="\e[1;38;2;253;246;227m" # Base3 bold — bright warm mark
|
|||||||
PROMPT_COLOR_ROOT_FG="\e[38;2;220;50;47m" # Red — root warning
|
PROMPT_COLOR_ROOT_FG="\e[38;2;220;50;47m" # Red — root warning
|
||||||
PROMPT_COLOR_USER_FG="\e[38;2;42;161;152m" # Cyan — normal user
|
PROMPT_COLOR_USER_FG="\e[38;2;42;161;152m" # Cyan — normal user
|
||||||
PROMPT_COLOR_DIR_FG="\e[38;2;38;139;210m" # Blue — working directory
|
PROMPT_COLOR_DIR_FG="\e[38;2;38;139;210m" # Blue — working directory
|
||||||
|
PROMPT_COLOR_CTX_FG="\e[38;2;181;137;0m" # Yellow — context segment (git/conda)
|
||||||
|
|||||||
@@ -90,9 +90,14 @@ check_updates()
|
|||||||
return 4
|
return 4
|
||||||
}
|
}
|
||||||
|
|
||||||
dwl "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || {
|
# In quiet mode (startup), use a short timeout so a missing or slow network
|
||||||
|
# never blocks the interactive prompt.
|
||||||
|
local dwl_opts=()
|
||||||
|
(( quiet == 1 )) && dwl_opts+=(-t 3)
|
||||||
|
|
||||||
|
dwl "${dwl_opts[@]}" "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || {
|
||||||
rm -f "$vfile"
|
rm -f "$vfile"
|
||||||
disp E "Cannot download version file; unable to continue."
|
(( quiet != 1 )) && disp E "Cannot download version file; unable to continue."
|
||||||
return 5
|
return 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
136
profile.sh
136
profile.sh
@@ -35,10 +35,123 @@
|
|||||||
# * OF SUCH DAMAGE.
|
# * OF SUCH DAMAGE.
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_profile_is_sourced()
|
||||||
|
{
|
||||||
|
[[ "${BASH_SOURCE[0]}" != "$0" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
_profile_finish()
|
||||||
|
{
|
||||||
|
local rc="${1:-0}"
|
||||||
|
if _profile_is_sourced; then
|
||||||
|
return "$rc"
|
||||||
|
fi
|
||||||
|
exit "$rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
_profile_install_in_file()
|
||||||
|
{
|
||||||
|
local rc_file="$1"
|
||||||
|
local source_line="$2"
|
||||||
|
|
||||||
|
[[ -f "$rc_file" ]] || touch "$rc_file" || {
|
||||||
|
printf "[ Error ] Cannot create %s\n" "$rc_file" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if grep -Fqx "$source_line" "$rc_file"; then
|
||||||
|
printf "[ Info ] Already configured in %s\n" "$rc_file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n%s\n" "$source_line" >> "$rc_file" || {
|
||||||
|
printf "[ Error ] Cannot write to %s\n" "$rc_file" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "[ Info ] Added profile source line to %s\n" "$rc_file"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_profile_install()
|
||||||
|
{
|
||||||
|
local install_bashrc=0
|
||||||
|
local install_profile=0
|
||||||
|
local target_selected=0
|
||||||
|
local script_dir source_line rc=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--bashrc)
|
||||||
|
install_bashrc=1
|
||||||
|
target_selected=1
|
||||||
|
;;
|
||||||
|
--profile)
|
||||||
|
install_profile=1
|
||||||
|
target_selected=1
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
printf "Usage: %s --install [--bashrc] [--profile]\n" "${BASH_SOURCE[0]}"
|
||||||
|
printf "If no target is specified, both ~/.bashrc and ~/.profile are configured.\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf "[ Error ] Unknown install option: %s\n" "$1" >&2
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( target_selected == 0 )); then
|
||||||
|
install_bashrc=1
|
||||||
|
install_profile=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")")
|
||||||
|
source_line="source \"$script_dir/profile.sh\""
|
||||||
|
|
||||||
|
if (( install_bashrc == 1 )); then
|
||||||
|
_profile_install_in_file "$HOME/.bashrc" "$source_line" || rc=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( install_profile == 1 )); then
|
||||||
|
_profile_install_in_file "$HOME/.profile" "$source_line" || rc=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "$rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
case "$1" in
|
||||||
|
--install)
|
||||||
|
shift
|
||||||
|
_profile_install "$@"
|
||||||
|
_profile_finish $?
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
printf "Usage: source %s\n" "${BASH_SOURCE[0]}"
|
||||||
|
printf " %s --install [--bashrc] [--profile]\n" "${BASH_SOURCE[0]}"
|
||||||
|
_profile_finish 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf "[ Error ] Unknown option: %s\n" "$1" >&2
|
||||||
|
_profile_finish 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _profile_is_sourced; then
|
||||||
|
printf "[ Warning ] profile.sh is designed to be sourced, not executed directly.\n" >&2
|
||||||
|
printf "Use: source \"%s\"\n" "$(realpath -s "${BASH_SOURCE[0]}")" >&2
|
||||||
|
printf "Or run: %s --install [--bashrc] [--profile]\n" "${BASH_SOURCE[0]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ! $SHELL =~ bash ]]; then
|
if [[ ! $SHELL =~ bash ]]; then
|
||||||
echo "That environment script is designed to be used with bash being the shell."
|
echo "That environment script is designed to be used with bash being the shell."
|
||||||
echo "Please consider using bash to enjoy our features!"
|
echo "Please consider using bash to enjoy our features!"
|
||||||
return 1
|
_profile_finish 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Required for associative arrays (4.0+) and namerefs (4.3+)
|
# Required for associative arrays (4.0+) and namerefs (4.3+)
|
||||||
@@ -131,8 +244,11 @@ parse_conf()
|
|||||||
|
|
||||||
# Correctly interpretet internal variables (e.g. $HOME)
|
# Correctly interpretet internal variables (e.g. $HOME)
|
||||||
if [[ "$value" == *\$* ]]; then
|
if [[ "$value" == *\$* ]]; then
|
||||||
|
if command -v envsubst >/dev/null 2>&1; then
|
||||||
value=$(envsubst <<< "$value")
|
value=$(envsubst <<< "$value")
|
||||||
fi
|
fi
|
||||||
|
# If envsubst is unavailable, $VAR references are left as-is.
|
||||||
|
fi
|
||||||
|
|
||||||
# Strip quotes (handling both " and ')
|
# Strip quotes (handling both " and ')
|
||||||
value="${value%\"}"; value="${value#\"}"
|
value="${value%\"}"; value="${value#\"}"
|
||||||
@@ -236,6 +352,10 @@ fi
|
|||||||
# Parse and load general configuration
|
# Parse and load general configuration
|
||||||
export PROFILE_CONF="$MYPATH/profile.conf"
|
export PROFILE_CONF="$MYPATH/profile.conf"
|
||||||
parse_conf "$PROFILE_CONF"
|
parse_conf "$PROFILE_CONF"
|
||||||
|
# Overload with user configuration if it exists
|
||||||
|
if [[ -f "$HOME/.profile.conf" ]]; then
|
||||||
|
parse_conf "$HOME/.profile.conf"
|
||||||
|
fi
|
||||||
load_conf system # Load Bash system behavior configuration (history, pager, etc.)
|
load_conf system # Load Bash system behavior configuration (history, pager, etc.)
|
||||||
load_conf general # General purpose configuration (compilation flags, etc.)
|
load_conf general # General purpose configuration (compilation flags, etc.)
|
||||||
|
|
||||||
@@ -255,7 +375,15 @@ shopt -u nullglob
|
|||||||
[[ $- == *i* ]] && export INTERACTIVE=1
|
[[ $- == *i* ]] && export INTERACTIVE=1
|
||||||
|
|
||||||
if [[ $INTERACTIVE ]]; then
|
if [[ $INTERACTIVE ]]; then
|
||||||
# For compiling (as we often compile with LFS/0linux...)
|
# Load custom bash completions
|
||||||
|
shopt -s nullglob
|
||||||
|
for _compl in "$MYPATH/profile.d/bash-completion/"*.sh; do
|
||||||
|
# shellcheck disable=SC1090 # Dynamic sourcing of completion scripts
|
||||||
|
[[ -f "$_compl" && -r "$_compl" ]] && . "$_compl"
|
||||||
|
done
|
||||||
|
unset _compl
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
# Aliases
|
# Aliases
|
||||||
load_alias aliases
|
load_alias aliases
|
||||||
|
|
||||||
@@ -281,7 +409,9 @@ if [[ $INTERACTIVE ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
unset pathremove pathprepend pathappend
|
unset -f _profile_is_sourced _profile_finish _profile_install_in_file _profile_install
|
||||||
|
unset -f parse_conf load_alias load_conf
|
||||||
|
unset -f pathremove pathprepend pathappend
|
||||||
|
|
||||||
#return 0
|
#return 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user