33 Commits

Author SHA1 Message Date
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
fatalerrors
28e4c112af added experimental mdcat 2026-05-20 13:55:28 +02:00
fatalerrors
9ec52aa49f update doc 2026-05-19 17:39:15 +02:00
fatalerrors
1b28e90c62 update doc 2026-05-19 17:20:12 +02:00
fatalerrors
1e31712b60 allow auto with taz 2026-05-19 17:19:50 +02:00
fatalerrors
5faae67d11 better cleanup 2026-05-19 16:57:27 +02:00
fatalerrors
ee72ede116 update doc and release 4.1.0 2026-05-07 15:03:23 +02:00
fatalerrors
f5244ac062 allow the profile to self install 2026-05-07 11:54:06 +02:00
fatalerrors
9a089112c3 make help better 2026-05-07 11:53:28 +02:00
fatalerrors
e64a857a43 fix too long long on non functionnal networt (and improve dwl) 2026-05-07 11:52:53 +02:00
fatalerrors
ddd7d4193a version bump 2026-05-06 18:28:25 +02:00
fatalerrors
83a1c8ce48 removed rogue french comments 2026-05-06 18:28:06 +02:00
fatalerrors
b29fa3b30c make get_pkgmgr public 2026-05-06 18:27:19 +02:00
fatalerrors
cd0bcfd214 fix completion 2026-05-06 18:24:38 +02:00
fatalerrors
a91c41871a fix completion 2026-05-06 18:20:30 +02:00
fatalerrors
9698f0e506 fix completion 2026-05-06 18:12:14 +02:00
fatalerrors
9e22f007b9 make disp smarter 2026-05-06 18:05:51 +02:00
fatalerrors
d472fb61aa load completion 2026-05-06 15:35:05 +02:00
fatalerrors
9108ee8266 add primary completion for git 2026-05-06 15:34:19 +02:00
fatalerrors
a7f7452b2b add auto to gacp 2026-05-05 16:25:13 +02:00
fatalerrors
bc67399ebc Merge branch '4.x' 2026-04-23 17:59:07 +02:00
fatalerrors
02b037d0fc proper changelog, removed the old history.txt file 2026-04-23 17:57:01 +02:00
fatalerrors
e567957ea0 update doc 2026-04-23 17:36:25 +02:00
fatalerrors
67bdd3e863 add gacp 2026-04-23 17:31:33 +02:00
fatalerrors
fa573bce8f add git helpers 2026-04-23 17:18:01 +02:00
fatalerrors
241d53ebc4 Merge branch '4.x' 2026-04-22 17:58:21 +02:00
fatalerrors
1225230a07 4.0 released 2026-04-22 17:56:08 +02:00
fatalerrors
9c43190202 bugfix: removed last french words and frenglish 2026-04-22 17:54:42 +02:00
31 changed files with 2430 additions and 265 deletions

100
README.md
View File

