66 Commits

Author SHA1 Message Date
fatalerrors
99dc9a4ef5 get rid of eval (use readarray) 2026-07-09 18:17:53 +02:00
fatalerrors
c1ea14e145 accept various codeset writting 2026-07-09 16:51:13 +02:00
fatalerrors
03ca259c4c remove english based git output dependencies 2026-07-09 16:29:09 +02:00
fatalerrors
97773515eb remove forbidden comments 2026-07-09 16:22:20 +02:00
fatalerrors
fbe1f074da several bug fix 2026-07-09 16:04:15 +02:00
fatalerrors
db7dcb6f03 bug fix / isipv6 complete rewrite 2026-07-09 14:58:01 +02:00
fatalerrors
e328199618 fixed disp with escape sequence 2026-07-09 14:56:30 +02:00
fatalerrors
e6607f07af optimise mdcat and fix trruncated files rendering 2026-07-09 11:30:43 +02:00
fatalerrors
54da852ac3 moved load_conf top of files 2026-07-09 11:11:05 +02:00
fatalerrors
d375a09623 moved load_conf top of files 2026-07-09 11:10:53 +02:00
fatalerrors
b2b25843ff fix debug robustness 2026-07-09 10:55:20 +02:00
fatalerrors
2702682fae fixed displayed value 2026-07-08 18:56:50 +02:00
fatalerrors
970cd2ccf6 bugfix 2026-07-08 18:51:24 +02:00
fatalerrors
38038e0fa1 add keep-all options 2026-07-08 18:40:09 +02:00
fatalerrors
9ba36582e4 implement multiple archive creation 2026-07-08 18:36:30 +02:00
fatalerrors
022d4f9e77 bugfix 2026-07-08 18:12:27 +02:00
fatalerrors
70fc5c2f78 removed last french 2026-07-08 18:02:06 +02:00
fatalerrors
a178439545 avoid double call when .profile source .bashrc and vice-versa 2026-06-23 17:15:48 +02:00
fatalerrors
a93d367974 added swap and gpu info to pinfo 2026-06-03 15:48:04 +02:00
fatalerrors
bcc86814b5 added gprune --deep parameter to allow deletion of forge merge 2026-06-03 15:47:29 +02:00
fatalerrors
462efef034 checked dependencies, made some optional, document optional dependencies 2026-06-02 17:49:16 +02:00
fatalerrors
f0c62a5e7f fix doc and style 2026-06-02 17:24:41 +02:00
fatalerrors
df4c63657b use internals 2026-06-02 17:17:29 +02:00
fatalerrors
9c201f0e97 make set_term 24 bits aware 2026-06-02 17:16:35 +02:00
fatalerrors
c4c52cfb48 allow true color for matrix and rain 2026-06-02 16:47:58 +02:00
fatalerrors
7c14c67ad0 added pkgf 2026-06-02 16:36:20 +02:00
fatalerrors
4882bee0ad updated todo 2026-06-02 13:55:09 +02:00
fatalerrors
bbfd877ee5 added wildcard support in rmhost 2026-06-02 13:54:42 +02:00
fatalerrors
1288b47c34 added support for fd when available 2026-06-02 13:54:01 +02:00
fatalerrors
d849b22001 added option all to conf_dump 2026-06-02 11:02:35 +02:00
fatalerrors
9fcdb24988 fix colors in conf_dump 2026-06-02 10:13:22 +02:00
fatalerrors
3e0bedd9c8 updated and fixed doc (markdown spellcheck) 2026-05-28 18:08:41 +02:00
fatalerrors
481cc94fa8 added autodetection for TERM var 2026-05-28 17:47:23 +02:00
fatalerrors
28e1e26b0e reworked known_bugs. 2026-05-28 16:33:05 +02:00
fatalerrors
bddcd170c3 added conf_dump and updated doc 2026-05-28 16:29:43 +02:00
fatalerrors
0a85d265cb made configuration saving generic 2026-05-28 12:04:30 +02:00
fatalerrors
c13945ced5 context factorization 2026-05-28 11:51:23 +02:00
fatalerrors
a33aa5f6be add timeout to git operations 2026-05-28 11:31:20 +02:00
fatalerrors
d9a62cf2a8 updated doc and exemple conf 2026-05-27 17:32:35 +02:00
fatalerrors
7287d8ef87 add random lang hability 2026-05-27 17:31:37 +02:00
fatalerrors
7c761a4895 make delays more iregular 2026-05-27 15:19:42 +02:00
fatalerrors
fef99326c3 make it infinite 2026-05-27 15:05:45 +02:00
fatalerrors
ac7eba14b0 unecessary stuff 2026-05-27 14:40:02 +02:00
fatalerrors
f6bf229c8d unecessary kill and wait 2026-05-27 14:37:44 +02:00
fatalerrors
01e17888fe add missing exports 2026-05-27 14:26:44 +02:00
fatalerrors
8ac0c1eaf2 added hack and fake_compile 2026-05-27 14:22:22 +02:00
fatalerrors
600f170d00 add error situation preview 2026-05-27 14:06:26 +02:00
fatalerrors
cbe29a05f5 fixed preview 2026-05-27 12:01:24 +02:00
fatalerrors
8399adbfa0 fixed preview 2026-05-27 11:59:27 +02:00
fatalerrors
2c81a48fe9 added rainbow 2026-05-21 16:12:56 +02:00
fatalerrors
d03ced93bf fix help and spellcheck 2026-05-21 16:12:34 +02:00
fatalerrors
8b1fc5f01c fix spellcheck 2026-05-21 16:11:26 +02:00
fatalerrors
02cd9a853e processes func switch to getopt 2026-05-21 14:28:40 +02:00
fatalerrors
648777a13e update todo 2026-05-21 11:55:07 +02:00
fatalerrors
5264470127 update theming doc 2026-05-21 11:51:08 +02:00
fatalerrors
f923dcb758 add preview and configuration saving to prompt theming 2026-05-21 11:43:15 +02:00
fatalerrors
c92ca8a51f made local really local 2026-05-21 11:17:21 +02:00
fatalerrors
19ca7fa905 added pinfo 2026-05-21 11:10:09 +02:00
fatalerrors
6bacb03687 improved kt and ku 2026-05-21 11:08:46 +02:00
fatalerrors
5f8800ab44 add resume support to dwl 2026-05-20 16:28:32 +02:00
fatalerrors
8bfb3272c0 fix mod 2026-05-20 16:12:29 +02:00
fatalerrors
1a48280b14 minor fix 2026-05-20 15:54:23 +02:00
fatalerrors
9a272689eb manage element density 2026-05-20 15:22:17 +02:00
fatalerrors
6e4d052170 add completion for set_theme 2026-05-20 15:01:10 +02:00
fatalerrors
ceb3386c57 added known-bugs.ml 2026-05-20 14:30:07 +02:00
fatalerrors
d22b24e54c prompt now display contextual infos 2026-05-20 14:14:06 +02:00
39 changed files with 3471 additions and 396 deletions

188
README.md
View File

@@ -1,25 +1,30 @@
# 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. directory.
The profile is designed to be **sourced**, not executed directly. The profile is designed to be **sourced**, not executed directly.
Manual setup: Manual setup:
```bash ```bash
source <installpath>/profile/profile.sh source <installpath>/profile/profile.sh
``` ```
Automatic setup (recommended): Automatic setup (recommended):
```bash ```bash
bash <installpath>/profile/profile.sh --install bash <installpath>/profile/profile.sh --install
``` ```
@@ -34,6 +39,7 @@ 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
@@ -48,6 +54,7 @@ files might interfere with some aliases and functions defined in profile.
In interactive shells (typical terminal sessions), profile enables In interactive shells (typical terminal sessions), profile enables
interactive-only features such as: interactive-only features such as:
- aliases from `[aliases]` - aliases from `[aliases]`
- bash completion scripts from `profile.d/bash-completion/` - bash completion scripts from `profile.d/bash-completion/`
- prompt initialization (`PROMPT_COMMAND` and timer hook) - prompt initialization (`PROMPT_COMMAND` and timer hook)
@@ -58,33 +65,41 @@ intentionally skipped to avoid side effects and startup noise. Public functions
remain available after sourcing, so scripts can still call profile helpers. remain available after sourcing, so scripts can still call profile helpers.
### 2.2. Initial configuration ### 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.
### 3.2. Functions reference ### 3.2. Functions reference
| 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; when called with `-q` at startup a 3-second network timeout is applied so a slow or absent network never delays the prompt | | `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 |
| `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 |
| `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 | | `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 | | `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 |
@@ -99,6 +114,7 @@ A bar-style prompt showing current time, execution time of the last command
| `gsync` | git | Fetch and rebase the current branch onto its upstream | | `gsync` | git | Fetch and rebase the current branch onto its upstream |
| `gst` | git | Display compact git status with branch tracking information | | `gst` | git | Display compact git status with branch tracking information |
| `gwip` | git | Create a quick WIP checkpoint commit | | `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` | | `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 |
@@ -109,14 +125,16 @@ A bar-style prompt showing current time, execution time of the last command
| `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 |
@@ -124,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 |
@@ -139,6 +158,7 @@ any custom completion definitions. profile already ships completions for its git
helper functions there (`git-completion.sh`). 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
@@ -153,11 +173,39 @@ template at `doc/profile.conf.example` (see [section 2.2](#22-initial-configurat
### 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
@@ -166,7 +214,7 @@ change the default without having to pass flags every time.
**`[compress]`** — `taz` / `utaz` **`[compress]`** — `taz` / `utaz`
| 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` | `auto` | Compression threads (`auto` = runtime CPU count, or explicit positive integer) | | `TAZ_DEFAULT_THREADS` | `auto` | Compression threads (`auto` = runtime CPU count, or explicit positive integer) |
| `TAZ_DEFAULT_LEVEL` | `6` | Compression level (19) | | `TAZ_DEFAULT_LEVEL` | `6` | Compression level (19) |
@@ -176,7 +224,7 @@ change the default without having to pass flags every time.
**`[filefct]`** — file utilities **`[filefct]`** — file utilities
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `EXPANDLIST_DEFAULT_SEPARATOR` | ` ` (space) | Separator used by `expandlist` | | `EXPANDLIST_DEFAULT_SEPARATOR` | ` ` (space) | Separator used by `expandlist` |
| `CLEAN_DEFAULT_RECURSIVE` | `0` | Set to `1` to make `clean` recurse into subdirectories | | `CLEAN_DEFAULT_RECURSIVE` | `0` | Set to `1` to make `clean` recurse into subdirectories |
| `RMSPC_DEFAULT_CHAR` | `_` | Replacement character used by `rmspc` | | `RMSPC_DEFAULT_CHAR` | `_` | Replacement character used by `rmspc` |
@@ -185,23 +233,26 @@ change the default without having to pass flags every time.
**`[rain]`** — screensavers **`[rain]`** — screensavers
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `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]`**
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `SSH_DEFAULT_OPT` | _(empty)_ | Extra options passed to `ssr` (e.g. `-Y` for X forwarding) | | `SSH_DEFAULT_OPT` | _(empty)_ | Extra options passed to `ssr` (e.g. `-Y` for X forwarding) |
**`[pwd]`** — password tools **`[pwd]`** — password tools
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `GENPWD_DEFAULT_LENGTH` | `16` | Generated password length | | `GENPWD_DEFAULT_LENGTH` | `16` | Generated password length |
| `GENPWD_DEFAULT_OCCURS` | `1` | Number of character class occurrences | | `GENPWD_DEFAULT_OCCURS` | `1` | Number of character class occurrences |
| `GENPWD_DEFAULT_COUNT` | `1` | Number of passwords to generate | | `GENPWD_DEFAULT_COUNT` | `1` | Number of passwords to generate |
@@ -214,14 +265,19 @@ change the default without having to pass flags every time.
**`[fun]`** **`[fun]`**
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `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]`** **`[git]`**
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `GIT_MAIN_BRANCH` | `main` | Fallback main branch name used when remote HEAD cannot be detected | | `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_DEFAULT_REMOTE` | `origin` | Default remote used by git helper functions |
| `GIT_WIP_PREFIX` | `wip` | Prefix used by `gwip` when generating automatic checkpoint messages | | `GIT_WIP_PREFIX` | `wip` | Prefix used by `gwip` when generating automatic checkpoint messages |
@@ -230,33 +286,34 @@ change the default without having to pass flags every time.
**`[info]`** **`[info]`**
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `METEO_DEFAULT_CITY` | _(empty)_ | Default city for `meteo` when no argument is given | | `METEO_DEFAULT_CITY` | _(empty)_ | Default city for `meteo` when no argument is given |
**`[net]`** **`[net]`**
| 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]`**
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `PKGS_DEFAULT_IGNORE_CASE` | `0` | Set to `1` to make `pkgs` case-insensitive by default | | `PKGS_DEFAULT_IGNORE_CASE` | `0` | Set to `1` to make `pkgs` case-insensitive by default |
**`[processes]`** **`[processes]`**
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `PPU_DEFAULT_FORMAT` | `pid,user,comm,args` | `ps` output format used by `ppu` | | `PPU_DEFAULT_FORMAT` | `pid,user,comm,args` | `ps` output format used by `ppu` |
| `KU_DEFAULT_SIGNAL` | `TERM` | Default signal sent by `ku` | | `KU_DEFAULT_SIGNAL` | `TERM` | Default signal sent by `ku` |
**`[updates]`** **`[updates]`**
| Key | Default | Description | | Key | Default | Description |
|---|---|---| | --- | --- | --- |
| `UPDT_DEFAULT_BRANCH` | `master` | Git branch used for update checks and `profile_upgrade`. Changing this value causes `profile_upgrade` to automatically switch the local checkout to the new branch on the next run and display a warning. | | `UPDT_DEFAULT_BRANCH` | `master` | Git branch used for update checks and `profile_upgrade`. Changing this value causes `profile_upgrade` to automatically switch the local checkout to the new branch on the next run and display a warning. |
### 4.3. Locale shortcuts ### 4.3. Locale shortcuts
@@ -300,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
@@ -308,10 +382,10 @@ 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 |
|---|---| | --- | --- |
| `PROMPT_COLOR_TIME_FG` / `TIME_BG` | Clock foreground / background | | `PROMPT_COLOR_TIME_FG` / `TIME_BG` | Clock foreground / background |
| `PROMPT_COLOR_BAR_BG` | Main bar background | | `PROMPT_COLOR_BAR_BG` | Main bar background |
| `PROMPT_COLOR_OK_FG` / `OK_MARK` | Exit-code text / checkmark on success | | `PROMPT_COLOR_OK_FG` / `OK_MARK` | Exit-code text / checkmark on success |
@@ -319,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:**
@@ -342,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
@@ -366,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
@@ -388,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.
@@ -400,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>

View File

@@ -10,6 +10,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
## [4.1.0] — 2026-05-07 ## [4.1.0] — 2026-05-07
### Added ### Added
- `profile.sh --install` command to automatically configure profile loading in - `profile.sh --install` command to automatically configure profile loading in
shell startup files. shell startup files.
- `--install --bashrc` and `--install --profile` target selectors for - `--install --bashrc` and `--install --profile` target selectors for
@@ -20,16 +21,17 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
automatically in interactive sessions from `profile.d/bash-completion/*.sh`. automatically in interactive sessions from `profile.d/bash-completion/*.sh`.
### Changed ### Changed
- `disp` now wraps long messages on terminal width, avoids mid-word splits, and - `disp` now wraps long messages on terminal width, avoids mid-word splits, and
aligns continuation lines with the message body after the prefix. aligns continuation lines with the message body after the prefix.
- `help` now supports `help <command>` and delegates to `<command> --help`. - `help` now supports `help <command>` and delegates to `<command> --help`.
- `taz` now supports `-p auto` / `--parallel=auto` to automatically use the - `taz` now supports `-p auto` / `--parallel=auto` to automatically use the
runtime CPU count. This mode is now the default via runtime CPU count. This mode is now the default via
`TAZ_DEFAULT_THREADS=auto`. `TAZ_DEFAULT_THREADS=auto`. Backward compatibility with 0 being interpreted as
- `taz` keeps backward compatibility with legacy `TAZ_DEFAULT_THREADS=0` auto is maintained.
values by interpreting `0` as `auto`.
### Fixed ### Fixed
- Startup responsiveness improved: `check_updates -q` now uses a short network - Startup responsiveness improved: `check_updates -q` now uses a short network
timeout so unavailable/slow networks no longer delay prompt readiness. timeout so unavailable/slow networks no longer delay prompt readiness.
- `dwl` gained timeout support (`-t` / `--timeout`) and is now used by quiet - `dwl` gained timeout support (`-t` / `--timeout`) and is now used by quiet
@@ -40,6 +42,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
## [4.0.0] — 2026-04-23 ## [4.0.0] — 2026-04-23
### Added ### Added
- New `profile.conf` reference template at `doc/profile.conf.example`. - New `profile.conf` reference template at `doc/profile.conf.example`.
- Dynamic locale shortcuts generated from `SET_LOCALE` and startup default - Dynamic locale shortcuts generated from `SET_LOCALE` and startup default
language selection through `DEFAULT_LANG`. language selection through `DEFAULT_LANG`.
@@ -49,11 +52,13 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
- Module defaults exposed as configuration keys in `profile.conf`. - Module defaults exposed as configuration keys in `profile.conf`.
### Changed ### Changed
- `utaz` now supports a wider range of archive formats. - `utaz` now supports a wider range of archive formats.
- Prompt and theme rendering improved, including better 24-bit color support. - Prompt and theme rendering improved, including better 24-bit color support.
- Overall code quality and maintainability improved across modules. - Overall code quality and maintainability improved across modules.
### Documentation ### Documentation
- README updated with full function reference and configuration tables. - README updated with full function reference and configuration tables.
- New and expanded docs in `doc/` (`CONTRIBUTING.md`, `FAQ.md`, `todo.md`). - New and expanded docs in `doc/` (`CONTRIBUTING.md`, `FAQ.md`, `todo.md`).
- Historical releases imported from `history.txt` into this changelog. - Historical releases imported from `history.txt` into this changelog.
@@ -63,6 +68,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
## [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
@@ -77,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
@@ -89,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
@@ -116,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
@@ -124,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
@@ -134,6 +144,7 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
## [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`.
@@ -151,35 +162,43 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
## Legacy releases (imported from `history.txt`) ## Legacy releases (imported from `history.txt`)
### [3.6.1] — 2026-03-05 ### [3.6.1] — 2026-03-05
- Fix typo in `compress.sh`. - Fix typo in `compress.sh`.
### [3.6.0] — 2026-03-05 ### [3.6.0] — 2026-03-05
- Improved `utaz` with broader multi-format support. - Improved `utaz` with broader multi-format support.
- Introduced `ppu` and `ppn`. - Introduced `ppu` and `ppn`.
- Improved update system. - Improved update system.
### [3.5.0] — 2026-03-04 ### [3.5.0] — 2026-03-04
- `rain` now has configurable speed and color. - `rain` now has configurable speed and color.
- `showinfo` adapted to `fastfetch` (in addition to `neofetch`). - `showinfo` adapted to `fastfetch` (in addition to `neofetch`).
### [3.3.1] — 2022-02-24 ### [3.3.1] — 2022-02-24
- Fixed version detection. - Fixed version detection.
- Added `busy`. - Added `busy`.
- Fixed use of library functions before loading. - Fixed use of library functions before loading.
### [3.3.0] — 2022-11-28 ### [3.3.0] — 2022-11-28
- Initial version update support. - Initial version update support.
- Changed versioning code. - Changed versioning code.
- Added installation path detection. - Added installation path detection.
### [3.2.3] — 2022-11-28 ### [3.2.3] — 2022-11-28
- Improved README. - Improved README.
### [3.2.2] — 2022-11-21 ### [3.2.2] — 2022-11-21
- Fixed `taz` compression level parsing. - Fixed `taz` compression level parsing.
- Fixed typo in `dpkgs`. - Fixed typo in `dpkgs`.
### [3.2.1] — 2022-11-20 ### [3.2.1] — 2022-11-20
- Fixed several messages. - Fixed several messages.
- Made `dpkgs` RPM-aware (initial support). - Made `dpkgs` RPM-aware (initial support).
- Removed version history from main script and reverted declaration order. - Removed version history from main script and reverted declaration order.
@@ -187,108 +206,139 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
- Completed `LICENSE` file. - Completed `LICENSE` file.
### [3.2.0] — 2022-11-18 ### [3.2.0] — 2022-11-18
- Created `disp` command and integrated it across the codebase. - Created `disp` command and integrated it across the codebase.
### [3.1.1] — 2022-11-10 ### [3.1.1] — 2022-11-10
- `genpwd`: added feasibility check for requested password constraints. - `genpwd`: added feasibility check for requested password constraints.
### [3.1.0] — 2022-11-08 ### [3.1.0] — 2022-11-08
- Added password generator. - Added password generator.
### [3.0.1] — 2022-11-07 ### [3.0.1] — 2022-11-07
- Added concatenation option to `rmspc`. - Added concatenation option to `rmspc`.
- Added `ku`. - Added `ku`.
- Improved error handling in `meteo`. - Improved error handling in `meteo`.
### [3.0.0] — 2022-08-27 ### [3.0.0] — 2022-08-27
- Split code into several files/modules. - Split code into several files/modules.
- Added `rain` screensaver. - Added `rain` screensaver.
### [2.8.2] — 2022-07-29 ### [2.8.2] — 2022-07-29
- Added warning for non-bash users. - Added warning for non-bash users.
### [2.8.1] — 2022-07-19 ### [2.8.1] — 2022-07-19
- Cleanup, fixes and optimizations. - Cleanup, fixes and optimizations.
### [2.8.0] — 2022-06-24 ### [2.8.0] — 2022-06-24
- Added `backtrace`, `error` and `settrace`. - Added `backtrace`, `error` and `settrace`.
- Bugfixes in `showinfo`. - Bugfixes in `showinfo`.
### [2.7.1] — 2022-06-22 ### [2.7.1] — 2022-06-22
- Minor corrections. - Minor corrections.
- Added `help` command. - Added `help` command.
### [2.7.0] — 2022-06-21 ### [2.7.0] — 2022-06-21
- Added `isipv4` and `isipv6`, integrated into `rmhost`. - Added `isipv4` and `isipv6`, integrated into `rmhost`.
- Removed broken Konsole save/restore support. - Removed broken Konsole save/restore support.
### [2.6.3] — 2021-10-18 ### [2.6.3] — 2021-10-18
- Changed PS1 to status-bar style. - Changed PS1 to status-bar style.
- Minor improvements. - Minor improvements.
### [2.6.2] — 2021-02-26 ### [2.6.2] — 2021-02-26
- Bugfix in `taz` for directories with trailing slash. - Bugfix in `taz` for directories with trailing slash.
### [2.6.1] — 2020-12-25 ### [2.6.1] — 2020-12-25
- Added checks in `rmhost`. - Added checks in `rmhost`.
- Improved `rmspc`. - Improved `rmspc`.
- Created `expandlist`. - Created `expandlist`.
### [2.6.0] — 2020-10-24 ### [2.6.0] — 2020-10-24
- Added Konsole session save/restore. - Added Konsole session save/restore.
### [2.5.3] — 2020-09-11 ### [2.5.3] — 2020-09-11
- Added aliases, improved code consistency and fixed typos. - Added aliases, improved code consistency and fixed typos.
- Improved `utaz`, removed `showdiskmap`, removed remaining French text. - Improved `utaz`, removed `showdiskmap`, removed remaining French text.
- Added license information for future publication. - Added license information for future publication.
### [2.5.2] — 2020-03-06 ### [2.5.2] — 2020-03-06
- Sorted and improved aliases. - Sorted and improved aliases.
### [2.5.1] — 2020-03-05 ### [2.5.1] — 2020-03-05
- Language consistency fixes. - Language consistency fixes.
- Added `pigz` support in `taz`. - Added `pigz` support in `taz`.
### [2.5.0] — 2020-03-03 ### [2.5.0] — 2020-03-03
- Added `taz` and `rmspc`. - Added `taz` and `rmspc`.
- Renamed `auzip` to `utaz` and improved it. - Renamed `auzip` to `utaz` and improved it.
### [2.4.0] — 2020-03-02 ### [2.4.0] — 2020-03-02
- Added `auzip`. - Added `auzip`.
### [2.3.2] — 2020-01-31 ### [2.3.2] — 2020-01-31
- `figlet`: changed default font to `ansi_shadow`. - `figlet`: changed default font to `ansi_shadow`.
### [2.3.1] — 2020-01-16 ### [2.3.1] — 2020-01-16
- Bugfix: non-interactive shells were blocked by some functions. - Bugfix: non-interactive shells were blocked by some functions.
### [2.3.0] — 2020-01-08 ### [2.3.0] — 2020-01-08
- Added `figlet` and `neofetch` as MOTD replacement. - Added `figlet` and `neofetch` as MOTD replacement.
### [2.2.0] — 2019-12-16 ### [2.2.0] — 2019-12-16
- Added `showinfo`. - Added `showinfo`.
- First implementation of `showdiskmap`. - First implementation of `showdiskmap`.
### [2.1.2] — 2019-09-24 ### [2.1.2] — 2019-09-24
- Bugfix in profile version display. - Bugfix in profile version display.
### [2.1.1] — 2019-09-23 ### [2.1.1] — 2019-09-23
- Bugfix in `dpkgs`. - Bugfix in `dpkgs`.
### [2.1.0] — 2018-09-16 ### [2.1.0] — 2018-09-16
- Added `rmhost`, `setc`, `setfr`. - Added `rmhost`, `setc`, `setfr`.
- Improved locale management. - Improved locale management.
### [2.0.1] — 2017-02-04 ### [2.0.1] — 2017-02-04
- `clean` improvements (`--shell`). - `clean` improvements (`--shell`).
### [2.0.0] — 2015-10-24 ### [2.0.0] — 2015-10-24
- Added advanced functions (`clean`, `ssr`, etc.). - Added advanced functions (`clean`, `ssr`, etc.).
### [1.0.0] — 2013-02-16 ### [1.0.0] — 2013-02-16
- Initial version. - Initial version.
### [Initial fork] ### [Initial fork]
Forked default Bash profile from Beyond Linux From Scratch by
* James Robertson <jameswrobertson@earthlink.net> Forked default Bash profile from Beyond Linux From Scratch by:
* Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>
- James Robertson <jameswrobertson@earthlink.net>
- Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>

View File

@@ -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,13 +72,15 @@ 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 |
| git | any | For contributing patches | | git | any | For contributing patches |
| 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.

View File

@@ -7,15 +7,19 @@
**Q: How do I install profile automatically into my shell startup files?** **Q: How do I install profile automatically into my shell startup files?**
Run the installer directly (no need to source first): Run the installer directly (no need to source first):
```bash ```bash
bash <installpath>/profile/profile.sh --install bash <installpath>/profile/profile.sh --install
``` ```
This appends the required `source` line to both `~/.bashrc` and `~/.profile`. This appends the required `source` line to both `~/.bashrc` and `~/.profile`.
To target only one file: To target only one file:
```bash ```bash
bash <installpath>/profile/profile.sh --install --bashrc bash <installpath>/profile/profile.sh --install --bashrc
bash <installpath>/profile/profile.sh --install --profile bash <installpath>/profile/profile.sh --install --profile
``` ```
The operation is idempotent — running it again will not add a duplicate line. The operation is idempotent — running it again will not add a duplicate line.
--- ---
@@ -23,9 +27,11 @@ 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.** **Q: I ran `profile.sh` directly and got a warning about sourcing.**
profile.sh is designed to be *sourced*, not executed: profile.sh is designed to be *sourced*, not executed:
```bash ```bash
source <installpath>/profile/profile.sh source <installpath>/profile/profile.sh
``` ```
The only exception is `--install`, which must be passed to a direct execution The only exception is `--install`, which must be passed to a direct execution
(`bash profile.sh --install`) to set up the sourcing line automatically. (`bash profile.sh --install`) to set up the sourcing line automatically.
@@ -35,11 +41,13 @@ The only exception is `--install`, which must be passed to a direct execution
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.
--- ---
@@ -55,12 +63,14 @@ scripts start with `#!/usr/bin/env bash`.
**Q: Can I use profile functions in scripts?** **Q: Can I use profile functions in scripts?**
Yes. The supported way is to source `profile.sh` from a Bash script: Yes. The supported way is to source `profile.sh` from a Bash script:
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
source /path/to/profile/profile.sh source /path/to/profile/profile.sh
taz -p auto -f lz mydir taz -p auto -f lz mydir
``` ```
You can also source one module directly (for example You can also source one module directly (for example
`profile.d/compress.sh`) if you only need a subset of functions. `profile.d/compress.sh`) if you only need a subset of functions.
@@ -82,10 +92,12 @@ 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.
@@ -119,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 ]`).
@@ -128,15 +141,39 @@ 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`, but you can create your `monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`, but you can create your
own theme. own theme.
@@ -147,16 +184,45 @@ own theme.
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`,
@@ -181,7 +247,7 @@ theme file cannot execute code. Values must be a colour variable reference
All git helpers are defined in `profile.d/git.sh` (new in 4.1.0): All git helpers are defined in `profile.d/git.sh` (new in 4.1.0):
| Command | Purpose | | Command | Purpose |
|---|---| | --- | --- |
| `gst` | Compact status with branch tracking info | | `gst` | Compact status with branch tracking info |
| `ggraph` | Decorated history graph | | `ggraph` | Decorated history graph |
| `gsync` | Fetch and rebase onto upstream | | `gsync` | Fetch and rebase onto upstream |
@@ -200,12 +266,14 @@ All commands accept `-h` / `--help`.
Profile ships dedicated completions in `profile.d/bash-completion/git-completion.sh`, Profile ships dedicated completions in `profile.d/bash-completion/git-completion.sh`,
loaded automatically in interactive sessions. If completions are missing, loaded automatically in interactive sessions. If completions are missing,
check that the system git completion is installed: check that the system git completion is installed:
```bash ```bash
# Debian / Ubuntu # Debian / Ubuntu
apt-get install bash-completion apt-get install bash-completion
# Fedora / RHEL # Fedora / RHEL
dnf install bash-completion dnf install bash-completion
``` ```
When the native git completion helpers are available, `gacp` path completion When the native git completion helpers are available, `gacp` path completion
behaves exactly like `git add` (modified files, untracked files, directories). behaves exactly like `git add` (modified files, untracked files, directories).
@@ -215,10 +283,12 @@ behaves exactly like `git add` (modified files, untracked files, directories).
The `-a` / `--auto` flag adds all modified files (equivalent to `git add -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`: The default depends on `GIT_GACP_AUTO_ADD` in `profile.conf`:
```ini ```ini
[git] [git]
GIT_GACP_AUTO_ADD = 1 GIT_GACP_AUTO_ADD = 1
``` ```
Set to `1` to make `-a` the default. Set to `1` to make `-a` the default.
--- ---
@@ -229,6 +299,7 @@ Set to `1` to make `-a` the 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
@@ -240,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
@@ -247,6 +319,7 @@ 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.
--- ---
@@ -254,9 +327,11 @@ Or set `DWL_PREFERRED_TOOL` in `[net]` to whichever tool you have.
**Q: How do I limit how long `dwl` waits for a download?** **Q: How do I limit how long `dwl` waits for a download?**
Use the `-t` / `--timeout` option: Use the `-t` / `--timeout` option:
```bash ```bash
dwl -t 5 https://example.com/file.txt /tmp/file.txt 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. This sets a 5-second cap on both the connection and the overall transfer.
The timeout is propagated to `curl` (`--max-time` + `--connect-timeout`), The timeout is propagated to `curl` (`--max-time` + `--connect-timeout`),
`wget` (`--timeout`), or `fetch` (`-T`) transparently. `wget` (`--timeout`), or `fetch` (`-T`) transparently.
@@ -274,11 +349,13 @@ fails silently and the prompt appears immediately.
**Q: `help` only shows a list of functions. Can I get usage for a specific one?** **Q: `help` only shows a list of functions. Can I get usage for a specific one?**
Yes — pass the command name as an argument: Yes — pass the command name as an argument:
```bash ```bash
help gacp help gacp
help dwl help dwl
help taz help taz
``` ```
This calls `<command> --help` and prints the full usage for that function. This calls `<command> --help` and prints the full usage for that function.
--- ---
@@ -299,9 +376,11 @@ identified, and check that the package manager binary is in your `PATH`.
`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.
--- ---
@@ -332,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`).
--- ---
@@ -341,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
View 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.

View File

@@ -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]
@@ -53,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
# ============================================================================== # ==============================================================================
@@ -78,6 +81,21 @@ 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] [git]
# Fallback main branch name used when remote HEAD cannot be detected. # Fallback main branch name used when remote HEAD cannot be detected.
@@ -117,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/
@@ -171,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]
@@ -207,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

View File

@@ -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
@@ -260,7 +272,7 @@ mkdin='make DESTDIR=$PWD/dest-install install'
ssh='ssh -Y' ssh='ssh -Y'
# Resume mode for wget # Resume mode for wget
wget='wget -c' # resume mode by default wget='wget -c'
# Human readable by default # Human readable by default
df='df -H' df='df -H'

View File

@@ -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]**

View File

@@ -40,7 +40,7 @@ _profile_git_complete_remotes()
return 0 return 0
fi fi
COMPREPLY=( $(compgen -W "$(git remote 2>/dev/null)" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "$(git remote 2>/dev/null)" -- "$cur")
} }
_profile_git_complete_refs() _profile_git_complete_refs()
@@ -53,7 +53,7 @@ _profile_git_complete_refs()
return 0 return 0
fi fi
COMPREPLY=( $(compgen -W "$(git for-each-ref --format='%(refname:short)' refs/heads refs/remotes refs/tags 2>/dev/null)" -- "$cur") ) 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() _profile_git_complete_add_paths()
@@ -85,7 +85,7 @@ _profile_git_complete_add_paths()
return 0 return 0
fi fi
COMPREPLY=( $(compgen -f -- "${COMP_WORDS[COMP_CWORD]}") ) mapfile -t COMPREPLY < <(compgen -f -- "${COMP_WORDS[COMP_CWORD]}")
} }
_complete_gst() _complete_gst()
@@ -95,10 +95,10 @@ _complete_gst()
case "$cur" in case "$cur" in
-*) -*)
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
;; ;;
*) *)
COMPREPLY=( $(compgen -d -- "$cur") ) mapfile -t COMPREPLY < <(compgen -d -- "$cur")
;; ;;
esac esac
} }
@@ -116,7 +116,7 @@ _complete_ggraph()
;; ;;
esac esac
COMPREPLY=( $(compgen -W "-h --help -n --limit" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help -n --limit" -- "$cur")
} }
_complete_gsync() _complete_gsync()
@@ -125,7 +125,7 @@ _complete_gsync()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
return 0 return 0
fi fi
@@ -146,7 +146,7 @@ _complete_gacp()
esac esac
if [[ $cur == -* ]]; then if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help -a --auto -m --message" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help -a --auto -m --message" -- "$cur")
return 0 return 0
fi fi
@@ -159,7 +159,7 @@ _complete_greset()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help -x --with-ignored" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help -x --with-ignored" -- "$cur")
return 0 return 0
fi fi
@@ -172,7 +172,7 @@ _complete_gwip()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
else else
COMPREPLY=() COMPREPLY=()
fi fi
@@ -184,7 +184,7 @@ _complete_gprune()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help" -- "$cur")
return 0 return 0
fi fi
@@ -196,7 +196,7 @@ _complete_groot()
local cur local cur
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "-h --help -g --go" -- "$cur") ) mapfile -t COMPREPLY < <(compgen -W "-h --help -g --go" -- "$cur")
} }
_profile_git_register_completions() _profile_git_register_completions()

View 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

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "compress"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Smartly uncompress archives # Smartly uncompress archives
# Usage: utaz [option] [directorie(s)|file(s)] # Usage: utaz [option] [directorie(s)|file(s)]
@@ -44,41 +46,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,38 +99,46 @@ 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()
{ {
# lha typically extracts into the current directory # lha extracts into the current directory, so we cd into the target
# We ensure it hits the target directory # directory and feed it an absolute path to the archive.
(cd "$2" && lha -x "../$1") >/dev/null 2>&1 local src
src=$(realpath -- "$1") || return 1
(cd "$2" && lha -x "$src") >/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,13 +146,18 @@ utaz()
tar --zstd -xf "$1" -C "$2" tar --zstd -xf "$1" -C "$2"
} }
local _uncpio
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_uncpio() _uncpio()
{ {
# CPIO requires careful directory handling # cpio extracts into the current directory, so we cd into the target
(cd "$2" && cpio -id < "../$1") >/dev/null 2>&1 # directory and read from an absolute path to the archive.
local src
src=$(realpath -- "$1") || return 1
(cd "$2" && cpio -id < "$src") >/dev/null 2>&1
} }
local _uncabextract
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_uncabextract() _uncabextract()
{ {
@@ -142,6 +165,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 +173,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()
{ {
@@ -164,7 +189,9 @@ utaz()
disp E "Invalid options, use \"utaz --help\" to display usage." disp E "Invalid options, use \"utaz --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -182,6 +209,7 @@ utaz()
printf "\t- tar.bz2, .tbz2\n" printf "\t- tar.bz2, .tbz2\n"
printf "\t- tar.xz, .txz\n" printf "\t- tar.xz, .txz\n"
printf "\t- tar.lz, .tlz\n" printf "\t- tar.lz, .tlz\n"
printf "\t- lzo\n"
printf "\t- rar\n" printf "\t- rar\n"
printf "\t- arj\n" printf "\t- arj\n"
printf "\t- lha, lzh\n" printf "\t- lha, lzh\n"
@@ -230,8 +258,10 @@ utaz()
esac esac
fi fi
[[ -n ${createdir} && -n ${nodir} ]] && \ [[ -n ${createdir} && -n ${nodir} ]] && {
disp E "The --create-dir and --no-dir options are mutually exclusive." disp E "The --create-dir and --no-dir options are mutually exclusive."
return 1
}
for zitem in "${FILES[@]}"; do for zitem in "${FILES[@]}"; do
# Build list of input files to process, with whitespace-safe handling. # Build list of input files to process, with whitespace-safe handling.
@@ -266,6 +296,9 @@ utaz()
extractor="_unxz" extractor="_unxz"
;; ;;
*.tar.lz|*.tlz) *.tar.lz|*.tlz)
extractor="_unlzip"
;;
*.lzo)
extractor="_unlzop" extractor="_unlzop"
;; ;;
*.tar) *.tar)
@@ -331,6 +364,10 @@ utaz()
fi fi
disp I "Processing archive ${f} with ${extractor}..." disp I "Processing archive ${f} with ${extractor}..."
# Track whether we create the target directory so a corrupted
# archive never wipes a pre-existing directory of the same name.
local dir_created=0
[[ -d "${dir}" ]] || dir_created=1
if ! mkdir -p "${dir}"; then if ! mkdir -p "${dir}"; then
disp E "The filesystem can't create directories, exit!" && disp E "The filesystem can't create directories, exit!" &&
return 1 return 1
@@ -351,7 +388,7 @@ utaz()
;; ;;
*) *)
disp E "The compressed file ${f} seems corrupted, failed." disp E "The compressed file ${f} seems corrupted, failed."
rm -rf "${dir}" >/dev/null 2>&1 [[ ${dir_created} -eq 1 ]] && rm -rf "${dir}" >/dev/null 2>&1
continue continue
;; ;;
esac esac
@@ -401,6 +438,7 @@ export -f utaz
# -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
# -K, --keep-all Keep every produced version instead of only the smallest
# -p, --parallel Number of threads to use, or 'auto' to use detected CPU count # -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)
@@ -408,6 +446,7 @@ export -f utaz
taz() taz()
{ {
# Resolve runtime CPU count for --parallel=auto. # Resolve runtime CPU count for --parallel=auto.
local _taz_detect_cpus
_taz_detect_cpus() _taz_detect_cpus()
{ {
local cpus=1 local cpus=1
@@ -421,6 +460,18 @@ taz()
printf "%s\n" "$cpus" printf "%s\n" "$cpus"
} }
# Render a byte count as a human-readable size (B/KB/MB/GB/...), base 1024.
local _taz_human
# shellcheck disable=SC2329
_taz_human()
{
if command -v numfmt >/dev/null 2>&1; then
numfmt --to=iec --suffix=B -- "$1" 2>/dev/null && return 0
fi
printf "%sB\n" "$1"
}
local _doxz
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_doxz() _doxz()
{ {
@@ -441,10 +492,11 @@ taz()
return $? return $?
} }
local _dolz
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_dolz() _dolz()
{ {
local procopt="--threads $2" local procopt=(--threads "$2")
local command=plzip local command=plzip
command -v plzip >/dev/null 2>&1 || { command -v plzip >/dev/null 2>&1 || {
@@ -453,7 +505,7 @@ taz()
return 127 return 127
} }
command=lzip command=lzip
local procopt="" local procopt=()
[[ $2 -gt 1 ]] && [[ $2 -gt 1 ]] &&
disp W "lzip doesn't support multithreading, falling back to 1 thread." && disp W "lzip doesn't support multithreading, falling back to 1 thread." &&
disp W "Consider installing plzip to obtain multithreading abilities." disp W "Consider installing plzip to obtain multithreading abilities."
@@ -461,17 +513,18 @@ taz()
local opt=() local opt=()
[[ $4 ]] && opt=('-vv') [[ $4 ]] && opt=('-vv')
opt+=("$procopt") opt+=("${procopt[@]}")
# Compress with lzip (lzma) # Compress with lzip (lzma)
$command "${opt[@]}" --keep "-$3" "$1" $command "${opt[@]}" --keep "-$3" "$1"
return $? return $?
} }
local _dogz
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_dogz() _dogz()
{ {
local procopt="--processes $2" local procopt=(--processes "$2")
local command=pigz local command=pigz
command -v pigz >/dev/null 2>&1 || { command -v pigz >/dev/null 2>&1 || {
@@ -480,7 +533,7 @@ taz()
return 127 return 127
} }
local command="gzip --compress" local command="gzip --compress"
local procopt="" local procopt=()
[[ $2 -gt 1 ]] && [[ $2 -gt 1 ]] &&
disp W "gzip doesn't support multithreading, falling back to 1 thread." && disp W "gzip doesn't support multithreading, falling back to 1 thread." &&
disp W "Consider installing pigz to obtain multithreading abilities." disp W "Consider installing pigz to obtain multithreading abilities."
@@ -488,17 +541,18 @@ taz()
local opt=() local opt=()
[[ $4 ]] && opt=('--verbose') [[ $4 ]] && opt=('--verbose')
opt+=("$procopt") opt+=("${procopt[@]}")
# Compress with gzip # 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()
{ {
local procopt="-p$2" local procopt=("-p$2")
local command=pbzip2 local command=pbzip2
command -v pbzip2 >/dev/null 2>&1 || { command -v pbzip2 >/dev/null 2>&1 || {
@@ -507,7 +561,7 @@ taz()
return 127 return 127
} }
local command=bzip2 local command=bzip2
local procopt="" local procopt=()
[[ $2 -gt 1 ]] && [[ $2 -gt 1 ]] &&
disp W "bzip2 doesn't support multithreading, falling back to 1 thread." && disp W "bzip2 doesn't support multithreading, falling back to 1 thread." &&
disp W "Consider installing pbzip2 to obtain multithreading abilities." disp W "Consider installing pbzip2 to obtain multithreading abilities."
@@ -515,13 +569,14 @@ taz()
local opt=() local opt=()
[[ $4 ]] && opt=('-v') [[ $4 ]] && opt=('-v')
opt+=("$procopt") opt+=("${procopt[@]}")
# Compress with bz2 # Compress with bz2
$command "${opt[@]}" --compress --keep "-$3" "$1" $command "${opt[@]}" --compress --keep "-$3" "$1"
return $? return $?
} }
local _dolzo
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_dolzo() _dolzo()
{ {
@@ -540,13 +595,16 @@ taz()
} }
local PARSED local PARSED
PARSED=$(getopt -o hdf:p:vq123456789 --long help,delete,format:,parallel:,verbose,quiet --name "taz" -- "$@") PARSED=$(getopt -o hdf:Kp:vq123456789 --long help,delete,format:,keep-all,parallel:,verbose,quiet --name "taz" -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after # shellcheck disable=SC2181 # getopt return code is checked immediately after
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
disp E "Invalid options, use \"taz --help\" to display usage." disp E "Invalid options, use \"taz --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
local compforms=()
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -555,8 +613,9 @@ taz()
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-K, --keep-all\tKeep every produced version instead of only the smallest\n"
printf "\t-p, --parallel\tNumber of threads, or 'auto' for runtime CPU count\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"
@@ -580,10 +639,20 @@ taz()
;; ;;
-f|--format) -f|--format)
local compform=$2 # Accept comma/space separated lists and repeated -f flags.
local _f
# shellcheck disable=SC2086 # intentional word splitting on the list
for _f in ${2//,/ }; do
compforms+=("$_f")
done
shift 2 shift 2
;; ;;
-K|--keep-all)
local keepall=1
shift
;;
-p|--parallel) -p|--parallel)
local nproc=$2 local nproc=$2
shift 2 shift 2
@@ -600,7 +669,7 @@ taz()
;; ;;
-[1-9]) -[1-9])
complevel="${1#-}" local complevel="${1#-}"
shift shift
;; ;;
--) --)
@@ -619,11 +688,32 @@ taz()
local FILES=("$@") local FILES=("$@")
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".") [[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
[[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz} [[ ! $compform ]] && local compform=${TAZ_DEFAULT_FORMAT:-lz}
[[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-auto} [[ ! $nproc ]] && local nproc=${TAZ_DEFAULT_THREADS:-auto}
[[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6} [[ ! $complevel ]] && local complevel=${TAZ_DEFAULT_LEVEL:-6}
[[ $verbose -gt 1 && $quiet -gt 1 ]] && [[ -n $verbose && -n $quiet ]] && {
disp E "The --verbose and --quiet options can't be used together." disp E "The --verbose and --quiet options can't be used together."
return 1
}
# Fall back to the configured (or built-in) default format list.
local _f
if [[ ${#compforms[@]} -eq 0 ]]; then
local _deffmt=${TAZ_DEFAULT_FORMAT:-lz}
# shellcheck disable=SC2086 # intentional word splitting on the list
for _f in ${_deffmt//,/ }; do
compforms+=("$_f")
done
fi
# Reject unknown formats early so we never dispatch to a missing _do*.
local _supported=" lz xz bz2 gz lzo tar "
for _f in "${compforms[@]}"; do
[[ $_supported == *" $_f "* ]] || {
disp E "Unsupported format '${_f}' (supported: lz xz bz2 gz lzo tar)."
return 1
}
done
# Backward compatibility: 0 previously meant auto-detect. # Backward compatibility: 0 previously meant auto-detect.
[[ $nproc == 0 ]] && nproc=auto [[ $nproc == 0 ]] && nproc=auto
@@ -634,6 +724,10 @@ taz()
return 1 return 1
fi fi
# Map each format to the extension its compressor appends. "tar" performs
# no compression, so its candidate is the tar (or source file) itself.
local -A _taz_ext=( [lz]=lz [xz]=xz [bz2]=bz2 [gz]=gz [lzo]=lzo [tar]="" )
for item in "${FILES[@]}"; do for item in "${FILES[@]}"; do
local donetar=0 local donetar=0
disp I "Processing $item..." disp I "Processing $item..."
@@ -652,23 +746,93 @@ taz()
local fname=$item local fname=$item
[[ $donetar -gt 0 ]] && fname=$item.tar [[ $donetar -gt 0 ]] && fname=$item.tar
# Skip compression part if tar is asked # Compress the archive with every requested format; the smallest
if [[ $compform != "tar" ]]; then # resulting file is kept and the others are discarded (unless --keep-all).
disp I "\t Compressing archive..." local candidates=() fmt exec_code aborted=0 tar_requested=0
local exec_code=0 for fmt in "${compforms[@]}"; do
"_do$compform" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$? if [[ $fmt == tar ]]; then
[[ ! $exec_code -eq 0 ]] && case $exec_code in tar_requested=1
candidates+=("$fname")
continue
fi
disp I "\t Compressing archive with ${fmt}..."
exec_code=0
"_do$fmt" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$?
case $exec_code in
0)
candidates+=("$fname.${_taz_ext[$fmt]}")
;;
127) 127)
disp E "Compression program unavailable, aborting." disp E "Compression program for '${fmt}' unavailable, aborting."
return 127 aborted=1
break
;; ;;
*) *)
disp E "Compression program returned an error, not deleting anything if asked, skipping to next item." disp W "Compression with '${fmt}' failed, skipping this format."
continue
;; ;;
esac esac
done
[[ $donetar -gt 0 ]] && rm "$fname" # Remove partial output produced before an unrecoverable abort.
local c
if [[ $aborted -eq 1 ]]; then
for c in "${candidates[@]}"; do
[[ $c != "$fname" ]] && rm -f -- "$c"
done
[[ $donetar -gt 0 ]] && rm -f -- "$fname"
return 127
fi
# Select the smallest archive among the produced candidates.
local winner="" winner_size=-1 sz
local -A _csize=()
for c in "${candidates[@]}"; do
[[ -f $c ]] || continue
sz=$(stat -c%s -- "$c" 2>/dev/null) || continue
_csize["$c"]=$sz
if [[ $winner_size -lt 0 || $sz -lt $winner_size ]]; then
winner_size=$sz
winner=$c
fi
done
if [[ -z $winner ]]; then
disp E "No archive could be produced for ${item}, skipping to next item."
[[ $donetar -gt 0 ]] && rm -f -- "$fname"
continue
fi
# In verbose mode, list every produced archive with its size and how
# much larger it is than the smallest one that will be kept.
if [[ -n $verbose && ${#_csize[@]} -gt 0 ]]; then
disp I "\t Produced archives:"
for c in "${candidates[@]}"; do
[[ -n ${_csize[$c]+x} ]] || continue
if [[ $c == "$winner" ]]; then
disp "\t ${c} $(_taz_human "${_csize[$c]}") (kept, smallest)"
else
disp "\t ${c} $(_taz_human "${_csize[$c]}") (+$(_taz_human "$(( _csize[$c] - winner_size ))"))"
fi
done
fi
# Discard every archive but the smallest, unless --keep-all was given.
# Either way, drop the intermediate tar when it was not itself a
# requested output format.
if [[ -z $keepall ]]; then
for c in "${candidates[@]}"; do
[[ $c != "$winner" ]] && rm -f -- "$c"
done
fi
[[ $donetar -gt 0 && $tar_requested -eq 0 ]] && rm -f -- "$fname"
if [[ -n $keepall ]]; then
[[ ${#compforms[@]} -gt 1 ]] &&
disp I "\t Kept all produced archives (smallest: ${winner}, $(_taz_human "$winner_size"))."
else
[[ ${#compforms[@]} -gt 1 ]] &&
disp I "\t Kept smallest archive: ${winner} ($(_taz_human "$winner_size"))."
fi fi
if [[ $willrm ]]; then if [[ $willrm ]]; then
@@ -676,12 +840,9 @@ taz()
rm -r "$item" rm -r "$item"
fi fi
done done
unset quiet
} }
export -f taz export -f taz
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "compress"
# EOF # EOF

439
profile.d/conf.sh Executable file
View File

@@ -0,0 +1,439 @@
#!/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-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.$$"
# The value is passed via ENVIRON (not awk -v) so backslashes are preserved
# verbatim; awk -v would interpret C escape sequences (\t, \n, \e, ...).
_CONF_SAVE_VAL="$value" \
awk -v sec="$section" -v key="$key" '
BEGIN {
val = ENVIRON["_CONF_SAVE_VAL"]
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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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
;;
*)
disp E "Invalid option, use \"conf_dump --help\" to display options list"
return 1
;;
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); when the key is not set in the environment we
# fall back to the value written in the comment, i.e. the documented
# default. 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 (val == "") { val = line; sub(/^[^=]*=[[:space:]]*/, "", val) }
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