@@ -10,12 +10,28 @@ current shell is not bash.
## 2. Getting started ## 2. Getting started
Download and extract (or use git clone) the profile archive into your home Download and extract (or use git clone) the profile archive into your home
directory. You will have to modify your `~/.bashrc` and/or `~/.profile` file to directory.
add at the end (preferably):
The profile is designed to be **sourced**, not executed directly.
Manual setup:
```bash ```bash
source <installpath>/profile/profile.sh source <installpath>/profile/profile.sh
``` ```
Automatic setup (recommended):
```bash
bash <installpath>/profile/profile.sh --install
```
`--install` appends the required `source` line to both `~/.bashrc` and
`~/.profile` by default. You can target one file only:
```bash
bash <installpath>/profile/profile.sh --install --bashrc
bash <installpath>/profile/profile.sh --install --profile
```
You may also set the `PROFILE_PATH` environment variable before sourcing if you You may also set the `PROFILE_PATH` environment variable before sourcing if you
want to override the automatic path detection: want to override the automatic path detection:
```bash ```bash
@@ -26,7 +42,22 @@ source /opt/profile/profile.sh
It's not recommended to load that profile in `/etc/profile` as users' `.bashrc` It's not recommended to load that profile in `/etc/profile` as users' `.bashrc`
files might interfere with some aliases and functions defined in profile. files might interfere with some aliases and functions defined in profile.
### 2.1. Initial configuration ### 2.1. Interactive vs non-interactive shells
`profile.sh` detects whether the current shell is interactive.
In interactive shells (typical terminal sessions), profile enables
interactive-only features such as:
- aliases from `[aliases]`
- bash completion scripts from `profile.d/bash-completion/`
- prompt initialization (`PROMPT_COMMAND` and timer hook)
- welcome display (`showinfo`) and startup update check (`check_updates -q`)
In non-interactive shells (typical script execution), those features are
intentionally skipped to avoid side effects and startup noise. Public functions
remain available after sourcing, so scripts can still call profile helpers.
### 2.2. Initial configuration
Copy the example configuration file and customise it to your needs: Copy the example configuration file and customise it to your needs:
```bash ```bash
cp <installpath>/profile/doc/profile.conf.example <installpath>/profile/profile.conf cp <installpath>/profile/doc/profile.conf.example <installpath>/profile/profile.conf
@@ -49,23 +80,32 @@ A bar-style prompt showing current time, execution time of the last command
| Function | Module | Description | | Function | Module | Description |
|---|---|---| |---|---|---|
| `busy` | fun | Monitor /dev/urandom for a hex pattern — look busy | | `busy` | fun | Monitor /dev/urandom for a hex pattern — look busy |
| `check_updates` | updates | Check whether a newer profile version is available online | | `check_updates` | updates | Check whether a newer profile version is available online; when called with `-q` at startup a 3-second network timeout is applied so a slow or absent network never delays the prompt |
| `clean` | filefct | Erase backup files in given directories, optionally recursive | | `clean` | filefct | Erase backup files in given directories, optionally recursive |
| `disp` | disp | Display formatted info / warning / error / debug messages | | `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 | | `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 |
| `file_stats` | filefct | Display file size statistics for a path | | `file_stats` | filefct | Display file size statistics for a path |
| `findbig` | filefct | Find the biggest files in the given or current directory | | `findbig` | filefct | Find the biggest files in the given or current directory |
| `finddead` | filefct | Find dead symbolic links in the given or current directory | | `finddead` | filefct | Find dead symbolic links in the given or current directory |
| `findzero` | filefct | Find empty files in the given or current directory | | `findzero` | filefct | Find empty files in the given or current directory |
| `gacp` | git | Add, commit and push changes; auto-pulls with rebase first if needed |
| `genpwd` | pwd | Generate one or more random secure passwords with configurable constraints | | `genpwd` | pwd | Generate one or more random secure passwords with configurable constraints |
| `ggraph` | git | Display a decorated git history graph |
| `gpid` | processes | Give the list of PIDs matching the given process name(s) | | `gpid` | processes | Give the list of PIDs matching the given process name(s) |
| `help` | help | Display the list of available functions and basic usage | | `gprune` | git | Delete local branches already merged into the main branch |
| `greset` | git | Reset the current branch to upstream, stashing local changes first |
| `groot` | git | Display the repository root path, or change directory to it with `-g` |
| `gsync` | git | Fetch and rebase the current branch onto its upstream |
| `gst` | git | Display compact git status with branch tracking information |
| `gwip` | git | Create a quick WIP checkpoint commit |
| `help` | help | Display the list of available functions and basic usage; `help <command>` delegates to `<command> --help` |
| `isipv4` | net | Tell if the given parameter is a valid IPv4 address | | `isipv4` | net | Tell if the given parameter is a valid IPv4 address |
| `isipv6` | net | Tell if the given parameter is a valid IPv6 address | | `isipv6` | net | Tell if the given parameter is a valid IPv6 address |
| `ku` | processes | Kill all processes owned by the given user name or ID | | `ku` | processes | Kill all processes owned by the given user name or ID |
| `matrix` | rain | Console screensaver with Matrix-style digital rain (binary, kana, ascii charset) | | `matrix` | rain | Console screensaver with Matrix-style digital rain (binary, kana, ascii charset) |
| `mcd` | filefct | Create a directory and immediately move into it | | `mcd` | filefct | Create a directory and immediately move into it |
| `mdcat` | disp | Render Markdown files in terminal with colors, inline formatting, and framed code blocks |
| `meteo` | info | Display weather forecast for the configured or given city | | `meteo` | info | Display weather forecast for the configured or given city |
| `myextip` | net | Get information about your public IP address | | `myextip` | net | Get information about your public IP address |
| `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) | | `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) |
@@ -91,6 +131,13 @@ A bar-style prompt showing current time, execution time of the last command
Locale shortcut functions (`setfr`, `setus`, etc.) are dynamically generated at Locale shortcut functions (`setfr`, `setus`, etc.) are dynamically generated at
startup from the `SET_LOCALE` configuration key (see section 4). startup from the `SET_LOCALE` configuration key (see section 4).
### 3.3. Bash completion
profile loads all `*.sh` files found under `profile.d/bash-completion/`
automatically in interactive sessions. This directory is the right place to add
any custom completion definitions. profile already ships completions for its git
helper functions there (`git-completion.sh`).
## 4. Configuration ## 4. Configuration
profile uses an INI-style configuration file (`profile.conf`) located in the profile uses an INI-style configuration file (`profile.conf`) located in the
same directory as `profile.sh`. Sections are declared with `[section_name]` and same directory as `profile.sh`. Sections are declared with `[section_name]` and
@@ -101,7 +148,7 @@ apply when unset.
`profile.conf` is listed in `.gitignore` so personal values (API keys, cities, `profile.conf` is listed in `.gitignore` so personal values (API keys, cities,
compiler flags, …) are never accidentally staged. Start from the annotated compiler flags, …) are never accidentally staged. Start from the annotated
template at `doc/profile.conf.example` (see [section 2.1](#21-initial-configuration)). template at `doc/profile.conf.example` (see [section 2.2](#22-initial-configuration)).
### 4.1. Core sections ### 4.1. Core sections
@@ -121,7 +168,7 @@ change the default without having to pass flags every time.
| Key | Default | Description | | Key | Default | Description |
|---|---|---| |---|---|---|
| `TAZ_DEFAULT_FORMAT` | `tar.gz` | Archive format for `taz` (`tar.gz`, `tar.bz2`, `tar.xz`, `zip`, …) | | `TAZ_DEFAULT_FORMAT` | `tar.gz` | Archive format for `taz` (`tar.gz`, `tar.bz2`, `tar.xz`, `zip`, …) |
| `TAZ_DEFAULT_THREADS` | `0` | Compression threads (0 = auto-detect) | | `TAZ_DEFAULT_THREADS` | `auto` | Compression threads (`auto` = runtime CPU count, or explicit positive integer) |
| `TAZ_DEFAULT_LEVEL` | `6` | Compression level (19) | | `TAZ_DEFAULT_LEVEL` | `6` | Compression level (19) |
| `UTAZ_DEFAULT_DELETE` | `0` | Set to `1` to delete the source archive after extraction | | `UTAZ_DEFAULT_DELETE` | `0` | Set to `1` to delete the source archive after extraction |
| `UTAZ_DEFAULT_DIR_MODE` | `0` | Set to `1` to always extract into a subdirectory | | `UTAZ_DEFAULT_DIR_MODE` | `0` | Set to `1` to always extract into a subdirectory |
@@ -141,8 +188,10 @@ change the default without having to pass flags every time.
|---|---|---| |---|---|---|
| `RAIN_DEFAULT_SPEED` | `0.1` | Falling speed for `rain` | | `RAIN_DEFAULT_SPEED` | `0.1` | Falling speed for `rain` |
| `RAIN_DEFAULT_COLOR` | `Green` | Colour for `rain` | | `RAIN_DEFAULT_COLOR` | `Green` | Colour for `rain` |
| `RAIN_DEFAULT_DENSITY` | dynamic | Maximum number of simultaneous falling elements for `rain` |
| `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]`**
@@ -171,6 +220,15 @@ change the default without having to pass flags every time.
| `BUSY_DEFAULT_PATTERN` | `[0-9a-f]` | Hex pattern matched by `busy` | | `BUSY_DEFAULT_PATTERN` | `[0-9a-f]` | Hex pattern matched by `busy` |
| `BUSY_DEFAULT_DELAY` | `0.1` | Polling delay (seconds) for `busy` | | `BUSY_DEFAULT_DELAY` | `0.1` | Polling delay (seconds) for `busy` |
**`[git]`**
| Key | Default | Description |
|---|---|---|
| `GIT_MAIN_BRANCH` | `main` | Fallback main branch name used when remote HEAD cannot be detected |
| `GIT_DEFAULT_REMOTE` | `origin` | Default remote used by git helper functions |
| `GIT_WIP_PREFIX` | `wip` | Prefix used by `gwip` when generating automatic checkpoint messages |
| `GIT_GACP_AUTO_ADD` | `1` | Set to `1` to make `gacp` automatically add all modified files when no explicit file list is given; set to `0` to require explicit paths or the `-a` flag |
**`[info]`** **`[info]`**
| Key | Default | Description | | Key | Default | Description |
@@ -252,7 +310,7 @@ PROMPT_COLOR_USER_FG = $ICyan
PROMPT_COLOR_DIR_FG = $IYellow PROMPT_COLOR_DIR_FG = $IYellow
``` ```
The eleven available `PROMPT_COLOR_*` keys are: The twelve available `PROMPT_COLOR_*` keys are:
| Key | Role | | Key | Role |
|---|---| |---|---|
@@ -263,6 +321,28 @@ 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_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
- `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
- both, separated by `|`, when both are available
**Writing a custom theme file:** **Writing a custom theme file:**

View File

@@ -7,6 +7,59 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
--- ---
## [4.1.0] — 2026-05-07
### Added
- `profile.sh --install` command to automatically configure profile loading in
shell startup files.
- `--install --bashrc` and `--install --profile` target selectors for
single-file installation.
- **`git.sh`** — entirely new module providing git workflow helpers: `gst`,
`ggraph`, `gsync`, `gacp`, `greset`, `gwip`, `gprune`, `groot`.
- Dedicated git helper completions under `profile.d/bash-completion/`, loaded
automatically in interactive sessions from `profile.d/bash-completion/*.sh`.
### Changed
- `disp` now wraps long messages on terminal width, avoids mid-word splits, and
aligns continuation lines with the message body after the prefix.
- `help` now supports `help <command>` and delegates to `<command> --help`.
- `taz` now supports `-p auto` / `--parallel=auto` to automatically use the
runtime CPU count. This mode is now the default via
`TAZ_DEFAULT_THREADS=auto`.
- `taz` keeps backward compatibility with legacy `TAZ_DEFAULT_THREADS=0`
values by interpreting `0` as `auto`.
### Fixed
- Startup responsiveness improved: `check_updates -q` now uses a short network
timeout so unavailable/slow networks no longer delay prompt readiness.
- `dwl` gained timeout support (`-t` / `--timeout`) and is now used by quiet
startup update checks to enforce fast failure.
- `profile.sh` now detects direct execution and warns that it is designed to be
sourced.
## [4.0.0] — 2026-04-23
### Added
- New `profile.conf` reference template at `doc/profile.conf.example`.
- Dynamic locale shortcuts generated from `SET_LOCALE` and startup default
language selection through `DEFAULT_LANG`.
- Prompt theming system with bundled themes (`default`, `dark`, `light`,
`solarized`, `solarized-light`, `monokai`, `monochrome`, `abyss`, `plasma`,
`adwaita`) and per-key prompt color overrides.
- Module defaults exposed as configuration keys in `profile.conf`.
### Changed
- `utaz` now supports a wider range of archive formats.
- Prompt and theme rendering improved, including better 24-bit color support.
- Overall code quality and maintainability improved across modules.
### Documentation
- README updated with full function reference and configuration tables.
- New and expanded docs in `doc/` (`CONTRIBUTING.md`, `FAQ.md`, `todo.md`).
- Historical releases imported from `history.txt` into this changelog.
---
## [3.99.2-4_rc_2] — 2026-04-21 ## [3.99.2-4_rc_2] — 2026-04-21
### Fixed ### Fixed
@@ -78,12 +131,6 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
--- ---
---
> **Note:** Versions prior to `3.95.x-4_beta` did not maintain a formal
> changelog. The full history of earlier changes is available through the git
> log (`git log --oneline`).
## [3.95.3-4_beta_3] — 2024 ## [3.95.3-4_beta_3] — 2024
### Added ### Added
@@ -95,3 +142,153 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
- `genpwd` / `pwdscore` password tools. - `genpwd` / `pwdscore` password tools.
- `matrix` / `rain` screensavers. - `matrix` / `rain` screensavers.
- `profile_upgrade` with git and archive download support. - `profile_upgrade` with git and archive download support.
---
> **Note:** The section below was imported from `history.txt` to preserve
> pre-`3.95.x-4_beta` release notes.
## Legacy releases (imported from `history.txt`)
### [3.6.1] — 2026-03-05
- Fix typo in `compress.sh`.
### [3.6.0] — 2026-03-05
- Improved `utaz` with broader multi-format support.
- Introduced `ppu` and `ppn`.
- Improved update system.
### [3.5.0] — 2026-03-04
- `rain` now has configurable speed and color.
- `showinfo` adapted to `fastfetch` (in addition to `neofetch`).
### [3.3.1] — 2022-02-24
- Fixed version detection.
- Added `busy`.
- Fixed use of library functions before loading.
### [3.3.0] — 2022-11-28
- Initial version update support.
- Changed versioning code.
- Added installation path detection.
### [3.2.3] — 2022-11-28
- Improved README.
### [3.2.2] — 2022-11-21
- Fixed `taz` compression level parsing.
- Fixed typo in `dpkgs`.
### [3.2.1] — 2022-11-20
- Fixed several messages.
- Made `dpkgs` RPM-aware (initial support).
- Removed version history from main script and reverted declaration order.
- Added required license information in all files.
- Completed `LICENSE` file.
### [3.2.0] — 2022-11-18
- Created `disp` command and integrated it across the codebase.
### [3.1.1] — 2022-11-10
- `genpwd`: added feasibility check for requested password constraints.
### [3.1.0] — 2022-11-08
- Added password generator.
### [3.0.1] — 2022-11-07
- Added concatenation option to `rmspc`.
- Added `ku`.
- Improved error handling in `meteo`.
### [3.0.0] — 2022-08-27
- Split code into several files/modules.
- Added `rain` screensaver.
### [2.8.2] — 2022-07-29
- Added warning for non-bash users.
### [2.8.1] — 2022-07-19
- Cleanup, fixes and optimizations.
### [2.8.0] — 2022-06-24
- Added `backtrace`, `error` and `settrace`.
- Bugfixes in `showinfo`.
### [2.7.1] — 2022-06-22
- Minor corrections.
- Added `help` command.
### [2.7.0] — 2022-06-21
- Added `isipv4` and `isipv6`, integrated into `rmhost`.
- Removed broken Konsole save/restore support.
### [2.6.3] — 2021-10-18
- Changed PS1 to status-bar style.
- Minor improvements.
### [2.6.2] — 2021-02-26
- Bugfix in `taz` for directories with trailing slash.
### [2.6.1] — 2020-12-25
- Added checks in `rmhost`.
- Improved `rmspc`.
- Created `expandlist`.
### [2.6.0] — 2020-10-24
- Added Konsole session save/restore.
### [2.5.3] — 2020-09-11
- Added aliases, improved code consistency and fixed typos.
- Improved `utaz`, removed `showdiskmap`, removed remaining French text.
- Added license information for future publication.
### [2.5.2] — 2020-03-06
- Sorted and improved aliases.
### [2.5.1] — 2020-03-05
- Language consistency fixes.
- Added `pigz` support in `taz`.
### [2.5.0] — 2020-03-03
- Added `taz` and `rmspc`.
- Renamed `auzip` to `utaz` and improved it.
### [2.4.0] — 2020-03-02
- Added `auzip`.
### [2.3.2] — 2020-01-31
- `figlet`: changed default font to `ansi_shadow`.
### [2.3.1] — 2020-01-16
- Bugfix: non-interactive shells were blocked by some functions.
### [2.3.0] — 2020-01-08
- Added `figlet` and `neofetch` as MOTD replacement.
### [2.2.0] — 2019-12-16
- Added `showinfo`.
- First implementation of `showdiskmap`.
### [2.1.2] — 2019-09-24
- Bugfix in profile version display.
### [2.1.1] — 2019-09-23
- Bugfix in `dpkgs`.
### [2.1.0] — 2018-09-16
- Added `rmhost`, `setc`, `setfr`.
- Improved locale management.
### [2.0.1] — 2017-02-04
- `clean` improvements (`--shell`).
### [2.0.0] — 2015-10-24
- Added advanced functions (`clean`, `ssr`, etc.).
### [1.0.0] — 2013-02-16
- Initial version.
### [Initial fork]
Forked default Bash profile from Beyond Linux From Scratch by
* James Robertson <jameswrobertson@earthlink.net>
* Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>

View File

@@ -4,6 +4,33 @@
## Installation & loading ## Installation & loading
**Q: How do I install profile automatically into my shell startup files?**
Run the installer directly (no need to source first):
```bash
bash <installpath>/profile/profile.sh --install
```
This appends the required `source` line to both `~/.bashrc` and `~/.profile`.
To target only one file:
```bash
bash <installpath>/profile/profile.sh --install --bashrc
bash <installpath>/profile/profile.sh --install --profile
```
The operation is idempotent — running it again will not add a duplicate line.
---
**Q: I ran `profile.sh` directly and got a warning about sourcing.**
profile.sh is designed to be *sourced*, not executed:
```bash
source <installpath>/profile/profile.sh
```
The only exception is `--install`, which must be passed to a direct execution
(`bash profile.sh --install`) to set up the sourcing line automatically.
---
**Q: profile refuses to load and prints "This profile requires Bash 4.3 or higher."** **Q: profile refuses to load and prints "This profile requires Bash 4.3 or higher."**
Your system's default shell is an older Bash (common on macOS, which ships Your system's default shell is an older Bash (common on macOS, which ships
@@ -25,6 +52,33 @@ scripts start with `#!/usr/bin/env bash`.
--- ---
**Q: Can I use profile functions in scripts?**
Yes. The supported way is to source `profile.sh` from a Bash script:
```bash
#!/usr/bin/env bash
source /path/to/profile/profile.sh
taz -p auto -f lz mydir
```
You can also source one module directly (for example
`profile.d/compress.sh`) if you only need a subset of functions.
When you source a module directly, profile configuration parsing/loading from
`profile.sh` is skipped, so defaults from `profile.conf` are not applied unless
your script loads them explicitly.
`profile.sh` also detects whether the current shell is interactive. In
non-interactive shells (typical script execution), interactive-only features
are intentionally disabled: prompt setup, aliases, welcome/info messages, and
startup update checks are not enabled.
In all cases, avoid aliases in scripts. Use real commands/functions instead,
because alias expansion is interactive-shell oriented and can be disabled or
behave differently in non-interactive execution.
---
**Q: I set `PROFILE_PATH` but profile still can't find its modules.** **Q: I set `PROFILE_PATH` but profile still can't find its modules.**
`PROFILE_PATH` must be exported *before* you source `profile.sh`: `PROFILE_PATH` must be exported *before* you source `profile.sh`:
@@ -84,7 +138,8 @@ Add to `profile.conf`:
PROMPT_THEME = dark PROMPT_THEME = dark
``` ```
Built-in names: `default`, `dark`, `light`, `solarized`, `solarized-light`, Built-in names: `default`, `dark`, `light`, `solarized`, `solarized-light`,
`monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`. `monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`, but you can create your
own theme.
--- ---
@@ -119,6 +174,55 @@ theme file cannot execute code. Values must be a colour variable reference
--- ---
## Git helpers
**Q: What git helper functions does profile provide?**
All git helpers are defined in `profile.d/git.sh` (new in 4.1.0):
| Command | Purpose |
|---|---|
| `gst` | Compact status with branch tracking info |
| `ggraph` | Decorated history graph |
| `gsync` | Fetch and rebase onto upstream |
| `gacp` | Add, commit and push in one command |
| `greset` | Reset to upstream, stashing local changes first |
| `gwip` | Quick WIP checkpoint commit |
| `gprune` | Delete merged local branches |
| `groot` | Print or cd to repository root |
All commands accept `-h` / `--help`.
---
**Q: Tab completion for `gacp` does not show modified files.**
Profile ships dedicated completions in `profile.d/bash-completion/git-completion.sh`,
loaded automatically in interactive sessions. If completions are missing,
check that the system git completion is installed:
```bash
# Debian / Ubuntu
apt-get install bash-completion
# Fedora / RHEL
dnf install bash-completion
```
When the native git completion helpers are available, `gacp` path completion
behaves exactly like `git add` (modified files, untracked files, directories).
---
**Q: `gacp` says "No files specified" even though I passed `-a`.**
The `-a` / `--auto` flag adds all modified files (equivalent to `git add -A`).
The default depends on `GIT_GACP_AUTO_ADD` in `profile.conf`:
```ini
[git]
GIT_GACP_AUTO_ADD = 1
```
Set to `1` to make `-a` the default.
---
## Functions ## Functions
**Q: `meteo` prints "No city specified" even though I set a default.** **Q: `meteo` prints "No city specified" even though I set a default.**
@@ -147,11 +251,46 @@ Or set `DWL_PREFERRED_TOOL` in `[net]` to whichever tool you have.
--- ---
**Q: How do I limit how long `dwl` waits for a download?**
Use the `-t` / `--timeout` option:
```bash
dwl -t 5 https://example.com/file.txt /tmp/file.txt
```
This sets a 5-second cap on both the connection and the overall transfer.
The timeout is propagated to `curl` (`--max-time` + `--connect-timeout`),
`wget` (`--timeout`), or `fetch` (`-T`) transparently.
---
**Q: The prompt takes a long time to appear when my network is unavailable.**
Fixed in 4.1.0. `check_updates -q` (called at startup) now enforces a
3-second network timeout. If the update server is unreachable the check
fails silently and the prompt appears immediately.
---
**Q: `help` only shows a list of functions. Can I get usage for a specific one?**
Yes — pass the command name as an argument:
```bash
help gacp
help dwl
help taz
```
This calls `<command> --help` and prints the full usage for that function.
---
**Q: `pkgs` does not find packages I know are installed.** **Q: `pkgs` does not find packages I know are installed.**
`pkgs` delegates to `dpkg -l` (Debian/Ubuntu) or `rpm -qa` (RHEL/Fedora). `pkgs` uses `get_pkgmgr` to detect the active package manager and delegates
If your distribution uses a different package manager (pacman, apk, brew …) to the appropriate tool. Supported families: `apt` (Debian/Ubuntu),
it is not yet supported. See `doc/todo.md` for the tracking issue. `dnf` / `yum` (RHEL/Fedora), `zypper` (openSUSE), `pacman` (Arch),
`apk` (Alpine), `portage` (Gentoo), `xbps` (Void), `nix`, `brew` (macOS).
If your distribution is not detected, run `get_pkgmgr` to see what is
identified, and check that the package manager binary is in your `PATH`.
--- ---

36
doc/known-bugs.md Executable file
View File

@@ -0,0 +1,36 @@
# Known bugs
This document tracks currently known issues and limitations.
## Open issues
- **Prompt execution time is inaccurate in Windows Terminal (WSL)**
- **Status:** open, likely not fully fixable with the current Bash prompt model.
- **Symptoms:** in Windows Terminal, the displayed duration includes idle time
and typing time, and is consistently higher than real command execution time.
Behavior differs across terminal environments:
- In a native Linux terminal (including Linux shells launched through WSL),
timing starts when Enter is pressed and stops when the prompt is shown again.
- In Windows Terminal, timing appears to start/stop on prompt display events.
- **Technical context:** execution time is measured from a `DEBUG` trap plus
`PROMPT_COMMAND`, using `date +%s%N` deltas. In WSL + Windows Terminal,
timer precision and scheduling behavior can introduce jitter that does not
match wall-clock perception.
- **Likely cause:** Windows Terminal does not handle Bash timing-related events
in the same way as native Linux terminals.
- **Impact:** cosmetic/observability issue only. Commands are executed normally.
- **Workarounds:**
- Use a native Linux terminal under WSL (for example QTerminal, Terminator,
Konsole, etc.) to recover the expected Enter→prompt timing behavior.
- Use `/usr/bin/time -p <command>` (or `time <command>`) when accurate timing is needed.
- Treat prompt timing as an approximate indicator in this environment.
---
## Rain/Matrix rendering is slow on Windows
- **Description:** The rain and matrix terminal effects are significantly slower on Windows, especially with high density settings.
- **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

@@ -30,8 +30,10 @@ TERM=xterm-256color
# Supported: lz (default), xz, bz2, gz, lzo, tar, zip, zst # Supported: lz (default), xz, bz2, gz, lzo, tar, zip, zst
#TAZ_DEFAULT_FORMAT=lz #TAZ_DEFAULT_FORMAT=lz
# taz: Number of compression threads (0 = auto-detect CPU count). # taz: Number of compression threads.
#TAZ_DEFAULT_THREADS=0 # auto — detect CPU count at runtime (default)
# N — explicit positive integer
#TAZ_DEFAULT_THREADS=auto
# taz: Compression level 1 (fast/large) … 9 (slow/small). # taz: Compression level 1 (fast/large) … 9 (slow/small).
#TAZ_DEFAULT_LEVEL=6 #TAZ_DEFAULT_LEVEL=6
@@ -76,6 +78,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
# ==============================================================================
[git]
# Fallback main branch name used when remote HEAD cannot be detected.
#GIT_MAIN_BRANCH=main
# Default remote used by git helper functions.
#GIT_DEFAULT_REMOTE=origin
# Prefix used by gwip when generating automatic checkpoint messages.
#GIT_WIP_PREFIX=wip
# gacp: Automatically add all modified files (git add -A) when no explicit file
# list is provided. Set to 0 to require explicit file paths or the -a flag.
#GIT_GACP_AUTO_ADD=1
# ============================================================================== # ==============================================================================
[info] [info]
# meteo: Default city when no argument is given. Leave unset to require an # meteo: Default city when no argument is given. Leave unset to require an
@@ -154,6 +171,24 @@ 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
#
# Show Conda environment name at the end of the top bar when active.
#PROMPT_SHOW_CONDA=1
#
# Show Python venv name when active (ignored if Conda is active).
#PROMPT_SHOW_VENV=1
#
# Show session markers (ssh, tmux, screen) when applicable.
#PROMPT_SHOW_SESSION=1
# ============================================================================== # ==============================================================================
[pwd] [pwd]
@@ -190,12 +225,20 @@ 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
# 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

@@ -191,6 +191,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 +202,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

View File

@@ -12,7 +12,7 @@ version-bump.
blockers are `local -A` (no associative arrays in ZSH without `typeset -A`) blockers are `local -A` (no associative arrays in ZSH without `typeset -A`)
and `local -n` namerefs. A thin compatibility shim would open the project to and `local -n` namerefs. A thin compatibility shim would open the project to
ZSH users. **[hard]** ZSH users. **[hard]**
- [ ] **Bash completion** — add a `profile.d/completion/` directory and write - [ ] **Bash completion** — add a more bash completion directory and write
`_profile_upgrade`, `_taz`, `_utaz`, `_meteo`, etc. completions so that `_profile_upgrade`, `_taz`, `_utaz`, `_meteo`, etc. completions so that
`<Tab>` works on all public functions. **[medium]** `<Tab>` works on all public functions. **[medium]**
@@ -20,11 +20,14 @@ 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]**
- [x] **Session context markers** — display lightweight session markers
(`ssh`, `tmux`, `screen`) at the end of the prompt bar, gated by
`[prompt]` config keys. **[easy]**
- [ ] **True-colour terminal auto-detection** — query `$COLORTERM` and - [ ] **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]**

View File

@@ -1,147 +0,0 @@
------------------------------------------------------------------------------
Initial version from Beyond Linux From Scratch by
* James Robertson <jameswrobertson@earthlink.net>
* Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>
------------------------------------------------------------------------------
Current version from Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
------------------------------------------------------------------------------
Version history:
------------------------------------------------------------------------------
# 05/03/2026 v3.6.1
Fix a typo in compress.sh
# 05/03/2026 v3.6.0
Improved utaz to make it multiformat with lot of it
Introduced ppu and ppn
Improved update system
# 04/03/2026 v3.5.0
rain has now configurable speed and color
showinfo adapted to fastfetch, replacing neofetch
# 24/02/2022 v3.3.1
Fixed version detection
Added "busy" function
Fixed use of library functions before it's loaded
# 28/11/2022 v3.3.0
Initial version update support
Changed versioning code
Added installation path detection
# 28/11/2022 v3.2.3
Made proper readme file, to improve
# 21/11/2022 v3.2.2
Fixed taz compression level analysis
Fixed typo in dpkgs
# 20/11/2022 v3.2.1
Fix some messages
Make dpkgs rpm aware (more to come)
Removed version history from main script and revert declaration order
Added required license information in all files
Completed LICENSE file
# 18/11/2022 v3.2.0
Created disp command for display and make use of it
# 10/11/2022 v3.1.1
genpwd: test if password is doable
# 08/11/2022 v3.1.0
Added password generator
# 07/11/2022 v3.0.1
Added concatenation to rmspc
Added ku
Error managed in meteo
# 27/08/2022 v3.0.0
Splitted everything in several files
Added rain screensaver
# 29/07/2022 v2.8.2
Added warning for non bash or zsh users
# 19/07/2022 v2.8.1
Few cleanups, fixes and optimizations
# 24/06/2022 v2.8.0
Added backtrace, error and settrace
[bugfix] corrected showinfo
# 22/06/2022 v2.7.1
[bugfix] few minor corrections
Added help command
# 21/06/2022 v2.7.0
Added isipv4 and isipv6 and use it in rmhost as an improvement
Removed konsole save and restore not working
# 18/10/2021 v2.6.3
Changed PS1 for status bar style version
Few minor improvements
# 26/02/2021 v2.6.2
[bugfix] taz: corrected bug with trailing slash on directories
# 25/12/2020 v2.6.1
Add check on rmhost
Improvements rmspc
Created expendlist
# 24/10/2020 v2.6.0
Added session save and restore for Konsole
# 11/09/2020 v2.5.3
Few more aliases, improved code consistancy and typo,
Improved utaz, removed showdiskmap, removed remaining French,
Added license information for future publication
# 06/03/2020 v2.5.2
Few aliases sorted out
# 05/03/2020 v2.5.1
Language consistancy fix
Added pigz support in taz
# 03/03/2020 v2.5.0
Added command taz and rmspc
Renamed auzip => utaz and improved it
# 02/03/2020 v2.4.0
Added command auzip
# 31/01/2020 v2.3.2
Figlet: changed default font to ansi_shadow
# 16/01/2020 v2.3.1
[bugfix] non-interactive were blocked with some functions
# 08/01/2020 v2.3.0
Added use of figlet and neofetch as a motd replace
# 16/12/2019 v2.2.0
Added showinfo
Primary write of showdiskmap
# 24/09/2019 v2.1.2
[bugfix] bug in profile version display
# 23/09/2019 v2.1.1
[bugfix] dpkgs
# 16/09/2018 v2.1.0
Added rmhost, setc, setfr
More locales management
# 04/02/2017 v2.0.1
clean improvements (--shell)
# 24/10/2015 v2.0.0
Added advanced functionnalities (clean, srr, etc.)
# 16/02/2013 v1.0.0
Initial version

View File

@@ -0,0 +1,220 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Git helper completions for profile.d/git.sh shortcuts.
# ------------------------------------------------------------------------------
# Return 0 when current directory is inside a git work tree.
_profile_git_in_repo()
{
git rev-parse --is-inside-work-tree >/dev/null 2>&1
}
# Load git completion helpers on demand if they are available on the system.
_profile_git_load_completion_helpers()
{
declare -F __git_complete >/dev/null 2>&1 && return 0
local completion_file
for completion_file in \
/usr/share/bash-completion/completions/git \
/usr/share/git/completion/git-completion.bash \
/etc/bash_completion.d/git
do
if [[ -r "$completion_file" ]]; then
# shellcheck source=/dev/null
. "$completion_file"
break
fi
done
declare -F __git_complete >/dev/null 2>&1
}
_profile_git_complete_remotes()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
if ! _profile_git_in_repo; then
COMPREPLY=()
return 0
fi
COMPREPLY=( $(compgen -W "$(git remote 2>/dev/null)" -- "$cur") )
}
_profile_git_complete_refs()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
if ! _profile_git_in_repo; then
COMPREPLY=()
return 0
fi
COMPREPLY=( $(compgen -W "$(git for-each-ref --format='%(refname:short)' refs/heads refs/remotes refs/tags 2>/dev/null)" -- "$cur") )
}
_profile_git_complete_add_paths()
{
# shellcheck disable=SC2034 # Used indirectly by git-completion helpers via dynamic scope.
local cur words cword prev __git_cmd_idx=0
local complete_opt="--others --modified --directory --no-empty-directory"
if declare -F __git_complete_index_file >/dev/null 2>&1; then
if declare -F _get_comp_words_by_ref >/dev/null 2>&1; then
_get_comp_words_by_ref -n =: cur words cword prev
else
cur="${COMP_WORDS[COMP_CWORD]}"
if (( COMP_CWORD > 0 )); then
prev="${COMP_WORDS[COMP_CWORD-1]}"
else
prev=""
fi
# shellcheck disable=SC2034 # Used indirectly by git-completion helpers via dynamic scope.
cword="$COMP_CWORD"
# shellcheck disable=SC2034 # Used indirectly by git-completion helpers via dynamic scope.
words=("${COMP_WORDS[@]}")
fi
if [[ -n $(__git_find_on_cmdline "-u --update") ]]; then
complete_opt="--modified"
fi
__git_complete_index_file "$complete_opt"
return 0
fi
COMPREPLY=( $(compgen -f -- "${COMP_WORDS[COMP_CWORD]}") )
}
_complete_gst()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
-*)
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
;;
*)
COMPREPLY=( $(compgen -d -- "$cur") )
;;
esac
}
_complete_ggraph()
{
local cur prev
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$prev" in
-n|--limit)
COMPREPLY=()
return 0
;;
esac
COMPREPLY=( $(compgen -W "-h --help -n --limit" -- "$cur") )
}
_complete_gsync()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
return 0
fi
_profile_git_complete_remotes
}
_complete_gacp()
{
local cur prev
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$prev" in
-m|--message)
COMPREPLY=()
return 0
;;
esac
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help -a --auto -m --message" -- "$cur") )
return 0
fi
_profile_git_complete_add_paths
}
_complete_greset()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help -x --with-ignored" -- "$cur") )
return 0
fi
_profile_git_complete_refs
}
_complete_gwip()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
else
COMPREPLY=()
fi
}
_complete_gprune()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ $cur == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
return 0
fi
_profile_git_complete_refs
}
_complete_groot()
{
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "-h --help -g --go" -- "$cur") )
}
_profile_git_register_completions()
{
complete -F _complete_gst gst
complete -F _complete_ggraph ggraph
complete -F _complete_gsync gsync
complete -F _complete_gacp gacp
complete -F _complete_greset greset
complete -F _complete_gwip gwip
complete -F _complete_gprune gprune
complete -F _complete_groot groot
}
# Register completions only in interactive bash sessions.
if [[ $- == *i* && -n ${BASH_VERSION:-} ]]; then
_profile_git_load_completion_helpers >/dev/null 2>&1 || true
_profile_git_register_completions
fi
# EOF

View File

@@ -0,0 +1,52 @@
#!/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
;;
esac
if [[ "$cur" == -* ]]; then
COMPREPLY=( $(compgen -W "-h --help -l --list" -- "$cur") )
return 0
fi
if [[ "$cur" == */* || "$cur" == .* ]]; then
COMPREPLY=( $(compgen -f -X '!*.theme' -- "$cur") )
return 0
fi
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