View File

@@ -34,10 +34,12 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "debug"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display a backtrace # Display a backtrace
# Usage: backtrace # Usage: backtrace
function backtrace() backtrace()
{ {
printf "========= Call stack =========\n" printf "========= Call stack =========\n"
local i=1 # We begin at 1 to ignore backtrace itself local i=1 # We begin at 1 to ignore backtrace itself
@@ -52,7 +54,7 @@ function backtrace()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Function to be trapped for errors investigation # Function to be trapped for errors investigation
function error() error()
{ {
local errcode=$? local errcode=$?
backtrace backtrace
@@ -64,10 +66,6 @@ function error()
# Usage: settrace <--on|--off|--status> # Usage: settrace <--on|--off|--status>
settrace() settrace()
{ {
local status="off"
[[ $(trap -p ERR) ]] && status="on"
#trap -p ERR
local PARSED local PARSED
PARSED=$(getopt -oh --long help,on,off,status,force -- "$@") PARSED=$(getopt -oh --long help,on,off,status,force -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after # shellcheck disable=SC2181 # getopt return code is checked immediately after
@@ -75,8 +73,13 @@ settrace()
disp E "Invalid options, use \"settrace --help\" to display usage." disp E "Invalid options, use \"settrace --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
local force=0 readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
# First pass: gather the requested action and flags. --on, --off and
# --status are mutually exclusive; --force only modifies --on.
local action="" force=0
while true; do while true; do
case $1 in case $1 in
-h|--help) -h|--help)
@@ -84,34 +87,24 @@ settrace()
printf "Options:\n" printf "Options:\n"
printf "\t--on\t\tActivate backtrace generation\n" printf "\t--on\t\tActivate backtrace generation\n"
printf "\t--force\t\tForce replacement of existing trap (use with --on)\n" printf "\t--force\t\tForce replacement of existing trap (use with --on)\n"
printf "\t--off\t\tDeactivate backtrace generation\n\n" printf "\t--off\t\tDeactivate backtrace generation\n"
printf "\t--status\tDisplay whether the ERR trap is set\n\n"
printf "That function active a trap event on error. If the script you want to\n" printf "That function active a trap event on error. If the script you want to\n"
printf "debug overload the ERR bash trap, it will not work.\n" printf "debug overload the ERR bash trap, it will not work.\n"
return 0 return 0
;; ;;
--on) --on|--off|--status)
if [[ ${status} == "on" ]] && [[ $force -eq 0 ]]; then if [[ -n $action ]]; then
disp E "ERR signal trap is already set. Use --force to replace it." disp E "The --on, --off and --status options are mutually exclusive."
return 1 return 1
fi fi
trap "error" ERR action="${1#--}"
shift shift
;; ;;
--force) --force)
force=1 force=1
shift shift
;; ;;
--off)
if [[ ${status} != "on" ]]; then
disp W "ERR signal trap is already unset!"
fi
trap - ERR
shift
;;
--status)
disp I "Trap signal is ${status}."
shift
;;
--) --)
shift shift
break break
@@ -122,7 +115,33 @@ settrace()
;; ;;
esac esac
done done
unset status force
# Second pass: evaluate the current trap state, then apply the action.
local status="off"
[[ -n $(trap -p ERR) ]] && status="on"
case $action in
on)
if [[ $status == "on" && $force -eq 0 ]]; then
disp E "ERR signal trap is already set. Use --force to replace it."
return 1
fi
set -o errtrace
trap "error" ERR
;;
off)
[[ $status != "on" ]] && disp W "ERR signal trap is already unset!"
trap - ERR
set +o errtrace
;;
status)
disp I "Trap signal on error is ${status}."
;;
"")
disp E "No action specified, use \"settrace --help\" to display usage."
return 1
;;
esac
} }
export -f settrace export -f settrace
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf disp
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Color definitions # Color definitions
set_colors() set_colors()
@@ -42,7 +44,7 @@ set_colors()
export DEFAULTFG='\e[0;39m' export DEFAULTFG='\e[0;39m'
export DEFAULTBG='\e[0;49m' export DEFAULTBG='\e[0;49m'
export DEFAULTCOL="${DEFAULTBG}${DEFAULTFG}" export DEFAULTCOL="${DEFAULTBG}${DEFAULTFG}"
export RESETCOL=$'\e[0m' export RESETCOL='\e[0m'
# Regular Colors # Regular Colors
export Black='\e[0;30m' export Black='\e[0;30m'
@@ -128,12 +130,12 @@ export -f set_colors
# D : debug (cyan) # D : debug (cyan)
disp() disp()
{ {
local _disp_print_wrapped
_disp_print_wrapped() _disp_print_wrapped()
{ {
local prefix="$1" local prefix="$1"
local prefix_len="$2" local target_fd="$2"
local target_fd="$3" shift 2
shift 3
local message="$*" local message="$*"
local cols="${COLUMNS:-}" local cols="${COLUMNS:-}"
@@ -142,14 +144,57 @@ disp()
fi fi
[[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80 [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80
# Match ANSI CSI sequences in real-ESC, literal \e and literal \033 forms
# so their on-screen width (zero) is excluded from all width calculations.
local ansi_re=$'\e\\[[0-9;]*[A-Za-z]'
ansi_re+='|\\e\[[0-9;]*[A-Za-z]'
ansi_re+='|\\033\[[0-9;]*[A-Za-z]'
# The hanging-indent width is the prefix's VISIBLE length (ANSI stripped),
# so callers no longer need to pass a separate plain-text length.
local _pv=$prefix
while [[ $_pv =~ $ansi_re ]]; do _pv=${_pv/"${BASH_REMATCH[0]}"/}; done
local indent_len=0 local indent_len=0
[[ "$prefix_len" =~ ^[0-9]+$ && "$prefix_len" -gt 0 ]] && indent_len=$((prefix_len + 1)) (( ${#_pv} > 0 )) && indent_len=$(( ${#_pv} + 1 ))
local width=$((cols - indent_len)) local width=$((cols - indent_len))
(( width < 10 )) && width=10 (( width < 10 )) && width=10
local wrapped # ANSI-aware soft wrap (fold -s semantics): break at the last space that
wrapped=$(printf "%s" "$message" | fold -s -w "$width") # keeps a line within `width` VISIBLE columns, hard-breaking only when a
# run has no space. ANSI escapes are copied through at zero width so they
# no longer inflate the measured width and cause premature wrapping.
local wrapped="" line="" line_vis=0 last_space=-1
local _i=0 _n=${#message}
while (( _i < _n )); do
local _sub=${message:_i}
if [[ $_sub =~ ^($ansi_re) ]]; then
line+=${BASH_REMATCH[0]}
_i=$(( _i + ${#BASH_REMATCH[0]} ))
continue
fi
local _c=${message:_i:1}
_i=$(( _i + 1 ))
line+=$_c
(( line_vis++ ))
[[ $_c == ' ' ]] && last_space=${#line}
if (( line_vis > width )); then
if (( last_space > 0 )); then
wrapped+=${line:0:last_space-1}$'\n'
line=${line:last_space}
local _st=$line
while [[ $_st =~ $ansi_re ]]; do _st=${_st/"${BASH_REMATCH[0]}"/}; done
line_vis=${#_st}
else
local _keep=${line: -1}
wrapped+=${line:0:${#line}-1}$'\n'
line=$_keep
line_vis=1
fi
last_space=-1
fi
done
wrapped+=$line
local first_line=1 local first_line=1
local line local line
@@ -185,50 +230,46 @@ disp()
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="$heads_plain" local heads="[ info ]"
fi fi
shift shift
[[ -z $QUIET || $QUIET -ne 1 ]] && \ [[ -z $QUIET || $QUIET -ne 1 ]] && \
_disp_print_wrapped "$heads" "${#heads_plain}" 1 "$*" _disp_print_wrapped "$heads" 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="$heads_plain" local heads="[ Warning ]"
fi fi
shift shift
_disp_print_wrapped "$heads" "${#heads_plain}" 2 "$*" _disp_print_wrapped "$heads" 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="$heads_plain" local heads="[ ERROR ]"
fi fi
shift shift
_disp_print_wrapped "$heads" "${#heads_plain}" 2 "$*" _disp_print_wrapped "$heads" 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="$heads_plain" local heads="[ debug ]"
fi fi
shift shift
[[ -n $DEBUG && $DEBUG -gt 1 ]] && \ [[ -n $DEBUG && $DEBUG -gt 1 ]] && \
_disp_print_wrapped "$heads" "${#heads_plain}" 1 "$*" _disp_print_wrapped "$heads" 1 "$*"
;; ;;
* ) * )
[[ -z $QUIET || $QUIET -ne 1 ]] && \ [[ -z $QUIET || $QUIET -ne 1 ]] && \
_disp_print_wrapped "" 0 1 "$*" _disp_print_wrapped "" 1 "$*"
;; ;;
esac esac
} }
@@ -241,6 +282,7 @@ export -f disp
# Usage: mdcat [file] # Usage: mdcat [file]
mdcat() mdcat()
{ {
local _mdcat_style_inline
_mdcat_style_inline() _mdcat_style_inline()
{ {
local text="$1" local text="$1"
@@ -346,9 +388,10 @@ mdcat()
printf "%s\n" "$text" printf "%s\n" "$text"
} }
local _mdcat_print_hr
_mdcat_print_hr() _mdcat_print_hr()
{ {
local cols="${COLUMNS:-}" local cols="${_mdcat_cols:-}"
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
cols=$(tput cols 2>/dev/null) cols=$(tput cols 2>/dev/null)
fi fi
@@ -364,13 +407,14 @@ mdcat()
fi fi
} }
local _mdcat_print_code_block
_mdcat_print_code_block() _mdcat_print_code_block()
{ {
local lang="$1" local lang="$1"
shift shift
local -a lines=("$@") local -a lines=("$@")
local cols="${COLUMNS:-}" local cols="${_mdcat_cols:-}"
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
cols=$(tput cols 2>/dev/null) cols=$(tput cols 2>/dev/null)
fi fi
@@ -422,14 +466,11 @@ mdcat()
printf "%b%s%b\n" "$frame_on" "$border" "$off" 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() _mdcat_print_table()
{ {
local -a lines=("$@") local _mdcat_parse_table_row
local -a table_rows=()
local -a col_widths=()
local i j ncols=0
local sep=$'\x1f'
_mdcat_parse_table_row() _mdcat_parse_table_row()
{ {
local input="$1" local input="$1"
@@ -466,8 +507,17 @@ mdcat()
printf '%s' "$joined" printf '%s' "$joined"
} }
local -a lines=("$@")
local -a table_rows=()
local -a col_widths=()
local -A cell_styled=() cell_vislen=()
local i j ncols=0 orow=0
local sep=$'\x1f'
# Parse header and data rows, skipping the Markdown separator row. # Parse header and data rows, skipping the Markdown separator row.
# Width is computed from visible text length with ANSI escapes stripped. # Each cell is styled exactly once here; the styled text and its visible
# width (ANSI stripped) are cached and reused when drawing, so the costly
# _mdcat_style_inline runs once per cell instead of twice.
for ((i=0; i<${#lines[@]}; ++i)); do for ((i=0; i<${#lines[@]}; ++i)); do
(( i == 1 )) && continue (( i == 1 )) && continue
local parsed local parsed
@@ -478,13 +528,15 @@ mdcat()
IFS="$sep" read -r -a row <<< "$parsed" IFS="$sep" read -r -a row <<< "$parsed"
(( ncols < ${#row[@]} )) && ncols=${#row[@]} (( ncols < ${#row[@]} )) && ncols=${#row[@]}
for ((j=0; j<${#row[@]}; ++j)); do for ((j=0; j<${#row[@]}; ++j)); do
local vis local styled visible key="$orow:$j"
vis=$(_mdcat_style_inline "${row[j]}") styled=$(_mdcat_style_inline "${row[j]}")
vis=$(printf '%b' "$vis" | sed -E 's/\x1B\[[0-9;]*[mK]//g') visible=$(printf '%b' "$styled" | sed -E 's/\x1B\[[0-9;]*[mK]//g')
local cell_len=${#vis} cell_styled["$key"]="$styled"
cell_vislen["$key"]=${#visible}
[[ -z "${col_widths[j]}" ]] && col_widths[j]=0 [[ -z "${col_widths[j]}" ]] && col_widths[j]=0
(( col_widths[j] < cell_len )) && col_widths[j]=$cell_len (( col_widths[j] < ${#visible} )) && col_widths[j]=${#visible}
done done
orow=$((orow + 1))
done done
# Ensure all width slots are initialized before drawing borders. # Ensure all width slots are initialized before drawing borders.
@@ -500,17 +552,12 @@ mdcat()
done done
printf "%b\n" "$IBlack$border$RESETCOL" printf "%b\n" "$IBlack$border$RESETCOL"
# Print header row # Print header row (cached styled cells)
local -a header=()
IFS="$sep" read -r -a header <<< "${table_rows[0]}"
printf "%b|" "$IBlack" printf "%b|" "$IBlack"
for ((j=0; j<ncols; ++j)); do for ((j=0; j<ncols; ++j)); do
local raw_cell="${header[j]:-}" local hkey="0:$j"
local styled_cell local styled_cell="${cell_styled[$hkey]:-}"
styled_cell=$(_mdcat_style_inline "$raw_cell") local pad=$((col_widths[j] - ${cell_vislen[$hkey]:-0}))
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 (( pad < 0 )) && pad=0
printf " %b%b%*s%b |" "$BBlue" "$styled_cell" "$pad" "" "$IBlack" printf " %b%b%*s%b |" "$BBlue" "$styled_cell" "$pad" "" "$IBlack"
done done
@@ -523,18 +570,13 @@ mdcat()
done done
printf "%b\n" "$RESETCOL" printf "%b\n" "$RESETCOL"
# Print data rows # Print data rows (cached styled cells)
for ((i=1; i<${#table_rows[@]}; ++i)); do for ((i=1; i<${#table_rows[@]}; ++i)); do
local -a row=()
IFS="$sep" read -r -a row <<< "${table_rows[i]}"
printf "%b|" "$IBlack" printf "%b|" "$IBlack"
for ((j=0; j<ncols; ++j)); do for ((j=0; j<ncols; ++j)); do
local raw_cell="${row[j]:-}" local key="$i:$j"
local styled_cell local styled_cell="${cell_styled[$key]:-}"
styled_cell=$(_mdcat_style_inline "$raw_cell") local pad=$((col_widths[j] - ${cell_vislen[$key]:-0}))
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 (( pad < 0 )) && pad=0
printf " %b%b%*s%b |" "$RESETCOL" "$styled_cell" "$pad" "" "$IBlack" printf " %b%b%*s%b |" "$RESETCOL" "$styled_cell" "$pad" "" "$IBlack"
done done
@@ -553,7 +595,9 @@ mdcat()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -598,19 +642,42 @@ mdcat()
local -a code_lines=() local -a code_lines=()
local in_table=0 local in_table=0
local -a table_lines=() local -a table_lines=()
while IFS= read -r raw || [[ -n "$raw" ]]; do
# Resolve the terminal width once for the whole render instead of shelling
# out to tput for every printed line.
local _mdcat_cols="${COLUMNS:-}"
if [[ -z "$_mdcat_cols" || ! "$_mdcat_cols" =~ ^[0-9]+$ || "$_mdcat_cols" -lt 20 ]]; then
_mdcat_cols=$(tput cols 2>/dev/null)
fi
[[ -z "$_mdcat_cols" || ! "$_mdcat_cols" =~ ^[0-9]+$ || "$_mdcat_cols" -lt 20 ]] && _mdcat_cols=80
# Single-line push-back buffer: table detection reads one line ahead, so a
# non-separator look-ahead line must be reprocessed rather than discarded.
local pending_line="" has_pending=0
while :; do
if [[ $has_pending -eq 1 ]]; then
raw="$pending_line"
has_pending=0
elif ! IFS= read -r raw; then
[[ -n "$raw" ]] || break
fi
line="${raw%$'\r'}" line="${raw%$'\r'}"
# Table detection: line with |, next line with | and --- # Table detection: line with |, next line with | and ---
if [[ $in_table -eq 0 && "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ ]]; then if [[ $in_table -eq 0 && "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ ]]; then
local next local next="" next_ok=1
IFS= read -r next || true IFS= read -r next || next_ok=0
# Accept: | --- | --- | or |:---|---:| etc. # Accept: | --- | --- | or |:---|---:| etc.
if [[ "$next" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then if [[ "$next" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then
in_table=1 in_table=1
table_lines=("$line" "$next") table_lines=("$line" "$next")
continue continue
fi fi
# Not a separator: push the look-ahead line back so it is not lost.
if [[ $next_ok -eq 1 || -n "$next" ]]; then
pending_line="$next"
has_pending=1
fi
fi fi
if [[ $in_table -eq 1 ]]; then if [[ $in_table -eq 1 ]]; then
# Accept table row if it starts and ends with | # Accept table row if it starts and ends with |
@@ -700,16 +767,19 @@ mdcat()
printf "%b\n" "$(_mdcat_style_inline "$line")" printf "%b\n" "$(_mdcat_style_inline "$line")"
done < "${input_file:-/dev/stdin}" done < "${input_file:-/dev/stdin}"
# Flush any block still open at end-of-input.
if [[ $in_code -eq 1 ]]; then if [[ $in_code -eq 1 ]]; then
_mdcat_print_code_block "$code_lang" "${code_lines[@]}" _mdcat_print_code_block "$code_lang" "${code_lines[@]}"
fi fi
if [[ $in_table -eq 1 ]]; then
_mdcat_print_table "${table_lines[@]}"
fi
} }
export -f mdcat export -f mdcat
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Load disp section variables # Load color codes
load_conf disp
set_colors set_colors
# EOF # EOF

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "filefct"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Expand wildcards in a file/directory list and quote the results # Expand wildcards in a file/directory list and quote the results
# Usage: expandlist [options] <item1 [item2 ... itemN]> # Usage: expandlist [options] <item1 [item2 ... itemN]>
@@ -47,7 +49,9 @@ expandlist()
disp E "Invalid options, use \"expandlist --help\" to display usage." disp E "Invalid options, use \"expandlist --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -97,6 +101,7 @@ expandlist()
continue continue
fi fi
local content
for content in "${expanded[@]}"; do for content in "${expanded[@]}"; do
if (( matched )); then if (( matched )); then
result+="$separator" result+="$separator"
@@ -104,6 +109,7 @@ expandlist()
result+="\"$content\"" result+="\"$content\""
matched=1 matched=1
done done
unset content
done done
shopt -u nullglob shopt -u nullglob
@@ -134,7 +140,9 @@ clean()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -180,6 +188,7 @@ clean()
(( ! recursive )) && findopt=(-maxdepth 1) (( ! recursive )) && findopt=(-maxdepth 1)
(( ! force )) && rmopt=(-i) (( ! force )) && rmopt=(-i)
local dir f
for dir in "${dirlist[@]}"; do for dir in "${dirlist[@]}"; do
find "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 | find "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 |
while IFS= read -r -d '' f; do while IFS= read -r -d '' f; do
@@ -189,10 +198,18 @@ clean()
else else
printf 'rm -- "%s"\n' "$f" printf 'rm -- "%s"\n' "$f"
fi fi
elif (( force )); then
# No confirmation: rm reads no stdin, so leave it attached
# to the find pipe (a /dev/tty redirect would fail when there
# is no controlling terminal, e.g. cron).
rm -- "$f" || disp E "Failed to remove \"$f\"."
else else
rm "${rmopt[@]}" -- "$f" # Interactive rm -i: read the answer from the terminal, not
# from the find pipe that feeds this loop's stdin.
rm -i -- "$f" </dev/tty || disp E "Failed to remove \"$f\"."
fi fi
done done
unset f
done done
} }
export -f clean export -f clean
@@ -212,7 +229,7 @@ mcd()
return 0 return 0
fi fi
if [[ ! $# -eq 1 ]]; then if (( $# != 1 )); then
disp E "Missing parameter. Use \"mcd --help\" to display usage." disp E "Missing parameter. Use \"mcd --help\" to display usage."
return 1 return 1
fi fi
@@ -245,13 +262,15 @@ rmspc()
local mvopt=() local mvopt=()
local PARSED local PARSED
PARSED=$(getopt -o hr:c::vs --long help,recursive,subst-char::,verbose,shell -n 'rmspc' -- "$@") PARSED=$(getopt -o hrc::vs --long help,recursive,subst-char::,verbose,shell -n 'rmspc' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after # shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"rmspc --help\" to display usage." disp E "Invalid options, use \"rmspc --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -300,6 +319,7 @@ rmspc()
(( verb )) && mvopt=(-v) (( verb )) && mvopt=(-v)
shopt -s nullglob shopt -s nullglob
local f
for f in *; do for f in *; do
if (( recurs )) && [[ -d "$f" ]]; then if (( recurs )) && [[ -d "$f" ]]; then
( (
@@ -307,11 +327,14 @@ rmspc()
(( verb )) && disp I "Entering directory $(pwd)/$f ..." (( verb )) && disp I "Entering directory $(pwd)/$f ..."
pushd "$f" >/dev/null || return 1 pushd "$f" >/dev/null || return 1
if (( substchar_set )); then # Build the recursive argument list by value: the ${var:+-flag}
rmspc ${recurs:+-r} -c "$substchar" ${verb:+-v} ${shell:+-s} # idiom would expand on the *string* "0" (non-empty) and wrongly
else # force -v/-s on every recursive call.
rmspc ${recurs:+-r} ${verb:+-v} ${shell:+-s} local rargs=(-r)
fi (( verb )) && rargs+=(-v)
(( shell )) && rargs+=(-s)
(( substchar_set )) && rargs+=(-c "$substchar")
rmspc "${rargs[@]}"
popd >/dev/null || return 1 popd >/dev/null || return 1
(( verb )) && disp I "Leaving directory $(pwd)/$lastdir" (( verb )) && disp I "Leaving directory $(pwd)/$lastdir"
@@ -335,6 +358,7 @@ rmspc()
fi fi
fi fi
done done
unset f
shopt -u nullglob shopt -u nullglob
} }
export -f rmspc export -f rmspc
@@ -370,7 +394,9 @@ file_stats()
disp E "Invalid options, use \"file_stats --help\" to display usage." disp E "Invalid options, use \"file_stats --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -460,17 +486,27 @@ file_stats()
fi fi
# Extension list filter # Extension list filter
local exts
if [[ -n "$ext_list" ]]; then if [[ -n "$ext_list" ]]; then
IFS=',' read -ra exts <<< "$ext_list" IFS=',' read -ra exts <<< "$ext_list"
find_cmd+=('(') find_cmd+=('(')
local i
for i in "${!exts[@]}"; do for i in "${!exts[@]}"; do
[[ $i -ne 0 ]] && find_cmd+=(-o) [[ $i -ne 0 ]] && find_cmd+=(-o)
find_cmd+=(-iname "*.${exts[$i]}") find_cmd+=(-iname "*.${exts[$i]}")
done done
unset i
find_cmd+=(')') find_cmd+=(')')
fi fi
unset exts
# 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
@@ -560,7 +596,7 @@ file_stats()
human_readable(high)) human_readable(high))
# We store buckets in an array, access them by index b # We store buckets in an array, access them by index b
printf "%-25s : %6d fichiers\n", label, bucket[b] printf "%-25s : %6d files\n", label, bucket[b]
} }
} }
}' }'
@@ -588,7 +624,9 @@ findbig()
disp E "Invalid options, use \"findbig --help\" to display usage." disp E "Invalid options, use \"findbig --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -637,12 +675,34 @@ 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
local line
"$_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
unset line
else
"$_fd" "${_fd_args[@]}" --exec stat --printf="%s %n\n" 2>/dev/null \
| sort -rn | head -n "$limit"
fi
elif (( details )); then
local line
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#* }"
ls -ld -- "$path" ls -ld -- "$path"
done done
unset line
else else
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"
fi fi
@@ -671,7 +731,9 @@ findzero()
disp E "Invalid options, use \"findzero --help\" to display usage." disp E "Invalid options, use \"findzero --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -713,7 +775,31 @@ 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..."
local f
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
printf "%s\n" "$f"
rm -f -- "$f"
done
unset f
elif (( details )); then
local f
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
ls -ls -- "$f"
done
unset f
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
@@ -745,7 +831,9 @@ finddead()
disp E "Invalid options, use \"finddead --help\" to display usage." disp E "Invalid options, use \"finddead --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -787,7 +875,35 @@ 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..."
local f
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
[[ -e "$f" ]] || { printf "%s\n" "$f"; rm -f -- "$f"; }
done
unset f
elif (( details )); then
local f
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
[[ -e "$f" ]] || ls -ls -- "$f"
done
unset f
else
local f
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
[[ -e "$f" ]] || printf "%s\n" "$f"
done
unset f
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
@@ -800,6 +916,4 @@ export -f finddead
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "filefct"
# EOF # EOF

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "fun"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Make non-IT peoples think you're busy doing something hard # Make non-IT peoples think you're busy doing something hard
# Usage: busy [options] [pattern] # Usage: busy [options] [pattern]
@@ -53,7 +55,9 @@ busy()
disp E "Invalid options, use \"busy --help\" to display usage." disp E "Invalid options, use \"busy --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -94,6 +98,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,9 +117,380 @@ busy()
wait "$sub_pid" 2>/dev/null wait "$sub_pid" 2>/dev/null
return 0 return 0
} }
export -f busy
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "fun" # ------------------------------------------------------------------------------
# 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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
# ------------------------------------------------------------------------------
# EOF # EOF

127
profile.d/git.sh Executable file → Normal file
View File

@@ -34,8 +34,9 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ load_conf git
# ------------------------------------------------------------------------------
# Built-in defaults (can be overridden from [git] section in profile.conf) # Built-in defaults (can be overridden from [git] section in profile.conf)
: "${GIT_MAIN_BRANCH:=main}" : "${GIT_MAIN_BRANCH:=main}"
: "${GIT_DEFAULT_REMOTE:=origin}" : "${GIT_DEFAULT_REMOTE:=origin}"
@@ -71,7 +72,7 @@ _git_default_branch()
head=$(git symbolic-ref --quiet --short "refs/remotes/${remote}/HEAD" 2>/dev/null) || true head=$(git symbolic-ref --quiet --short "refs/remotes/${remote}/HEAD" 2>/dev/null) || true
if [[ -n $head ]]; then if [[ -n $head ]]; then
printf "%s\n" "${head#${remote}/}" printf "%s\n" "${head#"${remote}"/}"
return 0 return 0
fi fi
@@ -94,7 +95,9 @@ gst()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -114,6 +117,14 @@ gst()
done done
local target="${1:-.}" local target="${1:-.}"
if ! command -v git >/dev/null 2>&1; then
disp E "git command not found."
return 1
fi
if ! git -C "$target" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
disp E "'$target' is not inside a git repository."
return 1
fi
git -C "$target" status --short --branch git -C "$target" status --short --branch
} }
export -f gst export -f gst
@@ -133,7 +144,9 @@ ggraph()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
local limit=30 local limit=30
while true; do while true; do
case "$1" in case "$1" in
@@ -184,7 +197,9 @@ gsync()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -239,8 +254,13 @@ gacp()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
local msg="" auto_add="$GIT_GACP_AUTO_ADD" readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
local msg="" auto_add=0
case "${GIT_GACP_AUTO_ADD,,}" in
1|true|yes|on) auto_add=1 ;;
esac
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -354,7 +374,9 @@ greset()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
local clean_ignored=0 local clean_ignored=0
while true; do while true; do
case "$1" in case "$1" in
@@ -410,16 +432,26 @@ greset()
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then 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')" stash_msg="greset:${branch}:$(date +'%Y-%m-%d %H:%M:%S')"
disp I "Stashing local changes as '$stash_msg'..." disp I "Stashing local changes as '$stash_msg'..."
local stash_before stash_after
stash_before=$(git rev-parse --quiet --verify refs/stash 2>/dev/null) || true
stash_out=$(git stash push -u -m "$stash_msg" 2>&1) || { stash_out=$(git stash push -u -m "$stash_msg" 2>&1) || {
disp E "Failed to stash local changes." disp E "Failed to stash local changes."
printf "%s\n" "$stash_out" printf "%s\n" "$stash_out"
return 1 return 1
} }
[[ $stash_out != "No local changes to save"* ]] && stash_created=1 stash_after=$(git rev-parse --quiet --verify refs/stash 2>/dev/null) || true
# A new stash means the top refs/stash sha changed (locale-independent).
[[ -n $stash_after && $stash_after != "$stash_before" ]] && stash_created=1
fi fi
# Only fetch when the derived remote is an actual configured remote; a bare
# tag or local-branch target has no remote to update from.
if git remote get-url "$remote" >/dev/null 2>&1; then
disp I "Fetching from $remote..." disp I "Fetching from $remote..."
git fetch --prune "$remote" || return 1 git fetch --prune "$remote" || return 1
else
disp I "Target '$target' is not a remote ref; skipping fetch."
fi
if ! git rev-parse --verify --quiet "$target" >/dev/null; then if ! git rev-parse --verify --quiet "$target" >/dev/null; then
disp E "Target '$target' does not exist." disp E "Target '$target' does not exist."
@@ -460,7 +492,9 @@ gwip()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -497,25 +531,46 @@ export -f gwip
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Delete merged local branches (except protected branches) # Delete merged local branches (except protected branches)
# Usage: gprune [main-branch] # 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() gprune()
{ {
local PARSED local PARSED deep=0
PARSED=$(getopt -o h --long help -n 'gprune' -- "$@") PARSED=$(getopt -o hD --long help,deep -n 'gprune' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after # shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"gprune --help\" to display usage." disp E "Invalid options, use \"gprune --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "gprune: Delete local branches already merged into main branch.\n" printf "gprune: Delete local branches already merged into main branch.\n\n"
printf "Usage: gprune [main-branch]\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 return 0
;; ;;
-D|--deep)
deep=1
shift
;;
--) --)
shift shift
break break
@@ -532,6 +587,7 @@ gprune()
local base="${1:-$(_git_default_branch "$GIT_DEFAULT_REMOTE")}" current deleted=0 local base="${1:-$(_git_default_branch "$GIT_DEFAULT_REMOTE")}" current deleted=0
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1 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..." disp I "Pruning branches merged into $base..."
while IFS= read -r b; do while IFS= read -r b; do
@@ -540,12 +596,39 @@ gprune()
[[ $b == "$base" ]] && continue [[ $b == "$base" ]] && continue
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue [[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
git branch -d "$b" >/dev/null 2>&1 && { git branch -d "$b" >/dev/null 2>&1 && {
printf "Deleted: %s\n" "$b" printf "Deleted (merged): %s\n" "$b"
((deleted++)) deleted=$(( deleted+1 ))
} }
done < <(git branch --merged "$base" | sed -E 's/^\*?\s*//') done < <(git branch --merged "$base" | sed -E 's/^\*?\s*//')
(( deleted == 0 )) && disp I "No merged branches to delete." # ── 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
local upstream sha
while IFS= read -r b; do
[[ -z $b ]] && continue
[[ $b == "$current" ]] && continue
[[ $b == "$base" ]] && continue
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
# Detect a gone upstream without relying on localized "gone]" text:
# resolve the branch's upstream, then check its remote-tracking ref.
upstream=$(git rev-parse --abbrev-ref "${b}@{upstream}" 2>/dev/null) || true
[[ -z "$upstream" ]] && continue # no tracking branch at all — skip
# If the remote-tracking ref still exists, the upstream is not gone.
git show-ref --verify --quiet "refs/remotes/$upstream" 2>/dev/null && continue
sha=$(git rev-parse --short "$b" 2>/dev/null)
git branch -D "$b" >/dev/null 2>&1 && {
printf "Deleted (gone upstream): %s (%s)\n" "$b" "$sha"
deleted=$(( deleted+1))
}
done < <(git for-each-ref --format='%(refname:short)' refs/heads/)
fi
(( deleted == 0 )) && disp I "No branches to delete."
} }
export -f gprune export -f gprune
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -564,7 +647,9 @@ groot()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
local do_go=0 local do_go=0
while true; do while true; do
case "$1" in case "$1" in
@@ -608,6 +693,4 @@ export -f groot
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf git
# EOF # EOF

View File

@@ -57,9 +57,12 @@ help()
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"
@@ -67,13 +70,14 @@ help()
printf "gacp\t\tAdd, commit and push changes (auto-pull if needed)\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 "ggraph\t\tDisplay decorated git history graph\n"
printf "gprune\t\tDelete local branches already merged into main branch\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 "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 "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 "gsync\t\tFetch and rebase current branch onto upstream\n"
printf "gst\t\tDisplay short git status and branch tracking info\n" printf "gst\t\tDisplay short git status and branch tracking info\n"
printf "gwip\t\tCreate a quick WIP checkpoint commit\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"
@@ -82,13 +86,15 @@ help()
printf "mdcat\t\tRender Markdown files in terminal with colors and code frames\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"
@@ -99,6 +105,7 @@ 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"

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf info
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Show profile version # Show profile version
# Usage: ver # Usage: ver
@@ -48,7 +50,9 @@ ver()
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -88,7 +92,9 @@ meteo()
disp E "Invalid options, use \"meteo --help\" to display usage." disp E "Invalid options, use \"meteo --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -127,6 +133,230 @@ 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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*)
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
@@ -140,7 +370,9 @@ showinfo()
disp E "Invalid options, use \"showinfo --help\" to display usage." disp E "Invalid options, use \"showinfo --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -179,21 +411,11 @@ 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
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf info
# EOF # EOF

View File

@@ -34,14 +34,53 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf lang
# ------------------------------------------------------------------------------
# check if a given locale is installed on the system.
# The comparison is tolerant to the various ways the codeset can be written
# across distributions (UTF-8, UTF8, utf8, utf-8, ISO-8859-1, iso88591, ...).
# On success the canonical name, exactly as the system spells it, is printed on
# stdout so callers can feed it verbatim to `export LANG=...` etc.
# Usage: canonical=$(locale_check <locale>) || handle-missing
locale_check() locale_check()
{ {
locale -a | grep -qx "$1" || { local requested="$1"
disp W "Locale '$1' is not installed on this system." [[ -z $requested ]] && { disp W "No locale given to check."; return 1; }
return 1
} # Split "lang_TERRITORY[.codeset][@modifier]" into base, codeset, modifier.
local req_body="$requested" req_cs="" req_mod=""
[[ $req_body == *@* ]] && { req_mod="${req_body##*@}"; req_body="${req_body%@*}"; }
[[ $req_body == *.* ]] && { req_cs="${req_body##*.}"; req_body="${req_body%.*}"; }
# Normalise the codeset: lowercase then strip every non-alphanumeric char so
# that UTF-8, UTF8, utf8 and utf-8 all compare equal (likewise ISO variants).
local req_cs_norm="${req_cs,,}"
req_cs_norm="${req_cs_norm//[^a-z0-9]/}"
local cand body cs mod cs_norm
while IFS= read -r cand; do
[[ -z $cand ]] && continue
body="$cand"; cs=""; mod=""
[[ $body == *@* ]] && { mod="${body##*@}"; body="${body%@*}"; }
[[ $body == *.* ]] && { cs="${body##*.}"; body="${body%.*}"; }
cs_norm="${cs,,}"
cs_norm="${cs_norm//[^a-z0-9]/}"
# Base language and modifier must match exactly.
[[ $body == "$req_body" && $mod == "$req_mod" ]] || continue
# A requested codeset must match (normalised); if none was requested,
# accept any codeset the system provides.
[[ -z $req_cs_norm || $cs_norm == "$req_cs_norm" ]] || continue
printf '%s\n' "$cand"
return 0 return 0
done < <(locale -a 2>/dev/null)
disp W "Locale '$requested' is not installed on this system."
return 1
} }
export -f locale_check
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -56,7 +95,9 @@ setlocale()
disp E "Invalid options, use \"setlocale --help\" to display usage." disp E "Invalid options, use \"setlocale --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -80,7 +121,9 @@ setlocale()
local loc=$1 local loc=$1
[[ -z $loc ]] && disp E "No locale specified." && return 1 [[ -z $loc ]] && disp E "No locale specified." && return 1
locale_check "$loc" || return 1 # Resolve to the exact spelling installed on this system, tolerant to the
# various codeset writings (UTF-8, UTF8, utf8, utf-8, ...).
loc=$(locale_check "$loc") || return 1
export LANG=$loc export LANG=$loc
export LC_MESSAGES=$loc export LC_MESSAGES=$loc
@@ -103,6 +146,7 @@ setc()
{ {
# Locale definitions # Locale definitions
export LC_ALL=C export LC_ALL=C
export LANG=C
disp I "Locale changed to standard C (POSIX)." disp I "Locale changed to standard C (POSIX)."
} }
export -f setc export -f setc
@@ -172,11 +216,10 @@ build_locale_shortcuts()
unset cfg item alias loc fname locale_items unset cfg item alias loc fname locale_items
} }
export -f build_locale_shortcuts
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Dynamically build locale functions based on SET_LOCALE configuration
load_conf lang
build_locale_shortcuts build_locale_shortcuts
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -34,22 +34,33 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "net"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Download a resource using curl, wget, or fetch. # Download a resource using curl, wget, or fetch.
# Usage: dwl [-t <seconds>] <url> [output_file] # Usage: dwl [-t <seconds>] [-r|--resume] <url> [output_file]
dwl() dwl()
{ {
local timeout="" local timeout=""
local resume=0
case "${DWL_DEFAULT_RESUME,,}" in
1|true|yes|on)
resume=1
;;
esac
# Parse leading options before the URL. # Parse leading options before the URL.
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--help|-h) --help|-h)
echo "Usage: dwl [-t <seconds>|--timeout <seconds>] <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 " -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
@@ -62,6 +73,14 @@ dwl()
timeout="$2" timeout="$2"
shift 2 shift 2
;; ;;
-r|--resume)
resume=1
shift
;;
--no-resume)
resume=0
shift
;;
--) --)
shift shift
break break
@@ -100,8 +119,12 @@ dwl()
local args=(-sL) local args=(-sL)
[[ -n "$timeout" ]] && args+=(--max-time "$timeout" --connect-timeout "$timeout") [[ -n "$timeout" ]] && args+=(--max-time "$timeout" --connect-timeout "$timeout")
if [[ -z "$output" ]]; then if [[ -z "$output" ]]; then
if (( resume == 1 )); then
echo "Warning: --resume requires an output file; ignoring resume mode for stdout." >&2
fi
curl "${args[@]}" "$url" curl "${args[@]}" "$url"
else else
(( resume == 1 )) && args+=(-C -)
curl "${args[@]}" -o "$output" "$url" curl "${args[@]}" -o "$output" "$url"
fi fi
} }
@@ -110,6 +133,7 @@ dwl()
{ {
local args=(-q) local args=(-q)
[[ -n "$timeout" ]] && args+=(--timeout="$timeout") [[ -n "$timeout" ]] && args+=(--timeout="$timeout")
(( resume == 1 )) && args+=(-c)
if [[ -z "$output" ]]; then if [[ -z "$output" ]]; then
wget "${args[@]}" -O- "$url" wget "${args[@]}" -O- "$url"
else else
@@ -121,6 +145,9 @@ dwl()
{ {
local args=() local args=()
[[ -n "$timeout" ]] && args+=(-T "$timeout") [[ -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 "${args[@]}" -o - "$url" fetch "${args[@]}" -o - "$url"
else else
@@ -179,20 +206,21 @@ isipv4()
for oct in "${ip_arr[@]}"; do for oct in "${ip_arr[@]}"; do
# Reject leading plus/minus or empty entries # Reject leading plus/minus or empty entries
if [[ -z $oct || $oct =~ [^0-9] ]]; then if [[ -z $oct || $oct =~ [^0-9] ]]; then
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4." [[ -t 1 ]] && disp E "The given parameter is NOT a valid IPv4."
return 1 return 1
fi fi
if (( oct > 255 )); then # 10# forces base-10 so leading-zero octets (08, 09) are not read as octal
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4." if (( 10#$oct > 255 )); then
[[ -t 1 ]] && disp E "The given parameter is NOT a valid IPv4."
return 1 return 1
fi fi
done done
[[ -t 1 ]] && disp "The given IPv4 is valid." [[ -t 1 ]] && disp I "The given IPv4 is valid."
return 0 return 0
fi fi
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4." [[ -t 1 ]] && disp E "The given parameter is NOT a valid IPv4."
return 1 return 1
} }
export -f isipv4 export -f isipv4
@@ -202,19 +230,94 @@ export -f isipv4
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Determine if parameter is a valid IPv6 address # Determine if parameter is a valid IPv6 address
# Usage: isipv6 <ip_address> # Usage: isipv6 <ip_address>
# Accepts full and "::"-compressed forms, an embedded IPv4 suffix
# (e.g. ::ffff:192.0.2.1) and an optional RFC 4007 zone id (e.g. fe80::1%eth0).
isipv6() isipv6()
{ {
local ip="$1" local ip="$1"
local regex='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$' local valid=0
if [[ $ip =~ $regex ]]; then
if [[ -t 1 ]]; then # A single-pass validation; any failing check breaks out with valid=0.
disp "The given IPv6 is valid." while true; do
[[ -z $ip ]] && break
# Strip an optional zone identifier (fe80::1%eth0); reject empty/multiple.
if [[ $ip == *%* ]]; then
[[ $ip == *%*%* ]] && break
[[ -z ${ip#*%} ]] && break
ip="${ip%%[%]*}"
fi fi
# Reject ':::' and more than one '::' compression.
[[ $ip == *:::* ]] && break
local compressed=0
if [[ $ip == *::* ]]; then
[[ ${ip#*::} == *::* ]] && break
compressed=1
fi
# A lone leading/trailing ':' that is not part of a '::' is invalid.
[[ $ip == :* && $ip != ::* ]] && break
[[ $ip == *: && $ip != *:: ]] && break
# An embedded IPv4 is allowed only as the trailing 32 bits. Validate it,
# then replace it with two zero hextets so the rest is a clean hextet list.
if [[ $ip == *.* ]]; then
local v4="${ip##*:}"
[[ $v4 == *.*.*.* ]] || break
local -a oct=()
IFS='.' read -ra oct <<< "$v4"
[[ ${#oct[@]} -eq 4 ]] || break
local o v4ok=1
for o in "${oct[@]}"; do
if [[ ! $o =~ ^[0-9]{1,3}$ ]] || (( 10#$o > 255 )); then
v4ok=0
break
fi
done
(( v4ok )) || break
ip="${ip%"$v4"}0:0"
fi
# Count the hextets on each side of an optional '::'.
local left right seg part total=0 ok=1
if (( compressed )); then
left="${ip%%::*}"
right="${ip#*::}"
else
left="$ip"
right=""
fi
for seg in "$left" "$right"; do
[[ -z $seg ]] && continue
local -a parts=()
IFS=':' read -ra parts <<< "$seg"
for part in "${parts[@]}"; do
[[ $part =~ ^[0-9a-fA-F]{1,4}$ ]] || { ok=0; break; }
(( total++ ))
done
(( ok )) || break
done
(( ok )) || break
# Without '::' there must be exactly 8 hextets; with it, fewer than 8
# (the '::' expands to at least one zero hextet).
if (( compressed )); then
(( total <= 7 )) || break
else
(( total == 8 )) || break
fi
valid=1
break
done
if (( valid )); then
[[ -t 1 ]] && disp I "The given IPv6 is valid."
return 0 return 0
fi fi
if [[ -t 1 ]]; then [[ -t 1 ]] && disp E "The given parameter is not a valid IPv6."
disp "The given parameter is not a valid IPv6."
fi
return 1 return 1
} }
export -f isipv6 export -f isipv6
@@ -229,12 +332,13 @@ urlencode()
local LANG=C local LANG=C
local str="$*" local str="$*"
local length="${#str}" local length="${#str}"
local i
for (( i = 0; i < length; i++ )); do for (( i = 0; i < length; i++ )); do
local c="${str:i:1}" local c="${str:i:1}"
case "$c" in case "$c" in
[a-zA-Z0-9.~_-]) printf '%s' "$c" ;; [a-zA-Z0-9.~_-]) printf '%s' "$c" ;;
' ') printf '+' ;; ' ') printf '+' ;;
*) printf '%%%02X' "'$c" #| cut -d' ' -f2 ;; *) printf '%%%02X' "'$c" ;;
esac esac
done done
} }
@@ -345,9 +449,4 @@ export -f myextip
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# EOF
load_conf "net"
# EOF # EOF

View File

@@ -34,6 +34,9 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "packages"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Detect the active package manager of the current distribution. # Detect the active package manager of the current distribution.
# Detection is based on /etc/os-release (ID / ID_LIKE), then falls back to # Detection is based on /etc/os-release (ID / ID_LIKE), then falls back to
@@ -130,7 +133,9 @@ pkgs()
disp E "Invalid options, use \"pkgs --help\" to display usage." disp E "Invalid options, use \"pkgs --help\" to display usage."
return 1 return 1
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -194,6 +199,84 @@ export -f pkgs
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "packages" # ------------------------------------------------------------------------------
# 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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
# ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -34,18 +34,46 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "processes"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Search processes matching the given string # Search processes matching the given string
# 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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 +109,38 @@ 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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 +159,38 @@ 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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 +208,41 @@ 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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 +293,121 @@ 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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,38 +420,115 @@ 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "processes"
# EOF # EOF

View File

@@ -34,6 +34,7 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "prompt"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Parse a prompt theme file safely — it is NEVER sourced or executed. # Parse a prompt theme file safely — it is NEVER sourced or executed.
@@ -78,6 +79,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 +170,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 +181,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 +276,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 +410,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 +523,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 +553,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\] "
@@ -353,8 +600,6 @@ _pt_theme="${CONF_prompt[PROMPT_THEME]:-}"
_pt_dir="${CONF_prompt[PROMPT_THEME_DIR]:-}" _pt_dir="${CONF_prompt[PROMPT_THEME_DIR]:-}"
[[ -n "$_pt_theme" ]] && load_theme "$_pt_theme" ${_pt_dir:+"$_pt_dir"} [[ -n "$_pt_theme" ]] && load_theme "$_pt_theme" ${_pt_dir:+"$_pt_dir"}
unset _pt_theme _pt_dir unset _pt_theme _pt_dir
load_conf "prompt"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "pwd"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# genpwd : generate a password with different criteria # genpwd : generate a password with different criteria
# Usage: genpwd [options] [--extracars=<cars>] [--length=<n>] [nb_passwd] # Usage: genpwd [options] [--extracars=<cars>] [--length=<n>] [nb_passwd]
@@ -67,7 +69,9 @@ genpwd()
-n 'genpwd' -- "$@") -n 'genpwd' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after # shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then return 1; fi if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -478,6 +482,4 @@ export -f pwdscore
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "pwd"
# EOF # EOF

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "rain"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Generic rain-like engine and presets # Generic rain-like engine and presets
@@ -42,6 +44,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 +87,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 +141,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 +271,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 +351,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 +377,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 +402,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 +434,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 +465,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 +474,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 +494,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 +544,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,12 +575,136 @@ 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
load_conf "rain" 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
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
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
# ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -34,6 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "ssh"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Remove host entries (name and IP) from ~/.ssh/known_hosts for the active user # Remove host entries (name and IP) from ~/.ssh/known_hosts for the active user
# Usage: rmhost <hostname|ip> [hostname2|ip2 [...]] # Usage: rmhost <hostname|ip> [hostname2|ip2 [...]]
@@ -46,16 +48,21 @@ rmhost()
PARSED=$(getopt -o ha --long help,all-users -n 'rmhost' -- "$@") PARSED=$(getopt -o ha --long help,all-users -n 'rmhost' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after # shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then return 1; fi if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
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 +113,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
@@ -202,6 +253,4 @@ export -f ssr
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "ssh"
# EOF # EOF

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -59,7 +59,9 @@ check_updates()
disp E "Invalid options, use \"check_updates --help\" to display usage." disp E "Invalid options, use \"check_updates --help\" to display usage."
return 2 return 2
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
@@ -139,7 +141,9 @@ profile_upgrade()
disp E "Invalid options, use \"profile_upgrade --help\" to display usage." disp E "Invalid options, use \"profile_upgrade --help\" to display usage."
return 2 return 2
fi fi
eval set -- "$PARSED" local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)

View File

@@ -73,6 +73,29 @@ _profile_install_in_file()
return 0 return 0
} }
# Detect whether $rc_file sources the file whose basename is $target_name.
# Returns 0 (true) if a sourcing statement referencing $target_name is found,
# ignoring commented-out lines.
_profile_file_sources()
{
local rc_file="$1"
local target_name="$2"
local line
[[ -f "$rc_file" ]] || return 1
while IFS= read -r line || [[ -n "$line" ]]; do
# Strip comments so commented-out source lines are not matched.
line="${line%%#*}"
# Keep only lines whose first word is a source command (`source` or `.`).
[[ "$line" =~ ^[[:space:]]*(source|\.)[[:space:]] ]] || continue
# Match if the sourcing statement references the target file.
[[ "$line" == *"$target_name"* ]] && return 0
done < "$rc_file"
return 1
}
_profile_install() _profile_install()
{ {
local install_bashrc=0 local install_bashrc=0
@@ -108,6 +131,19 @@ _profile_install()
install_profile=1 install_profile=1
fi fi
# Avoid double loading when both targets are selected and one already
# sources the other: only configure the called file so profile.sh is
# loaded exactly once.
if (( install_bashrc == 1 && install_profile == 1 )); then
if _profile_file_sources "$HOME/.bashrc" ".profile"; then
printf "[ Info ] ~/.bashrc sources ~/.profile; configuring only ~/.profile to avoid double loading.\n"
install_bashrc=0
elif _profile_file_sources "$HOME/.profile" ".bashrc"; then
printf "[ Info ] ~/.profile sources ~/.bashrc; configuring only ~/.bashrc to avoid double loading.\n"
install_profile=0
fi
fi
script_dir=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")") script_dir=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")")
source_line="source \"$script_dir/profile.sh\"" source_line="source \"$script_dir/profile.sh\""
@@ -244,8 +280,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#\"}"
@@ -375,13 +414,13 @@ if [[ $INTERACTIVE ]]; then
# Load custom bash completions # Load custom bash completions
shopt -s nullglob shopt -s nullglob
for _compl in "$MYPATH/profile.d/bash-completion/"*.sh; do for _compl in "$MYPATH/profile.d/bash-completion/"*.sh; do
# shellcheck disable=SC1090 # Dynamic sourcing of completion scripts
[[ -f "$_compl" && -r "$_compl" ]] && . "$_compl" [[ -f "$_compl" && -r "$_compl" ]] && . "$_compl"
done done
unset _compl unset _compl
shopt -u nullglob shopt -u nullglob
# For compiling (as we often compile with LFS/0linux...) # Aliases
#Aliases
load_alias aliases load_alias aliases
# Define PS1 # Define PS1
@@ -406,7 +445,7 @@ if [[ $INTERACTIVE ]]; then
fi fi
# Cleanup # Cleanup
unset -f _profile_is_sourced _profile_finish _profile_install_in_file _profile_install unset -f _profile_is_sourced _profile_finish _profile_install_in_file _profile_file_sources _profile_install
unset -f parse_conf load_alias load_conf unset -f parse_conf load_alias load_conf
unset -f pathremove pathprepend pathappend unset -f pathremove pathprepend pathappend