@@ -395,18 +395,32 @@ export -f utaz
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Compress directories or files into one or more archive # Compress directories or files into one or more archive
# Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN] # Usage: taz [option] [--parallel=<n|auto>] [--format=<format>] [directory1 ... directoryN]
# Options: # Options:
# -h, --help Display that help screen # -h, --help Display that help screen
# -d, --delete Delete source file or directory after success # -d, --delete Delete source file or directory after success
# -f, --format Chose archive format in the given list. If several format are # -f, --format Chose archive format in the given list. If several format are
# given, the smalest is kept # given, the smalest is kept
# -p, --parallel Number of threads to use (if allowed by underlying utility) # -p, --parallel Number of threads to use, or 'auto' to use detected CPU count
# -v, --verbose Display progress where possible # -v, --verbose Display progress where possible
# -q, --quiet Display less messages (only errors and warnings) # -q, --quiet Display less messages (only errors and warnings)
# -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest] # -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]
taz() taz()
{ {
# Resolve runtime CPU count for --parallel=auto.
_taz_detect_cpus()
{
local cpus=1
if command -v nproc >/dev/null 2>&1; then
cpus=$(nproc 2>/dev/null)
elif command -v getconf >/dev/null 2>&1; then
cpus=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
fi
[[ $cpus =~ ^[1-9][0-9]*$ ]] || cpus=1
printf "%s\n" "$cpus"
}
# shellcheck disable=SC2329 # shellcheck disable=SC2329
_doxz() _doxz()
{ {
@@ -422,7 +436,7 @@ taz()
disp W "xz format is not suited for long term archiving." disp W "xz format is not suited for long term archiving."
disp I "See https://www.nongnu.org/lzip/xz_inadequate.html for details." disp I "See https://www.nongnu.org/lzip/xz_inadequate.html for details."
# Compresse to xz (lzma2) - Deprecated # Compress with xz (lzma2) - Deprecated
xz "${verb[@]}" --compress --keep "-$3" -T "$2" "$1" xz "${verb[@]}" --compress --keep "-$3" -T "$2" "$1"
return $? return $?
} }
@@ -449,7 +463,7 @@ taz()
[[ $4 ]] && opt=('-vv') [[ $4 ]] && opt=('-vv')
opt+=("$procopt") opt+=("$procopt")
# Compresse au format lzip (lzma) # Compress with lzip (lzma)
$command "${opt[@]}" --keep "-$3" "$1" $command "${opt[@]}" --keep "-$3" "$1"
return $? return $?
} }
@@ -476,7 +490,7 @@ taz()
[[ $4 ]] && opt=('--verbose') [[ $4 ]] && opt=('--verbose')
opt+=("$procopt") opt+=("$procopt")
# Compresse au format bz2 # Compress with gzip
$command "${opt[@]}" --keep "-$3" "$1" $command "${opt[@]}" --keep "-$3" "$1"
return $? return $?
} }
@@ -503,7 +517,7 @@ taz()
[[ $4 ]] && opt=('-v') [[ $4 ]] && opt=('-v')
opt+=("$procopt") opt+=("$procopt")
# Compresse au format bz2 # Compress with bz2
$command "${opt[@]}" --compress --keep "-$3" "$1" $command "${opt[@]}" --compress --keep "-$3" "$1"
return $? return $?
} }
@@ -520,7 +534,7 @@ taz()
[[ $4 ]] && verb=('-v') [[ $4 ]] && verb=('-v')
[[ $2 -gt 1 ]] && disp W "lzop doesn't support multithreading, falling back to 1 thread." [[ $2 -gt 1 ]] && disp W "lzop doesn't support multithreading, falling back to 1 thread."
# Compresse au format lzo # Compress with lzo
lzop "${verb[@]}" --keep "-$3" "$1" lzop "${verb[@]}" --keep "-$3" "$1"
return $? return $?
} }
@@ -537,13 +551,13 @@ taz()
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "taz: archive all files of a directory.\n\n" printf "taz: archive all files of a directory.\n\n"
printf "Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]\n\n" printf "Usage: taz [option] [--parallel=<n|auto>] [--format=<format>] [directory1 ... directoryN]\n\n"
printf "Options:\n" printf "Options:\n"
printf "\t-h, --help\tDisplay that help screen\n" printf "\t-h, --help\tDisplay that help screen\n"
printf "\t-d, --delete\tDelete source file or directory after success\n" printf "\t-d, --delete\tDelete source file or directory after success\n"
printf "\t-f, --format\tChose archive format in the given list. If several format are" printf "\t-f, --format\tChose archive format in the given list. If several format are\n"
printf "\t\t\tgiven, the smalest is kept\n" printf "\t\t\tgiven, the smalest is kept\n"
printf "\t-p, --parallel\tNumber of threads to use (if allowed by underlying utility)\n" printf "\t-p, --parallel\tNumber of threads, or 'auto' for runtime CPU count\n"
printf "\t-v, --verbose\tDisplay progress where possible\n" printf "\t-v, --verbose\tDisplay progress where possible\n"
printf "\t-q, --quiet\tDisplay less messages (only errors and warnings)\n" printf "\t-q, --quiet\tDisplay less messages (only errors and warnings)\n"
printf "\t-1, .., -9\tCompression level to use [1=fast/biggest, 9=slow/smallest]\n\n" printf "\t-1, .., -9\tCompression level to use [1=fast/biggest, 9=slow/smallest]\n\n"
@@ -606,11 +620,20 @@ taz()
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".") [[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
[[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz} [[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz}
[[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-1} [[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-auto}
[[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6} [[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6}
[[ $verbose -gt 1 && $quiet -gt 1 ]] && [[ $verbose -gt 1 && $quiet -gt 1 ]] &&
disp E "The --verbose and --quiet options can't be used together." disp E "The --verbose and --quiet options can't be used together."
# Backward compatibility: 0 previously meant auto-detect.
[[ $nproc == 0 ]] && nproc=auto
if [[ $nproc == auto ]]; then
nproc=$(_taz_detect_cpus)
elif [[ ! $nproc =~ ^[1-9][0-9]*$ ]]; then
disp E "Invalid value for --parallel: '$nproc' (expected auto or a positive integer)."
return 1
fi
for item in "${FILES[@]}"; do for item in "${FILES[@]}"; do
local donetar=0 local donetar=0
disp I "Processing $item..." disp I "Processing $item..."

View File

@@ -128,52 +128,107 @@ export -f set_colors
# D : debug (cyan) # D : debug (cyan)
disp() disp()
{ {
_disp_print_wrapped()
{
local prefix="$1"
local prefix_len="$2"
local target_fd="$3"
shift 3
local message="$*"
local cols="${COLUMNS:-}"
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
cols=$(tput cols 2>/dev/null)
fi
[[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80
local indent_len=0
[[ "$prefix_len" =~ ^[0-9]+$ && "$prefix_len" -gt 0 ]] && indent_len=$((prefix_len + 1))
local width=$((cols - indent_len))
(( width < 10 )) && width=10
local wrapped
wrapped=$(printf "%s" "$message" | fold -s -w "$width")
local first_line=1
local line
while IFS= read -r line || [[ -n "$line" ]]; do
if (( first_line )); then
if [[ -n "$prefix" ]]; then
if [[ "$target_fd" -eq 2 ]]; then
printf "%b\n" "${prefix} ${line}${RESETCOL}" >&2
else
printf "%b\n" "${prefix} ${line}${RESETCOL}"
fi
else
if [[ "$target_fd" -eq 2 ]]; then
printf "%b\n" "${line}${RESETCOL}" >&2
else
printf "%b\n" "${line}${RESETCOL}"
fi
fi
first_line=0
else
if [[ "$target_fd" -eq 2 ]]; then
printf "%*s%b\n" "$indent_len" "" "${line}${RESETCOL}" >&2
else
printf "%*s%b\n" "$indent_len" "" "${line}${RESETCOL}"
fi
fi
done <<< "$wrapped"
}
# Handle NO_COLOR: disable colors if set # Handle NO_COLOR: disable colors if set
local color_enabled=1 local color_enabled=1
[[ -n $NO_COLOR ]] && color_enabled=0 [[ -n $NO_COLOR ]] && color_enabled=0
case ${1^^} in case ${1^^} in
"I") "I")
local heads_plain="[ info ]"
if [[ $color_enabled -eq 1 ]]; then if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IGreen}info${DEFAULTFG} ]" local heads="[ ${IGreen}info${DEFAULTFG} ]"
else else
local heads="[ info ]" local heads="$heads_plain"
fi fi
shift shift
[[ -z $QUIET || $QUIET -ne 1 ]] && \ [[ -z $QUIET || $QUIET -ne 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}" _disp_print_wrapped "$heads" "${#heads_plain}" 1 "$*"
;; ;;
"W") "W")
local heads_plain="[ Warning ]"
if [[ $color_enabled -eq 1 ]]; then if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IYellow}Warning${DEFAULTFG} ]" local heads="[ ${IYellow}Warning${DEFAULTFG} ]"
else else
local heads="[ Warning ]" local heads="$heads_plain"
fi fi
shift shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2 _disp_print_wrapped "$heads" "${#heads_plain}" 2 "$*"
;; ;;
"E") "E")
local heads_plain="[ ERROR ]"
if [[ $color_enabled -eq 1 ]]; then if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IRed}ERROR${DEFAULTFG} ]" local heads="[ ${IRed}ERROR${DEFAULTFG} ]"
else else
local heads="[ ERROR ]" local heads="$heads_plain"
fi fi
shift shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2 _disp_print_wrapped "$heads" "${#heads_plain}" 2 "$*"
;; ;;
"D") "D")
local heads_plain="[ debug ]"
if [[ $color_enabled -eq 1 ]]; then if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${ICyan}debug${DEFAULTFG} ]" local heads="[ ${ICyan}debug${DEFAULTFG} ]"
else else
local heads="[ debug ]" local heads="$heads_plain"
fi fi
shift shift
[[ -n $DEBUG && $DEBUG -gt 1 ]] && \ [[ -n $DEBUG && $DEBUG -gt 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}" _disp_print_wrapped "$heads" "${#heads_plain}" 1 "$*"
;; ;;
* ) * )
[[ -z $QUIET || $QUIET -ne 1 ]] && \ [[ -z $QUIET || $QUIET -ne 1 ]] && \
printf "%b\n" "$*" _disp_print_wrapped "" 0 1 "$*"
;; ;;
esac esac
} }
@@ -181,6 +236,478 @@ export -f disp
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Render Markdown files with terminal formatting
# Usage: mdcat [file]
mdcat()
{
_mdcat_style_inline()
{
local text="$1"
local bold_on="" italic_on="" code_on="" style_off=""
local link_text_on="" link_url_on=""
if [[ -z $NO_COLOR ]]; then
bold_on=$'\e[1m'
italic_on=$'\e[3m'
code_on="${On_IBlack}${BIWhite}"
link_text_on=$'\e[4;96m'
link_url_on="$IBlack"
style_off="$RESETCOL"
fi
# Apply inline transforms in a safe order: code, links, then emphasis.
# This prevents emphasis parsing inside code spans or link URLs.
while [[ "$text" =~ \[([^][]+)\]\(([^()]+)\) ]]; do
local match="${BASH_REMATCH[0]}"
local label="${BASH_REMATCH[1]}"
local url="${BASH_REMATCH[2]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
if [[ "$label" == "$url" ]]; then
if [[ -z $NO_COLOR ]]; then
text="${before}${link_text_on}${label}${style_off}${after}"
else
text="${before}${label}${after}"
fi
elif [[ -z $NO_COLOR ]]; then
text="${before}${link_text_on}${label}${style_off} ${link_url_on}(${url})${style_off}${after}"
else
text="${before}${label} (${url})${after}"
fi
done
# Style bare URLs without re-matching the same URL forever.
# The prefix capture keeps progress monotonic and prevents freeze loops.
# Skip this transformation in NO_COLOR mode.
if [[ -z $NO_COLOR ]]; then
while [[ "$text" =~ (^|[[:space:]\(])(https?://[^[:space:]\)]+) ]]; do
local match="${BASH_REMATCH[0]}"
local pre="${BASH_REMATCH[1]}"
local url="${BASH_REMATCH[2]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
text="${before}${pre}${link_text_on}${url}${style_off}${after}"
done
fi
while [[ "$text" =~ \`([^\`]+)\` ]]; do
local match="${BASH_REMATCH[0]}"
local val="${BASH_REMATCH[1]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
text="${before}${code_on}${val}${style_off}${after}"
done
while [[ "$text" =~ \*\*([^*]+)\*\* ]]; do
local match="${BASH_REMATCH[0]}"
local val="${BASH_REMATCH[1]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
text="${before}${bold_on}${val}${style_off}${after}"
done
while [[ "$text" =~ __([^_]+)__ ]]; do
local match="${BASH_REMATCH[0]}"
local val="${BASH_REMATCH[1]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
text="${before}${bold_on}${val}${style_off}${after}"
done
while [[ "$text" =~ \*([^*]+)\* ]]; do
local match="${BASH_REMATCH[0]}"
local val="${BASH_REMATCH[1]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
text="${before}${italic_on}${val}${style_off}${after}"
done
# Match _italic_ only when underscores are outside word-like identifiers.
while [[ "$text" =~ (^|[[:space:][:punct:]])_([^[:space:]_][^_]*[^[:space:]_])_([[:space:][:punct:]]|$) ]]; do
local match="${BASH_REMATCH[0]}"
local pre="${BASH_REMATCH[1]}"
local val="${BASH_REMATCH[2]}"
local post="${BASH_REMATCH[3]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
text="${before}${pre}${italic_on}${val}${style_off}${post}${after}"
done
# Unescape Markdown punctuation escapes, such as \<, \>, \_, and \*.
while [[ "$text" =~ \\([[:punct:]]) ]]; do
local match="${BASH_REMATCH[0]}"
local val="${BASH_REMATCH[1]}"
local before="${text%%"$match"*}"
local after="${text#*"$match"}"
text="${before}${val}${after}"
done
printf "%s\n" "$text"
}
_mdcat_print_hr()
{
local cols="${COLUMNS:-}"
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
cols=$(tput cols 2>/dev/null)
fi
[[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80
local hr
printf -v hr "%*s" "$cols" ""
hr="${hr// /-}"
if [[ -z $NO_COLOR ]]; then
printf "%b%s%b\n" "$IBlack" "$hr" "$RESETCOL"
else
printf "%s\n" "$hr"
fi
}
_mdcat_print_code_block()
{
local lang="$1"
shift
local -a lines=("$@")
local cols="${COLUMNS:-}"
if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then
cols=$(tput cols 2>/dev/null)
fi
[[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80
local max_inner=$((cols - 4))
(( max_inner < 16 )) && max_inner=16
local width=16 line
for line in "${lines[@]}"; do
local line_len=${#line}
(( line_len > width )) && width=$line_len
done
(( width > max_inner )) && width=$max_inner
local border
printf -v border "+-%*s-+" "$width" ""
border="${border// /-}"
local frame_on="" code_on="" off=""
if [[ -z $NO_COLOR ]]; then
frame_on="$IBlack"
code_on="$BIWhite"
off="$RESETCOL"
fi
printf "%b%s%b\n" "$frame_on" "$border" "$off"
if [[ -n "$lang" ]]; then
local tag="language: $lang"
local wrapped_tag
wrapped_tag=$(printf "%s" "$tag" | fold -s -w "$width")
while IFS= read -r line || [[ -n "$line" ]]; do
printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "$line" "$frame_on" "$off"
done <<< "$wrapped_tag"
printf "%b%s%b\n" "$frame_on" "$border" "$off"
fi
if [[ ${#lines[@]} -eq 0 ]]; then
printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "" "$frame_on" "$off"
else
for line in "${lines[@]}"; do
local wrapped
wrapped=$(printf "%s" "$line" | fold -s -w "$width")
while IFS= read -r wline || [[ -n "$wline" ]]; do
printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "$wline" "$frame_on" "$off"
done <<< "$wrapped"
done
fi
printf "%b%s%b\n" "$frame_on" "$border" "$off"
}
_mdcat_print_table()
{
local -a lines=("$@")
local -a table_rows=()
local -a col_widths=()
local i j ncols=0
local sep=$'\x1f'
_mdcat_parse_table_row()
{
local input="$1"
local line
line=$(printf '%s' "$input" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
line="${line#|}"
line="${line%|}"
# Parse Markdown cells using only '|' separators and preserve spaces inside cells.
local -a cells=()
local cell rest="$line"
while :; do
if [[ "$rest" == *'|'* ]]; then
cell="${rest%%|*}"
rest="${rest#*|}"
else
cell="$rest"
rest=""
fi
cell=$(printf '%s' "$cell" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
cells+=("$cell")
[[ -z "$rest" ]] && break
done
if [[ ${#cells[@]} -eq 0 ]]; then
printf '%s' ""
return
fi
local joined="${cells[0]}"
for ((j=1; j<${#cells[@]}; ++j)); do
joined+="$sep${cells[j]}"
done
printf '%s' "$joined"
}
# Parse header and data rows, skipping the Markdown separator row.
# Width is computed from visible text length with ANSI escapes stripped.
for ((i=0; i<${#lines[@]}; ++i)); do
(( i == 1 )) && continue
local parsed
parsed=$(_mdcat_parse_table_row "${lines[i]}")
table_rows+=("$parsed")
local -a row=()
IFS="$sep" read -r -a row <<< "$parsed"
(( ncols < ${#row[@]} )) && ncols=${#row[@]}
for ((j=0; j<${#row[@]}; ++j)); do
local vis
vis=$(_mdcat_style_inline "${row[j]}")
vis=$(printf '%b' "$vis" | sed -E 's/\x1B\[[0-9;]*[mK]//g')
local cell_len=${#vis}
[[ -z "${col_widths[j]}" ]] && col_widths[j]=0
(( col_widths[j] < cell_len )) && col_widths[j]=$cell_len
done
done
# Ensure all width slots are initialized before drawing borders.
for ((j=0; j<ncols; ++j)); do
[[ -z "${col_widths[j]}" ]] && col_widths[j]=0
done
# Draw top border
local border="+"
for ((j=0; j<ncols; ++j)); do
local w=$((col_widths[j]+2))
border+="$(printf '%*s' "$w" "" | tr ' ' '-')+"
done
printf "%b\n" "$IBlack$border$RESETCOL"
# Print header row
local -a header=()
IFS="$sep" read -r -a header <<< "${table_rows[0]}"
printf "%b|" "$IBlack"
for ((j=0; j<ncols; ++j)); do
local raw_cell="${header[j]:-}"
local styled_cell
styled_cell=$(_mdcat_style_inline "$raw_cell")
local visible
visible=$(printf '%b' "$styled_cell" | sed -E 's/\x1B\[[0-9;]*[mK]//g')
local pad=$((col_widths[j] - ${#visible}))
(( pad < 0 )) && pad=0
printf " %b%b%*s%b |" "$BBlue" "$styled_cell" "$pad" "" "$IBlack"
done
printf "%b\n" "$RESETCOL"
# Header separator
printf "%b|" "$IBlack"
for ((j=0; j<ncols; ++j)); do
printf " %s |" "$(printf '%*s' "${col_widths[j]}" "" | tr ' ' '-')"
done
printf "%b\n" "$RESETCOL"
# Print data rows
for ((i=1; i<${#table_rows[@]}; ++i)); do
local -a row=()
IFS="$sep" read -r -a row <<< "${table_rows[i]}"
printf "%b|" "$IBlack"
for ((j=0; j<ncols; ++j)); do
local raw_cell="${row[j]:-}"
local styled_cell
styled_cell=$(_mdcat_style_inline "$raw_cell")
local visible
visible=$(printf '%b' "$styled_cell" | sed -E 's/\x1B\[[0-9;]*[mK]//g')
local pad=$((col_widths[j] - ${#visible}))
(( pad < 0 )) && pad=0
printf " %b%b%*s%b |" "$RESETCOL" "$styled_cell" "$pad" "" "$IBlack"
done
printf "%b\n" "$RESETCOL"
done
# Draw bottom border
printf "%b\n" "$IBlack$border$RESETCOL"
}
local PARSED
PARSED=$(getopt -o h --long help -n 'mdcat' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"mdcat --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "mdcat: Render a Markdown file with terminal formatting.\n"
printf "Usage: mdcat [file]\n"
printf "If no file is provided, mdcat reads from standard input.\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid option, use \"mdcat --help\" to display options list"
return 1
;;
esac
done
if [[ $# -gt 1 ]]; then
disp E "Too many arguments. Usage: mdcat [file]"
return 1
fi
local input_file=""
if [[ $# -eq 1 ]]; then
input_file="$1"
if [[ ! -f "$input_file" ]]; then
disp E "File not found: $input_file"
return 1
fi
if [[ ! -r "$input_file" ]]; then
disp E "File is not readable: $input_file"
return 1
fi
elif [[ -t 0 ]]; then
disp E "No input provided. Usage: mdcat [file] or: cat file.md | mdcat"
return 1
fi
local in_code=0 code_lang="" raw line
local -a code_lines=()
local in_table=0
local -a table_lines=()
while IFS= read -r raw || [[ -n "$raw" ]]; do
line="${raw%$'\r'}"
# Table detection: line with |, next line with | and ---
if [[ $in_table -eq 0 && "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ ]]; then
local next
IFS= read -r next || true
# Accept: | --- | --- | or |:---|---:| etc.
if [[ "$next" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then
in_table=1
table_lines=("$line" "$next")
continue
fi
fi
if [[ $in_table -eq 1 ]]; then
# Accept table row if it starts and ends with |
if [[ "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ && ! "$line" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then
table_lines+=("$line")
continue
else
_mdcat_print_table "${table_lines[@]}"
in_table=0
table_lines=()
fi
fi
if [[ $in_code -eq 1 ]]; then
if [[ "$line" =~ ^\`\`\` ]]; then
_mdcat_print_code_block "$code_lang" "${code_lines[@]}"
in_code=0
code_lang=""
code_lines=()
else
code_lines+=("$line")
fi
continue
fi
if [[ "$line" =~ ^\`\`\`[[:space:]]*([^[:space:]]*) ]]; then
in_code=1
code_lang="${BASH_REMATCH[1]}"
code_lines=()
continue
fi
if [[ "$line" =~ ^(#{1,6})[[:space:]]+(.*)$ ]]; then
local lvl=${#BASH_REMATCH[1]}
local title="${BASH_REMATCH[2]}"
local h_on=""
if [[ -z $NO_COLOR ]]; then
case "$lvl" in
1) h_on="$BBlue" ;;
2) h_on="$BCyan" ;;
3) h_on="$BGreen" ;;
*) h_on="$BIWhite" ;;
esac
fi
printf "%b%s%b\n" "$h_on" "$title" "$RESETCOL"
continue
fi
if [[ "$line" =~ ^[[:space:]]*([\-*_])[[:space:]]*\1[[:space:]]*\1[\-*_[:space:]]*$ ]]; then
_mdcat_print_hr
continue
fi
if [[ "$line" =~ ^([[:space:]]*)\>[[:space:]]?(.*)$ ]]; then
local quote="${BASH_REMATCH[2]}"
quote=$(_mdcat_style_inline "$quote")
if [[ -z $NO_COLOR ]]; then
printf "%s%b|%b %b\n" "${BASH_REMATCH[1]}" "$ICyan" "$RESETCOL" "$quote"
else
printf "%s| %b\n" "${BASH_REMATCH[1]}" "$quote"
fi
continue
fi
if [[ "$line" =~ ^([[:space:]]*)[-+*][[:space:]]+(.*)$ ]]; then
local item="${BASH_REMATCH[2]}"
item=$(_mdcat_style_inline "$item")
if [[ -z $NO_COLOR ]]; then
printf "%s%b*%b %b\n" "${BASH_REMATCH[1]}" "$IGreen" "$RESETCOL" "$item"
else
printf "%s* %b\n" "${BASH_REMATCH[1]}" "$item"
fi
continue
fi
if [[ "$line" =~ ^([[:space:]]*)([0-9]+)\.[[:space:]]+(.*)$ ]]; then
local nitem="${BASH_REMATCH[3]}"
nitem=$(_mdcat_style_inline "$nitem")
if [[ -z $NO_COLOR ]]; then
printf "%s%b%s.%b %b\n" "${BASH_REMATCH[1]}" "$IGreen" "${BASH_REMATCH[2]}" "$RESETCOL" "$nitem"
else
printf "%s%s. %b\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "$nitem"
fi
continue
fi
printf "%b\n" "$(_mdcat_style_inline "$line")"
done < "${input_file:-/dev/stdin}"
if [[ $in_code -eq 1 ]]; then
_mdcat_print_code_block "$code_lang" "${code_lines[@]}"
fi
}
export -f mdcat
# ------------------------------------------------------------------------------
# Load disp section variables # Load disp section variables
load_conf disp load_conf disp
set_colors set_colors

613
profile.d/git.sh Normal file
View File

@@ -0,0 +1,613 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Built-in defaults (can be overridden from [git] section in profile.conf)
: "${GIT_MAIN_BRANCH:=main}"
: "${GIT_DEFAULT_REMOTE:=origin}"
: "${GIT_WIP_PREFIX:=wip}"
: "${GIT_GACP_AUTO_ADD:=1}"
# ------------------------------------------------------------------------------
# Internal helper: ensure git is available and cwd is a git worktree
_git_require_repo()
{
if ! command -v git >/dev/null 2>&1; then
disp E "git command not found."
return 1
fi
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
disp E "Current directory is not inside a git repository."
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Internal helper: return default branch from remote HEAD, fallback to config
_git_default_branch()
{
local remote="${1:-$GIT_DEFAULT_REMOTE}"
local head
head=$(git symbolic-ref --quiet --short "refs/remotes/${remote}/HEAD" 2>/dev/null) || true
if [[ -n $head ]]; then
printf "%s\n" "${head#"${remote}"/}"
return 0
fi
printf "%s\n" "$GIT_MAIN_BRANCH"
return 0
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display compact git status + branch tracking information
# Usage: gst [path]
gst()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'gst' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"gst --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "gst: Display short git status and branch tracking info.\n"
printf "Usage: gst [path]\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"gst --help\" to display usage."
return 1
;;
esac
done
local target="${1:-.}"
git -C "$target" status --short --branch
}
export -f gst
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Show a readable commit graph
# Usage: ggraph [-n limit]
ggraph()
{
local PARSED
PARSED=$(getopt -o hn: --long help,limit: -n 'ggraph' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"ggraph --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
local limit=30
while true; do
case "$1" in
-h|--help)
printf "ggraph: Display decorated git history graph.\n"
printf "Usage: ggraph [-n limit]\n"
printf "Options:\n"
printf "\t-n, --limit\tNumber of commits to display (default: 30)\n"
return 0
;;
-n|--limit)
limit="$2"
shift 2
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"ggraph --help\" to display usage."
return 1
;;
esac
done
[[ $limit =~ ^[0-9]+$ ]] || {
disp E "Invalid limit: must be a positive integer."
return 1
}
_git_require_repo || return 1
git log --graph --decorate --oneline --all --max-count="$limit"
}
export -f ggraph
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Sync current branch with remote (fetch + rebase)
# Usage: gsync [remote]
gsync()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'gsync' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"gsync --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "gsync: Fetch and rebase current branch onto its remote tracking branch.\n"
printf "Usage: gsync [remote]\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"gsync --help\" to display usage."
return 1
;;
esac
done
_git_require_repo || return 1
local remote="${1:-$GIT_DEFAULT_REMOTE}"
local branch upstream
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
disp I "Fetching from $remote..."
git fetch --prune "$remote" || return 1
if [[ -z $upstream ]]; then
disp W "No upstream configured for $branch, skipping rebase."
disp I "Set one with: git branch --set-upstream-to ${remote}/${branch} ${branch}"
return 0
fi
disp I "Rebasing $branch onto $upstream..."
git rebase "$upstream"
}
export -f gsync
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Add, commit, and push changes with automatic pull/rebase if needed
# Usage: gacp -m "message" [file1 file2 ...]
gacp()
{
local PARSED
PARSED=$(getopt -o ham: --long help,auto,message: -n 'gacp' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"gacp --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
local msg="" auto_add="$GIT_GACP_AUTO_ADD"
while true; do
case "$1" in
-h|--help)
printf "gacp: Run git add, git commit, and git push in one command.\n"
printf "Usage: gacp [-a] -m \"message\" [file1 file2 ...]\n"
printf "Options:\n"
printf "\t-a, --auto\tAutomatically add all modified files (git add -A)\n"
printf "\t-m, --message\tCommit message (mandatory)\n"
printf "\n"
printf "If files are provided, only those paths are added (-a is ignored).\n"
printf "If no file is provided and -a is active, all changes are added with git add -A.\n"
printf "Default for -a can be set via GIT_GACP_AUTO_ADD in profile.conf.\n"
printf "If the remote branch moved forward, gacp pulls with rebase before pushing.\n"
return 0
;;
-a|--auto)
auto_add=1
shift
;;
-m|--message)
msg="$2"
shift 2
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"gacp --help\" to display usage."
return 1
;;
esac
done
_git_require_repo || return 1
if [[ -z $msg ]]; then
disp E "Missing commit message. Use -m or --message."
return 1
fi
local branch upstream remote tracking_branch behind counts
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
if [[ $# -gt 0 ]]; then
auto_add=0
disp I "Adding selected paths..."
git add -- "$@" || return 1
elif [[ $auto_add -eq 1 ]]; then
disp I "Adding all changes..."
git add -A || return 1
else
disp E "No files specified. Use -a/--auto or provide file paths."
return 1
fi
if git diff --cached --quiet; then
disp W "No staged changes to commit."
return 1
fi
disp I "Creating commit..."
git commit -m "$msg" || return 1
if [[ -n $upstream ]]; then
remote="${upstream%%/*}"
tracking_branch="${upstream#*/}"
else
remote="$GIT_DEFAULT_REMOTE"
tracking_branch="$branch"
fi
disp I "Fetching from $remote..."
git fetch --prune "$remote" || return 1
if git rev-parse --verify --quiet "refs/remotes/${remote}/${tracking_branch}" >/dev/null; then
counts=$(git rev-list --left-right --count HEAD..."${remote}/${tracking_branch}" 2>/dev/null) || return 1
read -r _ behind <<< "$counts"
if [[ ${behind:-0} -gt 0 ]]; then
disp I "Remote branch is ahead, rebasing before push..."
git pull --rebase "$remote" "$tracking_branch" || return 1
fi
fi
if [[ -n $upstream ]]; then
disp I "Pushing to $upstream..."
git push || return 1
else
disp I "Pushing and setting upstream to ${remote}/${branch}..."
git push -u "$remote" "$branch" || return 1
fi
disp I "gacp complete."
return 0
}
export -f gacp
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Reset local branch to exact upstream state (stash local changes first)
# Usage: greset [target]
greset()
{
local PARSED
PARSED=$(getopt -o hx --long help,with-ignored -n 'greset' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"greset --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
local clean_ignored=0
while true; do
case "$1" in
-h|--help)
printf "greset: Reset current branch to upstream, stashing local changes first.\n"
printf "Usage: greset [target]\n"
printf "Options:\n"
printf "\t-x, --with-ignored\tAlso remove ignored files (git clean -fdx)\n"
printf "\n"
printf "Default target is current branch upstream (@{u}).\n"
printf "If no upstream exists, fallback target is <remote>/<branch>.\n"
printf "This command stashes local modifications (tracked + untracked),\n"
printf "drops local unpushed commits by hard-reset, and cleans untracked files.\n"
return 0
;;
-x|--with-ignored)
clean_ignored=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"greset --help\" to display usage."
return 1
;;
esac
done
_git_require_repo || return 1
local branch upstream target remote old_head stash_msg stash_out stash_created=0 dropped=0
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
target="$1"
if [[ -z $target ]]; then
if [[ -n $upstream ]]; then
target="$upstream"
else
remote="$GIT_DEFAULT_REMOTE"
target="${remote}/${branch}"
fi
fi
if [[ -z $remote ]]; then
remote="${target%%/*}"
fi
old_head=$(git rev-parse HEAD 2>/dev/null) || return 1
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then
stash_msg="greset:${branch}:$(date +'%Y-%m-%d %H:%M:%S')"
disp I "Stashing local changes as '$stash_msg'..."
stash_out=$(git stash push -u -m "$stash_msg" 2>&1) || {
disp E "Failed to stash local changes."
printf "%s\n" "$stash_out"
return 1
}
[[ $stash_out != "No local changes to save"* ]] && stash_created=1
fi
disp I "Fetching from $remote..."
git fetch --prune "$remote" || return 1
if ! git rev-parse --verify --quiet "$target" >/dev/null; then
disp E "Target '$target' does not exist."
return 1
fi
dropped=$(git rev-list --count "${target}..${old_head}" 2>/dev/null || printf "0")
disp W "Hard-resetting $branch to $target..."
git reset --hard "$target" || return 1
if (( clean_ignored )); then
git clean -fdx || return 1
else
git clean -fd || return 1
fi
if (( stash_created )); then
disp I "Local changes were stashed. Use 'git stash list' and 'git stash pop' when needed."
fi
disp I "greset complete. Dropped local-only commits: $dropped"
return 0
}
export -f greset
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Create a quick WIP commit for local checkpointing
# Usage: gwip [message]
gwip()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'gwip' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"gwip --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "gwip: Create a local checkpoint commit with all tracked/untracked changes.\n"
printf "Usage: gwip [message]\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"gwip --help\" to display usage."
return 1
;;
esac
done
_git_require_repo || return 1
local msg
if [[ $# -gt 0 ]]; then
msg="$*"
else
msg="$GIT_WIP_PREFIX: $(date +'%Y-%m-%d %H:%M:%S')"
fi
git add -A || return 1
git commit -m "$msg"
}
export -f gwip
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Delete merged local branches (except protected branches)
# Usage: gprune [main-branch]
gprune()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'gprune' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"gprune --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "gprune: Delete local branches already merged into main branch.\n"
printf "Usage: gprune [main-branch]\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"gprune --help\" to display usage."
return 1
;;
esac
done
_git_require_repo || return 1
local base="${1:-$(_git_default_branch "$GIT_DEFAULT_REMOTE")}" current deleted=0
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
disp I "Pruning branches merged into $base..."
while IFS= read -r b; do
[[ -z $b ]] && continue
[[ $b == "$current" ]] && continue
[[ $b == "$base" ]] && continue
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
git branch -d "$b" >/dev/null 2>&1 && {
printf "Deleted: %s\n" "$b"
((deleted++))
}
done < <(git branch --merged "$base" | sed -E 's/^\*?\s*//')
(( deleted == 0 )) && disp I "No merged branches to delete."
}
export -f gprune
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Print repository root path
# Usage: groot
groot()
{
local PARSED
PARSED=$(getopt -o hg --long help,go -n 'groot' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"groot --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
local do_go=0
while true; do
case "$1" in
-h|--help)
printf "groot: Display the absolute path of the current repository root.\n"
printf "Usage: groot [-g|--go]\n"
printf "Options:\n"
printf "\t-g, --go\tChange current directory to repository root\n"
return 0
;;
-g|--go)
do_go=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"groot --help\" to display usage."
return 1
;;
esac
done
_git_require_repo || return 1
local root
root=$(git rev-parse --show-toplevel) || return 1
if (( do_go )); then
cd "$root" || {
disp E "Failed to move to repository root: $root"
return 1
}
return 0
fi
printf "%s\n" "$root"
}
export -f groot
# ------------------------------------------------------------------------------
load_conf git
# EOF

View File

@@ -36,9 +36,21 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display list of commands and general informations # Display list of commands and general informations
# Usage: help # Usage: help [command]
help() help()
{ {
# If a command name is given, delegate to its --help output.
if [[ $# -gt 0 && "$1" != "--help" && "$1" != "-h" ]]; then
local cmd="$1"
if declare -F "$cmd" >/dev/null 2>&1 || command -v "$cmd" >/dev/null 2>&1; then
"$cmd" --help
else
disp E "Unknown command: $cmd"
return 1
fi
return
fi
# shellcheck disable=SC2154 # color code in disp.sh # shellcheck disable=SC2154 # color code in disp.sh
# shellcheck disable=SC2059 # printf format is a color variable # shellcheck disable=SC2059 # printf format is a color variable
printf "${BIWhite}Welcome to your profile! Here is a list of available commands:${DEFAULTCOL}\n\n" printf "${BIWhite}Welcome to your profile! Here is a list of available commands:${DEFAULTCOL}\n\n"
@@ -52,13 +64,22 @@ help()
printf "findbig\t\tFind the biggest files in the given or current directory\n" printf "findbig\t\tFind the biggest files in the given or current directory\n"
printf "finddead\tFind dead symbolic links in the given or current directory\n" printf "finddead\tFind dead symbolic links in the given or current directory\n"
printf "findzero\tFind empty files in the given or current directory\n" printf "findzero\tFind empty files in the given or current directory\n"
printf "gacp\t\tAdd, commit and push changes (auto-pull if needed)\n"
printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n" printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n"
printf "ggraph\t\tDisplay decorated git history graph\n"
printf "gprune\t\tDelete local branches already merged into main branch\n"
printf "greset\t\tReset branch to upstream (stash local, drop local commits)\n"
printf "groot\t\tDisplay repository root path (or cd to it with -g)\n"
printf "gsync\t\tFetch and rebase current branch onto upstream\n"
printf "gst\t\tDisplay short git status and branch tracking info\n"
printf "gwip\t\tCreate a quick WIP checkpoint commit\n"
printf "gpid\t\tGive the list of PIDs matching the given process name(s)\n" printf "gpid\t\tGive the list of PIDs matching the given process name(s)\n"
printf "isipv4\t\tTell if the given parameter is a valid IPv4 address\n" printf "isipv4\t\tTell if the given parameter is a valid IPv4 address\n"
printf "isipv6\t\tTell if the given parameter is a valid IPv6 address\n" printf "isipv6\t\tTell if the given parameter is a valid IPv6 address\n"
printf "ku\t\tKill all processes owned by the given user name or ID\n" printf "ku\t\tKill all processes owned by the given user name or ID\n"
printf "matrix\t\tConsole screensaver with Matrix-style digital rain (binary, kana, ascii charset)\n" printf "matrix\t\tConsole screensaver with Matrix-style digital rain (binary, kana, ascii charset)\n"
printf "mcd\t\tCreate a directory and immediately move into it\n" printf "mcd\t\tCreate a directory and immediately move into it\n"
printf "mdcat\t\tRender Markdown files in terminal with colors and code frames\n"
printf "meteo\t\tDisplay weather forecast for the configured or given city\n" printf "meteo\t\tDisplay weather forecast for the configured or given city\n"
printf "myextip\t\tGet information about your public IP address\n" printf "myextip\t\tGet information about your public IP address\n"
printf "pkgs\t\tSearch for a pattern in installed package names (dpkg/rpm, supports -i)\n" printf "pkgs\t\tSearch for a pattern in installed package names (dpkg/rpm, supports -i)\n"
@@ -82,7 +103,7 @@ help()
printf "utaz\t\tSmartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm)\n" printf "utaz\t\tSmartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm)\n"
printf "ver\t\tDisplay the installed profile version\n\n" printf "ver\t\tDisplay the installed profile version\n\n"
printf "\nPlease use <command> --help to obtain usage details.\n" printf "\nPlease use <command> --help or help <command> to obtain usage details.\n"
} }
export -f help export -f help
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -36,27 +36,52 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Download a resource using curl, wget, or fetch. # Download a resource using curl, wget, or fetch.
# Usage: dwl <url> [output_file] # Usage: dwl [-t <seconds>] <url> [output_file]
dwl() dwl()
{ {
case "$1" in local timeout=""
--help|-h)
echo "Usage: dwl <url> [output_file]" # Parse leading options before the URL.
echo "Downloads a resource using curl, wget, or fetch." while [[ $# -gt 0 ]]; do
echo "" case "$1" in
echo "Arguments:" --help|-h)
echo " url The full URL to download (http/https/ftp)." echo "Usage: dwl [-t <seconds>|--timeout <seconds>] <url> [output_file]"
echo " output_file (Optional) Path to save the file. If omitted, prints to stdout." echo "Downloads a resource using curl, wget, or fetch."
return 0 echo ""
;; echo "Arguments:"
echo " -t, --timeout Maximum time in seconds to wait for the transfer."
echo " url The full URL to download (http/https/ftp)."
echo " output_file (Optional) Path to save the file. If omitted, prints to stdout."
return 0
;;
-t|--timeout)
[[ -z "${2:-}" || ! "${2:-}" =~ ^[0-9]+$ ]] && {
echo "Error: --timeout requires a positive integer argument." >&2
return 1
}
timeout="$2"
shift 2
;;
--)
shift
break
;;
-*)
echo "Error: Unknown option '$1'. Try 'dwl --help'." >&2
return 1
;;
*)
break
;;
esac
done
case "${1:-}" in
"") "")
echo "Error: URL argument is missing." >&2 echo "Error: URL argument is missing." >&2
echo "Try 'get_resource --help' for usage." >&2 echo "Try 'dwl --help' for usage." >&2
return 1 return 1
;; ;;
esac
case "$1" in
http://*|https://*|ftp://*) ;; http://*|https://*|ftp://*) ;;
*) *)
echo "Error: '$1' does not look like a valid URL. Must start with http://, https://, or ftp://" >&2 echo "Error: '$1' does not look like a valid URL. Must start with http://, https://, or ftp://" >&2
@@ -65,35 +90,41 @@ dwl()
esac esac
local url="$1" local url="$1"
local output="$2" local output="${2:-}"
# Honour preferred tool from configuration; fall back to auto-detection. # Honour preferred tool from configuration; fall back to auto-detection.
local preferred="${DWL_PREFERRED_TOOL:-}" local preferred="${DWL_PREFERRED_TOOL:-}"
_try_curl() _try_curl()
{ {
local args=(-sL)
[[ -n "$timeout" ]] && args+=(--max-time "$timeout" --connect-timeout "$timeout")
if [[ -z "$output" ]]; then if [[ -z "$output" ]]; then
curl -sL "$url" curl "${args[@]}" "$url"
else else
curl -sL -o "$output" "$url" curl "${args[@]}" -o "$output" "$url"
fi fi
} }
_try_wget() _try_wget()
{ {
local args=(-q)
[[ -n "$timeout" ]] && args+=(--timeout="$timeout")
if [[ -z "$output" ]]; then if [[ -z "$output" ]]; then
wget -qO- "$url" wget "${args[@]}" -O- "$url"
else else
wget -q -O "$output" "$url" wget "${args[@]}" -O "$output" "$url"
fi fi
} }
_try_fetch() _try_fetch()
{ {
local args=()
[[ -n "$timeout" ]] && args+=(-T "$timeout")
if [[ -z "$output" ]]; then if [[ -z "$output" ]]; then
fetch -o - "$url" fetch "${args[@]}" -o - "$url"
else else
fetch -o "$output" "$url" fetch "${args[@]}" -o "$output" "$url"
fi fi
} }

View File

@@ -40,7 +40,7 @@
# checking available binaries in a fixed priority order. # checking available binaries in a fixed priority order.
# Echoes one of: apt dnf yum zypper pacman apk portage xbps nix # Echoes one of: apt dnf yum zypper pacman apk portage xbps nix
# Returns 1 if no known package manager could be identified. # Returns 1 if no known package manager could be identified.
_get_pkgmgr() get_pkgmgr()
{ {
local distro_id="" distro_like="" local distro_id="" distro_like=""
if [[ -r /etc/os-release ]]; then if [[ -r /etc/os-release ]]; then
@@ -56,30 +56,48 @@ _get_pkgmgr()
for id in $distro_id $distro_like; do for id in $distro_id $distro_like; do
case "${id,,}" in case "${id,,}" in
debian|ubuntu|linuxmint|raspbian|pop|kali|elementary|zorin|neon|parrot) debian|ubuntu|linuxmint|raspbian|pop|kali|elementary|zorin|neon|parrot)
echo "apt"; return 0 ;; echo "apt"
return 0
;;
fedora) fedora)
echo "dnf"; return 0 ;; echo "dnf"
return 0
;;
rhel|centos|rocky|almalinux|ol|scientific|amzn) rhel|centos|rocky|almalinux|ol|scientific|amzn)
command -v dnf >/dev/null 2>&1 && { echo "dnf"; return 0; } command -v dnf >/dev/null 2>&1 && { echo "dnf"; return 0; }
echo "yum"; return 0 ;; echo "yum"
return 0
;;
opensuse*|sles|sled) opensuse*|sles|sled)
echo "zypper"; return 0 ;; echo "zypper"
return 0
;;
arch|manjaro|endeavouros|garuda|artix|cachyos) arch|manjaro|endeavouros|garuda|artix|cachyos)
echo "pacman"; return 0 ;; echo "pacman"
return 0
;;
alpine) alpine)
echo "apk"; return 0 ;; echo "apk"
return 0
;;
gentoo) gentoo)
echo "portage"; return 0 ;; echo "portage"
return 0
;;
void) void)
echo "xbps"; return 0 ;; echo "xbps"
return 0
;;
nixos) nixos)
echo "nix"; return 0 ;; echo "nix"
return 0
;;
esac esac
done done
# Fallback: check for binaries in priority order. # Fallback: check for binaries in priority order.
local bin local bin
for bin in apt-get dnf yum zypper pacman apk emerge xbps-install nix-env; do for bin in apt apt-get dnf yum zypper pacman apk emerge xbps-install nix-env; do
command -v "$bin" >/dev/null 2>&1 && { command -v "$bin" >/dev/null 2>&1 && {
case "$bin" in case "$bin" in
apt-get) echo "apt" ;; apt-get) echo "apt" ;;
@@ -94,7 +112,7 @@ _get_pkgmgr()
return 1 return 1
} }
export -f _get_pkgmgr export -f get_pkgmgr
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -150,7 +168,7 @@ pkgs()
(( ignore_case )) && grep_opt="-i" (( ignore_case )) && grep_opt="-i"
local pkgmgr local pkgmgr
pkgmgr=$(_get_pkgmgr) || { pkgmgr=$(get_pkgmgr) || {
disp E "No usable package manager could be detected on this system." disp E "No usable package manager could be detected on this system."
return 2 return 2
} }

View File

@@ -78,6 +78,7 @@ load_theme()
[PROMPT_COLOR_ERR_BG]=1 [PROMPT_COLOR_ERR_FG]=1 [PROMPT_COLOR_ERR_MARK]=1 [PROMPT_COLOR_ERR_BG]=1 [PROMPT_COLOR_ERR_FG]=1 [PROMPT_COLOR_ERR_MARK]=1
[PROMPT_COLOR_ROOT_FG]=1 [PROMPT_COLOR_USER_FG]=1 [PROMPT_COLOR_ROOT_FG]=1 [PROMPT_COLOR_USER_FG]=1
[PROMPT_COLOR_DIR_FG]=1 [PROMPT_COLOR_DIR_FG]=1
[PROMPT_COLOR_CTX_FG]=1
) )
# ---- Colour variable names exported by disp.sh -------------------------- # ---- Colour variable names exported by disp.sh --------------------------
@@ -184,16 +185,16 @@ set_theme()
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\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 ~/my.theme\t— apply a theme by path\n"
return 0 return 0
fi fi
@@ -285,6 +286,68 @@ 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()
{ {
_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=""
if [[ "${PROMPT_SHOW_GIT_STATUS:-1}" != "0" ]]; then
if ! git diff --no-ext-diff --quiet --ignore-submodules -- 2>/dev/null || \
! git diff --cached --no-ext-diff --quiet --ignore-submodules -- 2>/dev/null; then
dirty="*"
fi
local counts ahead behind
counts=$(git rev-list --left-right --count "@{upstream}...HEAD" 2>/dev/null) || counts=""
if [[ "$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
printf "%s" "git:${branch}${dirty}${sync}"
}
_prompt_conda_env()
{
[[ "${PROMPT_SHOW_CONDA:-1}" == "0" ]] && return 0
[[ -z "${CONDA_DEFAULT_ENV:-}" ]] && return 0
printf "%s" "conda:${CONDA_DEFAULT_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##*/}"
}
_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 +364,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 +394,42 @@ 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)"
if [[ -n "$_git_seg" ]]; then
_ctx="$_git_seg"
fi
if [[ -n "$_conda_env" ]]; then
if [[ -n "$_ctx" ]]; then
_ctx+=" | ${_conda_env}"
else
_ctx="${_conda_env}"
fi
fi
if [[ -n "$_venv_env" ]]; then
if [[ -n "$_ctx" ]]; then
_ctx+=" | ${_venv_env}"
else
_ctx="${_venv_env}"
fi
fi
if [[ -n "$_session_tags" ]]; then
if [[ -n "$_ctx" ]]; then
_ctx+=" | ${_session_tags}"
else
_ctx="${_session_tags}"
fi
fi
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\] "

View File

@@ -110,12 +110,24 @@ _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"
}
_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 +187,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 +267,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]}"
drop_length=$((max_rain_height * RANDOM / 32768 + 1)) if [[ "$mode" == "matrix" ]]; then
drop_length=$((max_rain_height * RANDOM / 32768 + 2))
else
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")
@@ -277,11 +297,12 @@ rain()
{ {
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 +317,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 +349,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 +380,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
@@ -353,12 +393,13 @@ matrix()
{ {
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 +408,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 +458,20 @@ matrix()
return 1 return 1
fi fi
;; ;;
-d|--density)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
density_override=$(_rain_normalize_density "$2") || {
disp E "--density requires a positive integer value."
_matrix_show_usage
return 1
}
shift
else
disp E "--density requires a positive integer value."
_matrix_show_usage
return 1
fi
;;
-h|--help) -h|--help)
_matrix_show_usage _matrix_show_usage
return 0 return 0
@@ -429,10 +489,9 @@ 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
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf "rain" load_conf "rain"

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

@@ -90,9 +90,14 @@ check_updates()
return 4 return 4
} }
dwl "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || { # In quiet mode (startup), use a short timeout so a missing or slow network
# never blocks the interactive prompt.
local dwl_opts=()
(( quiet == 1 )) && dwl_opts+=(-t 3)
dwl "${dwl_opts[@]}" "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || {
rm -f "$vfile" rm -f "$vfile"
disp E "Cannot download version file; unable to continue." (( quiet != 1 )) && disp E "Cannot download version file; unable to continue."
return 5 return 5
} }

View File

@@ -35,10 +35,123 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
_profile_is_sourced()
{
[[ "${BASH_SOURCE[0]}" != "$0" ]]
}
_profile_finish()
{
local rc="${1:-0}"
if _profile_is_sourced; then
return "$rc"
fi
exit "$rc"
}
_profile_install_in_file()
{
local rc_file="$1"
local source_line="$2"
[[ -f "$rc_file" ]] || touch "$rc_file" || {
printf "[ Error ] Cannot create %s\n" "$rc_file" >&2
return 1
}
if grep -Fqx "$source_line" "$rc_file"; then
printf "[ Info ] Already configured in %s\n" "$rc_file"
return 0
fi
printf "\n%s\n" "$source_line" >> "$rc_file" || {
printf "[ Error ] Cannot write to %s\n" "$rc_file" >&2
return 1
}
printf "[ Info ] Added profile source line to %s\n" "$rc_file"
return 0
}
_profile_install()
{
local install_bashrc=0
local install_profile=0
local target_selected=0
local script_dir source_line rc=0
while [[ $# -gt 0 ]]; do
case "$1" in
--bashrc)
install_bashrc=1
target_selected=1
;;
--profile)
install_profile=1
target_selected=1
;;
-h|--help)
printf "Usage: %s --install [--bashrc] [--profile]\n" "${BASH_SOURCE[0]}"
printf "If no target is specified, both ~/.bashrc and ~/.profile are configured.\n"
return 0
;;
*)
printf "[ Error ] Unknown install option: %s\n" "$1" >&2
return 2
;;
esac
shift
done
if (( target_selected == 0 )); then
install_bashrc=1
install_profile=1
fi
script_dir=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")")
source_line="source \"$script_dir/profile.sh\""
if (( install_bashrc == 1 )); then
_profile_install_in_file "$HOME/.bashrc" "$source_line" || rc=$?
fi
if (( install_profile == 1 )); then
_profile_install_in_file "$HOME/.profile" "$source_line" || rc=$?
fi
return "$rc"
}
if [[ $# -gt 0 ]]; then
case "$1" in
--install)
shift
_profile_install "$@"
_profile_finish $?
;;
-h|--help)
printf "Usage: source %s\n" "${BASH_SOURCE[0]}"
printf " %s --install [--bashrc] [--profile]\n" "${BASH_SOURCE[0]}"
_profile_finish 0
;;
*)
printf "[ Error ] Unknown option: %s\n" "$1" >&2
_profile_finish 2
;;
esac
fi
if ! _profile_is_sourced; then
printf "[ Warning ] profile.sh is designed to be sourced, not executed directly.\n" >&2
printf "Use: source \"%s\"\n" "$(realpath -s "${BASH_SOURCE[0]}")" >&2
printf "Or run: %s --install [--bashrc] [--profile]\n" "${BASH_SOURCE[0]}" >&2
exit 1
fi
if [[ ! $SHELL =~ bash ]]; then if [[ ! $SHELL =~ bash ]]; then
echo "That environment script is designed to be used with bash being the shell." echo "That environment script is designed to be used with bash being the shell."
echo "Please consider using bash to enjoy our features!" echo "Please consider using bash to enjoy our features!"
return 1 _profile_finish 1
fi fi
# Required for associative arrays (4.0+) and namerefs (4.3+) # Required for associative arrays (4.0+) and namerefs (4.3+)
@@ -216,12 +329,12 @@ if [[ ! -e "$MYPATH/profile.sh" ]]; then
echo "[ Warning ] Path detection failed, trying to use pwd..." echo "[ Warning ] Path detection failed, trying to use pwd..."
MYPATH=$(pwd) MYPATH=$(pwd)
if [[ ! -e "$MYPATH/profile.sh" ]]; then if [[ ! -e "$MYPATH/profile.sh" ]]; then
echo "[ Error ] Impossible to determine installation path, pretty much nothing will work." echo "[ Error ] Unable to determine installation path, pretty much nothing will work."
fi fi
fi fi
if [[ ! -s "$MYPATH/version" ]]; then if [[ ! -s "$MYPATH/version" ]]; then
echo "[ Warning ] Impossible to determine running version of profile, your installation might be broken." echo "[ Warning ] Unable to determine running profile version; your installation might be broken."
fi fi
PROFVERSION=$(cat "$MYPATH"/version) PROFVERSION=$(cat "$MYPATH"/version)
export PROFVERSION export PROFVERSION
@@ -236,6 +349,10 @@ fi
# Parse and load general configuration # Parse and load general configuration
export PROFILE_CONF="$MYPATH/profile.conf" export PROFILE_CONF="$MYPATH/profile.conf"
parse_conf "$PROFILE_CONF" parse_conf "$PROFILE_CONF"
# Overload with user configuration if it exists
if [[ -f "$HOME/.profile.conf" ]]; then
parse_conf "$HOME/.profile.conf"
fi
load_conf system # Load Bash system behavior configuration (history, pager, etc.) load_conf system # Load Bash system behavior configuration (history, pager, etc.)
load_conf general # General purpose configuration (compilation flags, etc.) load_conf general # General purpose configuration (compilation flags, etc.)
@@ -255,8 +372,16 @@ shopt -u nullglob
[[ $- == *i* ]] && export INTERACTIVE=1 [[ $- == *i* ]] && export INTERACTIVE=1
if [[ $INTERACTIVE ]]; then if [[ $INTERACTIVE ]]; then
# For compiling (as we often compile with LFS/0linux...) # Load custom bash completions
#Aliases shopt -s nullglob
for _compl in "$MYPATH/profile.d/bash-completion/"*.sh; do
# shellcheck disable=SC1090 # Dynamic sourcing of completion scripts
[[ -f "$_compl" && -r "$_compl" ]] && . "$_compl"
done
unset _compl
shopt -u nullglob
# Aliases
load_alias aliases load_alias aliases
# Define PS1 # Define PS1
@@ -277,11 +402,13 @@ if [[ $INTERACTIVE ]]; then
fi fi
showinfo && printf "\n" showinfo && printf "\n"
check_updates -q check_updates -q
disp I "Profile version $PROFVERSION chargé..." disp I "Profile version $PROFVERSION loaded..."
fi fi
# Cleanup # Cleanup
unset pathremove pathprepend pathappend unset -f _profile_is_sourced _profile_finish _profile_install_in_file _profile_install
unset -f parse_conf load_alias load_conf
unset -f pathremove pathprepend pathappend
#return 0 #return 0

View File

@@ -1 +1 @@
3.99.2-4_rc_2 4.1.0