Compare commits

...

204 Commits

Author SHA1 Message Date
azalea a463f4234c [U] Release 2.0.2 2025-09-04 07:12:01 -04:00
azalea beb04101db [F] Fix script 2025-09-04 07:09:56 -04:00
azalea bf60e4265b [F] Fix script 2025-09-04 07:05:40 -04:00
azalea 42421bc57d [+] Changelog 2025-09-04 06:52:58 -04:00
azalea f64ee7b7e9 [O] Better warning message #419 2025-09-04 06:46:20 -04:00
azalea 1fa29cf831 [+] Adipophilia flag (#424) 2025-09-04 06:24:10 -04:00
azalea 4861dd5d4a [+] Nullflux flag #397 2025-09-04 06:11:55 -04:00
azalea 5614b723fd [+] Hypergender flags
Fixes hykilpikonna/hyfetch#422
2025-09-04 06:04:37 -04:00
azalea 5e1e13b091 [M] Move flag order 2025-09-04 05:52:17 -04:00
azalea 79ddb3ca92 [+] Fictosexual flag (#394) 2025-09-04 05:41:49 -04:00
azalea 684929edea [U] gitignore 2025-09-04 05:41:49 -04:00
Emi f02ec8c4ba Fix nixos_small logo (#421)
* [F] Fix nixos_small logo

* [F] Fix nixos_small logo missing line break

* [?] i was trying to fix a difference between the python version and the rust version what the hell dude
2025-08-27 15:10:59 +09:00
azalea 5fd4ed9b0c [F] Fix #420 fastfetch custom config 2025-08-25 02:20:24 -04:00
a catgirl 003c295084 [F] fix: custom *fetch configurations breaking hyfetch (#420)
if the backend is fastfetch, hyfetch calls
`/bin/fastfetch --logo none -s OS --disable-linewrap`
which for me returns:
`  Artix Linux x86_64`
... and that breaks hyfetch

instead, get the information from the source (/etc/os-release), which im
pretty sure most fetch backends read from anyway

this won't work on *BSD though, because those lack
/etc/os-release, so unless calling `uname -s` is alright, BSDs would
still have the problem

related pull: https://github.com/hykilpikonna/hyfetch/issues/418
2025-08-23 17:34:57 -07:00
azalea d4560e3edb [U] Release 2.0.1 2025-08-21 12:56:30 -04:00
azalea 62b4c27404 [+] Changelog 2025-08-21 12:55:45 -04:00
azalea 169368a784 [F] :) 2025-08-21 12:46:00 -04:00
azalea 3e47640c1a [F] Maybe fix git install 2025-08-21 12:42:12 -04:00
azalea 6ddd762b35 [F] ? 2025-08-21 12:37:37 -04:00
azalea 713bcce0ec [F] Fix git install 2025-08-21 12:35:04 -04:00
azalea fdfd9816f5 [F] Fix deploy script 2025-08-21 12:06:24 -04:00
azalea d6f9d1f569 [F] Fix june flicker #408 2025-08-21 11:48:24 -04:00
azalea b28aa0ddf2 [U] Bump 2025-08-21 11:23:37 -04:00
azalea a5e784c528 [F] Fix distro name detection 2025-08-21 11:21:48 -04:00
azalea 93b4dd3471 [U] Bump 2025-08-21 11:02:20 -04:00
azalea d15afe579f [O] Optimize calculations 2025-08-21 11:01:59 -04:00
azalea f79aca3c65 [-] Remove unused warning 2025-08-21 10:09:00 -04:00
azalea fa3fc59a5f [F] Fix trying to get backend from non-existing backend 2025-08-21 10:08:06 -04:00
azalea afa3c1dfb9 [F] Fix cache path might be missing 2025-08-21 10:05:02 -04:00
azalea cf6b77a1b7 [F] 💢💢💢💢💢💢💢💢💢💢💢 2025-08-21 09:52:03 -04:00
azalea b58425badd [F] 💢💢💢💢💢 2025-08-21 09:47:48 -04:00
azalea 8c1713c37d [F] Forgot to bump lock 2025-08-21 09:29:42 -04:00
azalea 3c7c449da4 [F] Maybe this can work 2025-08-21 09:28:44 -04:00
azalea 2fe4b3f287 [F] Fix cargo publish #405 2025-08-21 09:14:05 -04:00
azalea 6a80928f10 [F] Fix path 2025-08-21 08:35:42 -04:00
azalea 4ac698817d [F] Fix cargo publish #405 2025-08-21 08:31:41 -04:00
azalea 13acc0b6b6 [U] Bump 2025-08-21 08:31:41 -04:00
Ven0m0 455f0382e7 [+] Use cloudflare for public IP field (#416)
* Update neofetch

* [F] Fix drill cloudflare and add fallback

---------

Co-authored-by: Azalea <22280294+hykilpikonna@users.noreply.github.com>
2025-08-21 05:05:08 -07:00
azalea 0c9e4db742 [F] Fix #418 2025-08-21 03:17:56 -04:00
Aiden Bohlander aee058e591 Update logic to not mark the pride month animation as shown if --june is explicitly specified. (#411)
Update logic to not mark the pride month animation as shown if --june is explicitly specified.
2025-06-02 04:03:08 -04:00
azalea b74787768f [+] Bazzite (#406) 2025-05-27 11:43:32 +09:00
Zulta 218c535d06 Add Rhino Linux Logo (#407)
* added Rhino Linux Logo

* updated rhino.py

* fixed the ;; on line 8745

* [F] Fix python generation

---------

Co-authored-by: Azalea <22280294+hykilpikonna@users.noreply.github.com>
2025-05-25 21:59:28 -04:00
stella :3c 76ae5e507a [FEATURE PROTOTYPE] adds step on interactive setup which prompts the user for either the default or small variant of their distro logo. (#392)
* adds step on interactive setup which prompts the user for either the default or small variant of their distro logo.

* fixed distro small logo variant check

* fixes logo vec index overflow error

* now takes --distro as an input when attempting to fetch a small logo variant

* fixes an incorrect error message
2025-04-06 01:05:04 -04:00
Yuki Kurosawa d2dfd950a3 Update KSLinux detection due to kpt and kpm command is removed (#395)
* Update KSLinux detection due to kpt and kpm command is removed

* Fix Bugs
2025-04-06 01:03:39 -04:00
Paul 45ee7e199e Fix unwanted space in Python version (#401)
Before:

```
Python: Python 3.13.2 [Clang 16.0.0 ]
```

After:

```
Python: Python 3.13.2 [Clang 16.0.0]
```
2025-04-06 01:02:46 -04:00
azalea 2cfea54a88 [+] Cisgender flag for #386
Okay I think if I added this, someone will ask about the straight flag again.

In terms of historical context, the cisgender flag has been used neutrally, and it has not been used as a symbol of dismissal like the straight flag did. While some people question whether it's necessary to have a pride flag for the majority identity that do not face discrimination, its existence isn't perceived as offensive by the LGBTQ+ community. So I judged it would be harmless to add the cisgender flag, while adding the straight flag would be harmful.
2025-02-16 17:41:21 -05:00
Alice 0cc6ddbca8 [PR] Auto detect light/dark mode, changing the theme accordingly (#380)
* disable clippy lint

* fmt

* move detect bg into a function

* auto detect light/dark opt

* auto detect dark cli opt

* optional light-dark

* cli opt

* nicer looking output

* [-] Revert code style changes

* [-] Revert code style changes

* [-] Revert code style changes

* [-] Revert

* make the default theme dark

---------

Co-authored-by: Azalea <22280294+hykilpikonna@users.noreply.github.com>
2025-02-16 16:58:45 -05:00
Streaksu 8a536c30f0 [PR] Separate Gloire from Ironclad (#388) 2025-02-15 09:08:03 -05:00
Un1q32 5c24bd443a [PR] fix cpu fetching code for leopard and tiger (#387)
* fix cpu fetching code for leopard and tiger

* some 10.5 systems have this sysctl, just check if it exists like this
2025-02-15 09:03:48 -05:00
Un1q32 e691b2d9da [PR] fix memory code for old macOS versions (#389)
* fix memory code for old macOS versions

* don't print memory at all here, instead of just printing zeros
2025-02-15 10:48:21 +08:00
Ameyama Izumi 5ec7b4f01a [+] Add Kessoku Band flag for meme (#384)
* [+] Add Kessoku Band flag for meme

* [F] Fix key

---------

Co-authored-by: Azalea <22280294+hykilpikonna@users.noreply.github.com>
2025-01-19 05:55:17 +08:00
azalea f38b754783 [F] Port fix #383 to rust 2025-01-18 16:51:17 -05:00
jjanzen 211beb7b6a Limit permutations to 1000 (#383)
limit permutations to 1000

Co-authored-by: jjanzen <jjanzen@jjanzen.ca>
2025-01-19 03:08:12 +08:00
azalea 02c64ef281 [F] Fix #379 2025-01-06 04:53:25 -05:00
azalea 4e9c510240 [F] Fix #378 2025-01-06 04:47:46 -05:00
zenobit d6b559fb40 [F] am count packages fix (#375) 2025-01-04 03:16:00 +08:00
azalea 9806ced8b2 [F] Fix windows build 2024-12-22 08:25:13 -05:00
azalea 982ac52c69 [O] Better pathfinding 2024-12-22 08:14:24 -05:00
azalea f9c9a92048 [O] Write neofetch file instead of finding path 2024-12-22 06:56:55 -05:00
azalea b8e5dadd78 [O] Use include_str instead of hardcoding 2024-12-22 06:38:40 -05:00
azalea 287921439d [U] Update readme 2024-12-22 06:34:09 -05:00
azalea e8e0a58e4b [U] Update builder script 2024-12-22 06:23:55 -05:00
azalea 3a80670491 [F] Fix version detection 2024-12-22 06:23:17 -05:00
azalea 8cc06c02b5 [F] Fix entry 2024-12-22 06:19:24 -05:00
azalea 48f52be2d3 [F] Fix windows build 2024-12-22 05:55:38 -05:00
azalea b009342d78 [F] Fix osx build strip issue 2024-12-22 05:53:46 -05:00
azalea 8e4b57e33a [F] Fix rust musl build segfault 2024-12-22 05:53:28 -05:00
azalea 9a9101a9a3 [+] Rust entrypoint in pypi 2024-12-22 04:59:40 -05:00
azalea 90d12d9385 [+] OSXCross rust build 2024-12-22 04:47:16 -05:00
azalea ca74acbafb [O] Build rust using musl static 2024-12-22 04:22:52 -05:00
azalea afd9ad33d4 [F] Fix rust builder 2024-12-22 01:39:13 -05:00
azalea cafa3ff752 [+] Rust builder? 2024-12-22 01:09:44 -05:00
azalea a0db219ba3 [+] Rust launcher 2024-12-22 00:27:22 -05:00
azalea 89cced9950 [F] Fix rust windows build 2024-12-22 00:27:10 -05:00
azalea 1ff4b12f19 [F] Fix windows 2024-12-22 00:26:55 -05:00
azalea 4f8ddd1c22 [O] Shorten code for #372 2024-12-21 21:40:06 -05:00
azalea 7a42824883 [PR] #370 from teohhanhui/rust-catchup
Port missed changes from the Python version
2024-12-21 21:31:01 -05:00
azalea 916f7274df [PR] #372 from ctcl-bregis: n/p for next/previous shorthand
One-letter page selection and page number roll over on Rust version
2024-12-21 21:30:27 -05:00
ctcl-bregis 79fc190013 shorthand next and prev and page number roll over 2024-12-16 19:51:46 -05:00
azalea e78d95e573 [U] Bump version to 2.0.0 2024-12-10 08:56:33 -05:00
azalea 943503004d [+] Add print_font_logo CLI option 2024-12-10 08:54:32 -05:00
azalea deb2bddbe1 [+] Font logo 2024-12-10 08:54:10 -05:00
azalea 15fabbdb1a [PR] #371 from bertronika/fast-rpm: Speed-up rpm total package count
neofetch: Speed-up rpm total package count
2024-12-10 04:04:16 -05:00
Nejc Bertoncelj df71b0132a neofetch: Speed-up rpm total package count
Skipping package digest and signature verification yields a significant
speed-up when counting total packages.

Idea taken from rpm's bash-completion script.
2024-11-30 20:27:13 +01:00
Teoh Han Hui 191995c0fe Fix preset name mismatch ("throatlozenges") 2024-11-30 09:37:17 +08:00
Teoh Han Hui 6558cf39f1 Apply fix from clippy (Rust 1.83) 2024-11-30 08:52:31 +08:00
Teoh Han Hui 99d652383a Add random preset option (port of 1211dc6) 2024-11-30 08:52:31 +08:00
Teoh Han Hui e78ae3233c Add a higher-contrast version of pangender flag (port of f4d6b75) 2024-11-30 08:52:31 +08:00
Teoh Han Hui a60dd35169 Add caninekin flag for all the dogs (port of #318) 2024-11-30 08:52:30 +08:00
Teoh Han Hui ccc8121550 Add queer subculture flags (port of #302) 2024-11-30 08:52:19 +08:00
azalea bb260890c5 Merge pull request #369 from LS-KR/master
[+] Add watermelon cream lozenges flag for meme
2024-11-22 02:46:58 -05:00
Elihuso Quigley 057b3b53e2 [F] Fix build 2024-11-22 15:06:13 +08:00
Elihuso Quigley 3a96fe8561 [+] Add watermelon cream lozenges for meme 2024-11-22 15:00:42 +08:00
azalea 1b3b5ca883 [PR] #365 from bittin: Add Slackware credits
Update README.md
2024-11-11 21:15:10 -05:00
Luna Jernberg 53f7e3fcdb Update README.md
Add more Slackware credits
2024-11-10 15:06:19 +01:00
Cat e630b7837f [F] Update openSUSE logos, add Slowroll 2024-10-29 13:46:19 -07:00
Lubos Kocman aa065fe910 openSUSE distro refresh
* Add openSUSE Slowroll
* Updated logos for openSUSE Leap and Tumbleweed
  gh#openSUSE/branding#154 gh#openSUSE/branding#160
* Update neofetch accordingly to gh#openSUSE/neofetch fork
2024-10-29 14:30:28 +01:00
azalea 28b365d418 [PR] #361 from getchoo-contrib: Run neowofetch directly on unix
fix: run `neowofetch` directly on unix
2024-10-25 15:05:28 -07:00
seth 4ea0a4a9dc fix: run neowofetch directly on unix
The script should contain a shebang, so no need to spawn a subshell for
it
2024-10-24 15:21:41 -04:00
azalea b5b49ecbc0 [PR] #317 from rust-malaysia: RIIR
Rewrite it in Rust
2024-10-23 14:52:35 -07:00
Teoh Han Hui 0ca67a5608 Set foreground colors for the newly added distro logos 2024-08-30 00:31:18 +08:00
Teoh Han Hui b2f7a3cee9 Merge remote-tracking branch 'origin/master' into riir 2024-08-30 00:14:04 +08:00
Teoh Han Hui b33b26625a Merge pull request #44 from BKasin/riir
Add missing upstream flags
2024-08-30 00:06:14 +08:00
Bailey Kasin e04ef3b0fd Add note of where polyam colours were pulled from
Co-authored-by: Teoh Han Hui <teohhanhui@gmail.com>
2024-08-29 01:27:22 -07:00
Bailey Kasin a51a19658a Add missing upstream flags
Signed-off-by: Bailey Kasin <baileykasin@gmail.com>
2024-08-28 23:37:20 -07:00
Teoh Han Hui 474f744bc6 Default to fastfetch if it is installed
Otherwise fall back to neofetch.
2024-08-19 00:58:24 +08:00
Teoh Han Hui 7c139595a6 Use fastfetch as fallback on platforms other than *nix
* Use fastfetch on Android (Termux)
2024-08-10 14:37:17 +08:00
Teoh Han Hui ebd253523b Merge pull request #43 from teohhanhui/riir
Use "alternate screen" for Pride month animation
2024-07-30 21:27:14 +08:00
Teoh Han Hui 13e29325b9 Use "alternate screen" for Pride month animation
This removes the need to clear screen at each frame, which fixes the
screen flickering.
2024-07-30 21:23:40 +08:00
Teoh Han Hui 00369355e6 Fix vertical fore-back coloring when span is empty
e.g. back-to-back color codes, or color code at end of line
2024-07-28 06:40:46 +08:00
Teoh Han Hui 2865ee7131 Add more fg distros 2024-07-28 05:42:47 +08:00
Teoh Han Hui faa7ab240f Merge pull request #42 from teohhanhui/riir
Track only fg color indices in ascii art; the rest are bg colors
2024-07-28 02:43:57 +08:00
Teoh Han Hui bcdc720d8a Track only fg color indices in ascii art; the rest are bg colors
* Fix vertical fore-back coloring when there are non-ASCII chars
* Allow 0-width / 0-height ascii art (again)

Co-authored-by: Luna <contact@luna.computer>
2024-07-28 02:35:32 +08:00
Teoh Han Hui 52844b55ad Merge pull request #41 from teohhanhui/riir
Put fore-back color indices in ascii art structs
2024-07-27 04:41:44 +08:00
Teoh Han Hui 6ccf0d9c31 Put fore-back color indices in ascii art structs
* Support multiple color indices for fore-back
2024-07-27 04:36:05 +08:00
Teoh Han Hui 2f18cc2826 Merge pull request #40 from teohhanhui/riir
Use structs to represent ascii art in different states of processing
2024-07-27 01:39:52 +08:00
Teoh Han Hui 4c72ed6d79 Use structs to represent ascii art in different states of processing
Stop passing raw strings around, as that's error prone.
2024-07-27 01:35:04 +08:00
Teoh Han Hui 55b0eed88d Merge pull request #39 from teohhanhui/riir
Use checked arithmetic
2024-07-26 11:31:00 +08:00
Teoh Han Hui 28f98a40fb Use checked arithmetic
This hurts readability, but should be worth it for correctness...
2024-07-26 11:09:35 +08:00
Teoh Han Hui a5913cc3d7 Fix nits 2024-07-25 06:36:43 +08:00
Teoh Han Hui f976781997 Merge pull request #38 from teohhanhui/riir
Remove `--c-overlay` option
2024-07-25 05:03:34 +08:00
Teoh Han Hui cbe176ff4e Remove --c-overlay option
It no longer serves any purpose.
2024-07-25 05:00:12 +08:00
Teoh Han Hui fe7ed923ea Merge pull request #37 from teohhanhui/riir
Fix all the line iterators
2024-07-25 03:55:46 +08:00
Teoh Han Hui c2795125d2 Fix all the line iterators
Use `str::lines` for proper line splitting.
2024-07-25 03:53:40 +08:00
Teoh Han Hui 370b56c1db Merge pull request #35 from 1337isnot1337/riir
created --june pride month animation! still needs some work. first PR be kind pls :3
2024-07-24 22:39:03 +08:00
Teoh Han Hui f47561c947 Clean up pride_month.rs 2024-07-24 22:36:10 +08:00
Cinna Davis 89e5342614 FIxed most bugs, should be working fine now? 2024-07-24 21:57:54 +08:00
Cinna Davis 4e3ec2604a created --june pride month animation! still needs some work though. changed cargo toml a bit too and I might have done something wrong. 2024-07-24 21:57:53 +08:00
Teoh Han Hui a7c277cb76 Move create_config back to hyfetch.rs
p/s: sorry...
2024-07-22 17:40:29 +08:00
Teoh Han Hui c2586212d6 Remove fastfetch-old backend
fastfetch version < 1.8.0 is very old at this point. Even EPEL 8 is
on 1.12.2
2024-07-22 16:44:53 +08:00
Teoh Han Hui 632296dbb4 Merge pull request #34 from teohhanhui/riir
Add `macchina` backend
2024-07-22 02:09:00 +08:00
Teoh Han Hui fe7d902dac Add macchina backend
* Remove `qwqfetch` TODOs as that project is unmaintained
2024-07-21 23:56:46 +08:00
Teoh Han Hui c7295ebed8 Add debug in recolor_ascii to see fields from tracing::instrument 2024-07-21 00:54:13 +08:00
Teoh Han Hui bdd03a34af Merge remote-tracking branch 'origin/master' into riir 2024-07-20 23:52:36 +08:00
Teoh Han Hui 683d1fc2ec Move create_config to Config::create_config 2024-07-20 23:45:57 +08:00
Teoh Han Hui c80a6185fc Handle name clashes in ascii distro patterns 2024-07-20 19:17:25 +08:00
Teoh Han Hui 2a8cd48674 Merge pull request #33 from teohhanhui/riir
Implement select_backend and save config
2024-07-20 17:57:13 +08:00
Teoh Han Hui 13ead437ec Implement select_backend and save config 2024-07-20 17:51:50 +08:00
Teoh Han Hui 8edb50c4d8 Merge pull request #32 from teohhanhui/riir
Support foreground-background for vertical color alignment
2024-07-19 23:34:25 +08:00
Teoh Han Hui 14ff66b836 Support foreground-background for vertical color alignment 2024-07-19 23:29:42 +08:00
Teoh Han Hui 0c7c43d71c Merge pull request #31 from teohhanhui/riir
Fix stray newlines
2024-07-19 01:26:30 +08:00
Teoh Han Hui 2613471978 Merge pull request #28 from luna-1024/riir-fix-escaped-backslashes
Fix escaped backslashes in output ascii art by unescaping them
2024-07-19 01:23:31 +08:00
Teoh Han Hui a558cf7ac9 Fix stray newlines 2024-07-19 01:19:42 +08:00
Luna cd1858134c Fix escaped backslashes in output ascii art by unescaping them 2024-07-18 10:12:03 -07:00
Teoh Han Hui 0a31cd0817 Merge pull request #29 from teohhanhui/riir
Use rainbow preset for color testing
2024-07-18 08:09:35 +08:00
Teoh Han Hui e25684cfd8 Use rainbow preset for color testing 2024-07-18 08:07:23 +08:00
Teoh Han Hui de43bf74a9 Merge pull request #27 from teohhanhui/riir
Implement "check term size" and select_color_mode
2024-07-17 22:43:31 +08:00
Teoh Han Hui d8f3302b89 Implement "check term size" and select_color_mode 2024-07-17 22:38:29 +08:00
Teoh Han Hui 5517283fb2 Merge pull request #26 from teohhanhui/riir
Implement selection of color alignment
2024-07-16 10:07:44 +08:00
Teoh Han Hui 66b08f7a2e Implement selection of color alignment 2024-07-16 10:03:49 +08:00
Teoh Han Hui 19e3b382b4 Use same-file only on Windows 2024-07-14 22:39:42 +08:00
Teoh Han Hui e10bdac351 Merge pull request #25 from springmaple/riir
Fix git-bash finding in Windows not working properly
2024-07-14 19:58:19 +08:00
SpringMaple 6e28482706 Fix git-bash finding in Windows not working properly 2024-07-14 19:34:58 +08:00
Teoh Han Hui 198742e15b Merge pull request #24 from teohhanhui/riir
Implement select_lightness
2024-07-14 05:02:33 +08:00
Teoh Han Hui a5de091883 Implement select_lightness 2024-07-14 04:59:37 +08:00
Teoh Han Hui da5f6ef655 Switch to terminal-colorsaurus 2024-07-13 23:37:41 +08:00
Teoh Han Hui dbecd3d705 Merge pull request #23 from teohhanhui/riir
Attempt to better support fallback executable paths on Windows
2024-07-13 16:58:25 +08:00
Teoh Han Hui e2d538df1f Attempt to better support fallback executable paths on Windows 2024-07-13 16:52:19 +08:00
Teoh Han Hui 8514629960 Merge pull request #22 from teohhanhui/riir
Use selected backend to get distro name
2024-07-13 04:12:31 +08:00
Teoh Han Hui d11f6f0a9f Use selected backend to get distro name 2024-07-13 04:07:31 +08:00
Teoh Han Hui d79196a7fc Merge pull request #21 from teohhanhui/riir
Prompt for preset selection
2024-07-12 22:44:32 +08:00
Teoh Han Hui 114de9be12 Prompt for preset selection 2024-07-12 22:42:58 +08:00
Teoh Han Hui 4861744b50 Merge pull request #20 from teohhanhui/riir
[WIP] Implement `--config` option
2024-07-12 19:17:06 +08:00
Teoh Han Hui 66b57cfea6 [WIP] Implement --config option 2024-07-12 19:06:13 +08:00
Teoh Han Hui ca583bddcb Merge pull request #19 from teohhanhui/riir
Support fastfetch backend
2024-07-11 22:28:09 +08:00
Teoh Han Hui 965b009bc7 Support fastfetch backend 2024-07-11 19:44:07 +08:00
Teoh Han Hui 3cc26d7dd9 Fix wrong line length counting of ascii art
Count Unicode grapheme clusters instead of bytes.
2024-07-11 00:35:11 +08:00
Teoh Han Hui 85a85ad4d3 Merge pull request #18 from teohhanhui/riir
Tie fore-back to distro ascii instead of config
2024-07-10 23:58:43 +08:00
Teoh Han Hui f3b1840e92 Tie fore-back to distro ascii instead of config 2024-07-10 23:53:39 +08:00
Teoh Han Hui d773c69918 Merge pull request #17 from teohhanhui/riir
Run neofetch using git bash on Windows
2024-07-09 01:36:51 +08:00
Teoh Han Hui 290da7c363 Fix path normalization on Windows 2024-07-09 01:35:31 +08:00
Teoh Han Hui fa66bb486b Run neofetch using git bash on Windows 2024-07-08 15:50:12 +08:00
Teoh Han Hui 1239e86d1a Merge pull request #16 from teohhanhui/riir
Finish implementation of recolor_ascii
2024-07-08 03:46:13 +08:00
Teoh Han Hui 66491a4e73 Implement recoloring of ascii art using "custom" colors 2024-07-08 03:40:58 +08:00
Teoh Han Hui c100ca52a6 Implement recoloring of ascii art that don't use fore-back 2024-07-08 02:04:45 +08:00
Teoh Han Hui 46b618252f Merge pull request #15 from teohhanhui/riir
Implement recolored ascii output
2024-07-07 04:47:25 +08:00
Teoh Han Hui be70233b03 Implement recolored ascii output 2024-07-07 04:39:41 +08:00
Teoh Han Hui ff46c8f4ca Merge pull request #14 from teohhanhui/riir
Add more scaffolding
2024-07-03 23:57:18 +08:00
Teoh Han Hui 59213687fb Add more scaffolding 2024-07-03 23:53:25 +08:00
Teoh Han Hui 475b10ba07 Merge pull request #13 from teohhanhui/riir
Apply lightness to preset color profile
2024-07-03 05:51:11 +08:00
Teoh Han Hui b0737a33ba Apply lightness to preset color profile 2024-07-03 05:48:51 +08:00
Teoh Han Hui 3c355f54f2 Merge pull request #12 from teohhanhui/riir
Parse config
2024-07-01 04:08:10 +08:00
Teoh Han Hui 86e442b8a4 Parse config
Co-authored-by: Chiew Yan Wei <chiewyanwei@gmail.com>
Co-authored-by: Tan Chee Keong <tanck2005@gmail.com>
2024-07-01 04:03:56 +08:00
Teoh Han Hui a0856aa4f5 Merge pull request #11 from teohhanhui/riir
Implement neofetch_util functions
2024-06-29 21:40:11 +08:00
Teoh Han Hui d1d99daa87 Implement neofetch_util functions
Co-authored-by: Chiew Yan Wei <chiewyanwei@gmail.com>
2024-06-29 21:36:17 +08:00
Teoh Han Hui 4f1efbc699 Merge pull request #8 from teohhanhui/riir
Clean up build.rs
2024-06-29 17:39:12 +08:00
Teoh Han Hui c1b30918e9 Clean up build.rs 2024-06-29 17:37:22 +08:00
Teoh Han Hui 517dde43cc Merge pull request #5 from Venketaramana/riir
[rust] implement list_distros match_condition method in rust
2024-06-29 16:28:10 +08:00
Venket 955046c959 [rust] implement list_distros match_condition method in rust 2024-06-29 16:01:50 +08:00
Teoh Han Hui 274440e9db Merge pull request #4 from teohhanhui/riir
Add some more scaffolding
2024-06-29 15:03:20 +08:00
Teoh Han Hui fd413caba4 Add some more scaffolding 2024-06-29 14:59:05 +08:00
Teoh Han Hui 474c339599 Set default-run in Cargo.toml 2024-06-29 13:09:39 +08:00
Teoh Han Hui 927803549e Merge pull request #3 from teohhanhui/riir
Generate distros.rs on build
2024-06-29 10:40:51 +08:00
Teoh Han Hui 11601933f9 Generate distros.rs on build 2024-06-29 10:36:55 +08:00
Teoh Han Hui 7c874a8e9a Enable color output in bpaf 2024-06-28 05:53:00 +08:00
Teoh Han Hui 6e0e3fd18a Merge pull request #2 from teohhanhui/riir
Get distro name from neofetch
2024-06-28 05:17:12 +08:00
Teoh Han Hui 974c4514b2 Fix distro overriding 2024-06-28 05:09:32 +08:00
Teoh Han Hui e3b220a248 Switch to tracing 2024-06-28 01:13:42 +08:00
Teoh Han Hui ae1f6a2b3f Get distro name from neofetch 2024-06-27 22:25:49 +08:00
Teoh Han Hui 290445110d Parse the rest of the CLI options 2024-06-27 16:31:39 +08:00
Teoh Han Hui 4f9e6deb90 Add xenogender preset 2024-06-27 01:24:43 +08:00
Teoh Han Hui 3b94b94528 Merge remote-tracking branch 'origin/master' into riir 2024-06-27 01:22:15 +08:00
Teoh Han Hui 39b44ad041 Merge pull request #1 from teohhanhui/riir
Parse CLI options, including presets
2024-06-27 01:17:38 +08:00
Teoh Han Hui bf2c3c51d4 Parse CLI options, including presets 2024-06-27 01:13:40 +08:00
54 changed files with 7564 additions and 538 deletions
+2
View File
@@ -0,0 +1,2 @@
[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }
+71
View File
@@ -0,0 +1,71 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 4
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.csv]
trim_trailing_whitespace = false
[*.js]
indent_style = space
indent_size = 4
[*.json]
indent_style = space
indent_size = 4
[*.md]
trim_trailing_whitespace = false
[*.py]
indent_style = space
indent_size = 4
[*.rs]
indent_style = space
indent_size = 4
[*.sh]
indent_style = space
indent_size = 4
[*.toml]
indent_style = space
indent_size = 4
[*.{yaml,yml}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = false
[.github/{actions,workflows}/**/*.yml]
indent_style = space
indent_size = 2
[Cargo.toml]
indent_style = space
indent_size = 4
[Makefile]
indent_style = tab
indent_size = 4
[neofetch]
indent_style = space
indent_size = 4
[package.json]
indent_style = space
indent_size = 2
+17 -2
View File
@@ -1,2 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
* text=auto eol=lf
*.csv text eol=lf
*.js text eol=lf
*.json text eol=lf
*.md text eol=lf
*.py text eol=lf
*.rs text eol=lf
*.sh text eol=lf
*.toml text eol=lf
*.txt text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
Cargo.lock text eol=lf merge=binary
Makefile text eol=lf
MANIFEST.in text eol=lf
neofetch text eol=lf
+12 -1
View File
@@ -120,4 +120,15 @@ webhook-log
start_moderation.sh
gh_moderator.toml
moderator-data
hyfetch/git
hyfetch/git
build
dist
target
.vscode
.idea
*.iml
*.egg-info
__pycache__
*.pyc
*.pyo
*.pyd
Generated
+1216
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
[workspace]
resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "2.0.2"
authors = ["Azalea Gui <azalea@hydev.org>"]
edition = "2021"
rust-version = "1.75.0"
description = "Neofetch with LGBTQ+ pride flags!"
repository = "https://github.com/hykilpikonna/hyfetch"
license = "MIT"
[workspace.dependencies]
aho-corasick = { version = "1.1.3", default-features = false }
ansi_colours = { version = "1.2.2", default-features = false }
anstream = { version = "0.6.14", default-features = false }
anyhow = { version = "1.0.86", default-features = false }
bpaf = { version = "0.9.12", default-features = false }
crossterm = { version = "0.27.0", default-features = false }
deranged = { version = "0.3.11", default-features = false }
directories = { version = "5.0.1", default-features = false }
enable-ansi-support = { version = "0.2.1", default-features = false }
enterpolation = { version = "0.2.1", default-features = false }
fastrand = { version = "2.1.0", default-features = false }
indexmap = { version = "2.2.6", default-features = false }
itertools = { version = "0.13.0", default-features = false }
normpath = { version = "1.2.0", default-features = false }
palette = { version = "0.7.6", default-features = false }
regex = { version = "1.10.5", default-features = false }
same-file = { version = "1.0.6", default-features = false }
serde = { version = "1.0.203", default-features = false }
serde_json = { version = "1.0.118", default-features = false }
serde_path_to_error = { version = "0.1.16", default-features = false }
shell-words = { version = "1.1.0", default-features = false }
strum = { version = "0.26.3", default-features = false }
supports-color = { version = "3.0.0", default-features = false }
tempfile = { version = "3.10.1", default-features = false }
terminal-colorsaurus = { version = "0.4.3", default-features = false }
terminal_size = { version = "0.3.0", default-features = false }
thiserror = { version = "1.0.61", default-features = false }
time = { version = "0.3.36", default-features = false }
toml_edit = { version = "0.22.16", default-features = false }
tracing = { version = "0.1.40", default-features = false }
tracing-subscriber = { version = "0.3.18", default-features = false }
unicode-normalization = { version = "0.1.23", default-features = false }
unicode-segmentation = { version = "1.11.0", default-features = false }
which = { version = "7.0.1", default-features = false }
[workspace.lints.clippy]
arithmetic_side_effects = "warn"
+68 -11
View File
@@ -16,16 +16,17 @@ This repo also serves as an updated version of the original `neofetch` since the
## Installation
### Method 1: Install using Python pip (Recommended)
### Method 1: Install using pip (Recommended)
Install Python >= 3.7 first. Then, just do:
Even though hyfetch has been rewritten in Rust since 2.0.0, we also published PyPI packages with prebuilt rust binaries to allow seamless transition from the Python version. You can install the rust binaries using pip:
```sh
pip install wheel
pip install -U hyfetch
# or
pipx install hyfetch
```
If a rust binary is not available for your platform (e.g. riscv64), it will automatically fall back to the v1.99.0 Python version.
### Method 2: Install using system package manager
Currently, these distributions have existing packages for HyFetch:
@@ -36,7 +37,7 @@ Currently, these distributions have existing packages for HyFetch:
* Nix: `nix-env -i hyfetch` (Thanks to [@YisuiDenghua](https://github.com/YisuiDenghua))
* Nix Profile: `nix profile install nixpkgs#hyfetch`
* Guix: `guix install hyfetch` (Thanks to [@WammKD](https://github.com/WammKD))
* Slackware: `sbopkg -b hyfetch` [Slackbuild](https://slackbuilds.org/repository/15.0/desktop/hyfetch/?search=hyfetch) (Thanks to [@bittin](https://github.com/bittin) and Urchlay)
* Slackware: `sbopkg -b hyfetch` [Slackbuild](https://slackbuilds.org/repository/15.0/desktop/hyfetch/?search=hyfetch) (Thanks to [@bittin](https://github.com/bittin) , willysr (https://github.com/willysr), jebrhansen and Urchlay)
* Homebrew: `brew install hyfetch` (Thanks to [@catumin](https://github.com/catumin) and [@osalbahr](https://github.com/osalbahr))
* openSUSE Tumbleweed: `zypper in python311-hyfetch` (Thanks to [@catumin](https://github.com/catumin))
* Gentoo: `emerge --ask app-misc/hyfetch` (Thanks to [@catumin](https://github.com/catumin))
@@ -44,16 +45,18 @@ Currently, these distributions have existing packages for HyFetch:
[![Packaging status](https://repology.org/badge/vertical-allrepos/hyfetch.svg?columns=4&exclude_unsupported=1)](https://repology.org/project/hyfetch/versions)
### Method 3: Install the latest developmental version using git
### Method 3: Install using Cargo
Install Python >= 3.7 first. Then run the following commands:
Since hyfetch has been rewritten in Rust since 2.0.0, you can now install it using Cargo:
```sh
git clone https://github.com/hykilpikonna/hyfetch.git
cd hyfetch
pip install .
cargo install hyfetch
```
> [!WARNING]
> If you install using Cargo, you would need to manually install dependencies like fastfetch if you want to use it as the backend, and Git Bash if you're on Windows.
>
> The PyPI package has all the dependencies bundled, so it's recommended to use that instead.
## Usage
@@ -121,11 +124,65 @@ Updates to `neowofetch` begins with the emoji 🖼️
Note: You can install the latest nightly version by using:
```sh
pip install git+https://github.com/hykilpikonna/hyfetch.git@master
cargo install --git https://github.com/hykilpikonna/hyfetch
```
<!-- CHANGELOG STARTS HERE --->
### 2.0.2
This is a small patch release that adds more flags and fixes some bugs from the recent Rust rewrite.
* 🏳️‍🌈 **New Flags**: Added new pride flags for Nullflux ([#397](https://github.com/hykilpikonna/hyfetch/pull/397)), Hypergender ([#422](https://github.com/hykilpikonna/hyfetch/pull/422)), Fictosexual ([#394](https://github.com/hykilpikonna/hyfetch/pull/394)), and Adipophilia ([#424](https://github.com/hykilpikonna/hyfetch/pull/424)).
* 🐛 **Bug Fixes**:
* Fixed a critical bug where custom `fastfetch` configurations could break hyfetch ([#420](https://github.com/hykilpikonna/hyfetch/pull/420)).
* Corrected the `nixos_small` ASCII logo to display properly ([#421](https://github.com/hykilpikonna/hyfetch/pull/421)).
* 🔧 **Improvements**:
* Improved a warning message for better clarity ([#419](https://github.com/hykilpikonna/hyfetch/issues/419)).
### 2.0.1
(changelog is generated by Gemini from commit history)
**🦀 The Rust Rewrite!**
This is a massive update, rewriting the entire hyfetch core from Python to Rust for significantly improved performance, reliability, and maintainability. A huge thank you to **@teohhanhui** and all the contributors who made this possible!
* 🚀 **Complete Rewrite in Rust**: The entire codebase has been ported to Rust, resulting in major performance improvements and a smaller binary size.
* ⚙️ **New Backends**: In addition to neofetch and fastfetch, hyfetch now supports **macchina** as a backend option.
* 🌈 **Improved June Animation**: The `--june` pride month animation is now smoother, flicker-free ([#408](https://github.com/hykilpikonna/hyfetch/pull/408)).
* 🪟 **Native Windows Support**: The Rust version provides much-improved support for Windows.
**✨ Features & Enhancements**
* 🎨 **Automatic Theme Detection**: Hyfetch can now automatically detect your terminal's light/dark mode (when auto_detect_light_dark is true) ([#380](https://github.com/hykilpikonna/hyfetch/pull/380)).
* ✍️ **Font Logos**: Added a new feature to display a logo created from your system's font.
* 🌐 **Cloudflare for Public IP**: Now uses Cloudflare to fetch the public IP address, with a fallback option ([#416](https://github.com/hykilpikonna/hyfetch/pull/416)).
* 🖼️ **Interactive Logo Selection**: An interactive setup step now allows you to choose between default and small logo variants for your distro ([#392](https://github.com/hykilpikonna/hyfetch/pull/392)).
* 🏳️‍🌈 **New Pride Flags**: Added flags for Cisgender ([#386](https://github.com/hykilpikonna/hyfetch/pull/386)), Kessoku Band ([#384](https://github.com/hykilpikonna/hyfetch/pull/384)), and Watermelon Cream Lozenges ([#369](https://github.com/hykilpikonna/hyfetch/pull/369)) for memes.
* 📄 **Page Navigation**: Added `n` and `p` as shorthands for next/previous page selection and implemented page number roll-over ([#372](https://github.com/hykilpikonna/hyfetch/pull/372)).
**🖼️ New & Updated Distro Support**
* **Added Bazzite** ([#406](https://github.com/hykilpikonna/hyfetch/pull/406))
* **Added Rhino Linux Logo** ([#407](https://github.com/hykilpikonna/hyfetch/pull/407))
* **Updated openSUSE Logos & Added Slowroll**
* **Updated KSLinux Detection** ([#395](https://github.com/hykilpikonna/hyfetch/pull/395))
**🐛 Bug Fixes**
* **macOS**: Fixed CPU and memory fetching on older macOS versions like Leopard and Tiger ([#387](https://github.com/hykilpikonna/hyfetch/pull/387), [#389](https://github.com/hykilpikonna/hyfetch/pull/389)).
* **Packaging**: Resolved several issues with `cargo publish` to ensure reliable package deployment ([#405](https://github.com/hykilpikonna/hyfetch/pull/405)).
* **Performance**: Sped up RPM package counting by skipping digest and signature verification ([#371](https://github.com/hykilpikonna/hyfetch/pull/371)).
* **Animation**: Updated logic to ensure the pride month animation is always shown when `--june` is explicitly used ([#411](https://github.com/hykilpikonna/hyfetch/pull/411)).
* **General**: Fixed numerous bugs related to builds, path detection, cache handling, and distro name detection.
* **Formatting**: Fixed an unwanted space in the Python version output ([#401](https://github.com/hykilpikonna/hyfetch/pull/401)).
**🔧 Maintenance**
* **Documentation**: Updated the README with additional credits for Slackware contributors ([#365](https://github.com/hykilpikonna/hyfetch/pull/365)).
* **Build System**: Overhauled the build scripts for Rust, including support for `musl` static builds and cross-compilation for macOS and Windows.
### 1.99.0
This version would be the last version of HyFetch on Python as we migrate to Rust (Huge thanks to everyone on [#317](https://github.com/hykilpikonna/hyfetch/pull/317)!). It will also be an effort to start a transition that phases out the neowofetch/neofetch backend in favor of FastFetch, since the time needed to maintain the NF backend currently exceed our capacity. If you are willing to help maintaining it, please let us know!
+62
View File
@@ -0,0 +1,62 @@
[package]
name = "hyfetch"
version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
description = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
default-run = "hyfetch"
[dependencies]
aho-corasick = { workspace = true, features = ["perf-literal", "std"] }
ansi_colours = { workspace = true, features = [] }
anstream = { workspace = true, features = [], optional = true }
anyhow = { workspace = true, features = ["std"] }
bpaf = { workspace = true, features = [] }
crossterm = { workspace = true, features = [] }
deranged = { workspace = true, features = ["serde", "std"] }
directories = { workspace = true, features = [] }
enterpolation = { workspace = true, features = ["bspline", "std"] }
fastrand = { workspace = true, features = ["std"] }
indexmap = { workspace = true, features = ["serde", "std"] }
itertools = { workspace = true, features = ["use_std"] }
palette = { workspace = true, features = ["std"] }
serde = { workspace = true, features = ["derive", "std"] }
serde_json = { workspace = true, features = ["std"] }
serde_path_to_error = { workspace = true, features = [] }
shell-words = { workspace = true, features = ["std"] }
strum = { workspace = true, features = ["derive", "std"] }
supports-color = { workspace = true, features = [] }
tempfile = { workspace = true, features = [] }
terminal-colorsaurus = { workspace = true, features = [] }
terminal_size = { workspace = true, features = [] }
thiserror = { workspace = true, features = [] }
time = { workspace = true, features = ["local-offset", "std"] }
toml_edit = { workspace = true, features = [], optional = true }
tracing = { workspace = true, features = ["attributes", "std"] }
tracing-subscriber = { workspace = true, features = ["ansi", "fmt", "smallvec", "std", "tracing-log"] }
unicode-segmentation = { workspace = true, features = [] }
which = { workspace = true, features = [] }
[build-dependencies]
indexmap = { workspace = true, features = ["std"] }
regex = { workspace = true, features = ["perf", "std", "unicode"] }
unicode-normalization = { workspace = true, features = ["std"] }
fs_extra = "1.3.0"
[target.'cfg(windows)'.dependencies]
enable-ansi-support = { workspace = true, features = [] }
normpath = { workspace = true, features = [] }
same-file = { workspace = true, features = [] }
crossterm = { workspace = true, features = ["windows"] }
[features]
default = ["autocomplete", "color", "macchina"]
autocomplete = ["bpaf/autocomplete"]
color = ["bpaf/dull-color"]
macchina = ["dep:anstream", "dep:toml_edit", "toml_edit/display"]
[lints]
workspace = true
+273
View File
@@ -0,0 +1,273 @@
use std::env;
use std::fmt::Write as _;
use std::fs;
use std::path::{Path, PathBuf};
use fs_extra::dir::{CopyOptions};
use indexmap::IndexMap;
use regex::Regex;
use unicode_normalization::UnicodeNormalization as _;
#[derive(Debug)]
struct AsciiDistro {
pattern: String,
art: String,
}
impl AsciiDistro {
fn friendly_name(&self) -> String {
self.pattern
.split('|')
.next()
.expect("invalid distro pattern")
.trim_matches(|c: char| c.is_ascii_punctuation() || c == ' ')
.replace(['"', '*'], "")
}
}
fn anything_that_exist(paths: &[&Path]) -> Option<PathBuf> {
paths.iter().copied().find(|p| p.exists()).map(Path::to_path_buf)
}
fn main() {
// Path hack to make file paths work in both workspace and manifest directory
let dir = PathBuf::from(env::var_os("CARGO_WORKSPACE_DIR").unwrap_or_else(|| env::var_os("CARGO_MANIFEST_DIR").unwrap()));
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
for file in &["neofetch", "hyfetch"] {
let src = anything_that_exist(&[
&dir.join(file),
&dir.join("../../").join(file),
]).expect("couldn't find neofetch");
let dst = o.join(file);
println!("cargo:rerun-if-changed={}", src.display());
// Copy either file or directory
if src.is_dir() {
let opt = CopyOptions { overwrite: true, copy_inside: true, ..CopyOptions::default() };
fs_extra::dir::copy(&src, &dst, &opt).expect("Failed to copy directory to OUT_DIR");
}
else { fs::copy(&src, &dst).expect("Failed to copy file to OUT_DIR"); }
}
export_distros(&o.join("neofetch"), &o);
}
fn export_distros<P>(neofetch_path: P, out_path: &Path)
where
P: AsRef<Path>,
{
let distros = parse_ascii_distros(neofetch_path);
let mut variants = IndexMap::with_capacity(distros.len());
for distro in &distros {
let variant = distro
.friendly_name()
.replace(|c: char| c.is_ascii_punctuation() || c == ' ', "_")
.nfc()
.collect::<String>();
if variants.contains_key(&variant) {
let variant_fallback = format!("{variant}_fallback");
if variants.contains_key(&variant_fallback) {
todo!("too many name clashes in ascii distro patterns: {variant}");
}
variants.insert(variant_fallback, distro);
continue;
}
variants.insert(variant, distro);
}
let mut buf = r###"
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum Distro {
"###
.to_owned();
for (variant, AsciiDistro { pattern, .. }) in &variants {
write!(
buf,
r###"
// {pattern})
{variant},
"###,
)
.unwrap();
}
buf.push_str(
r###"
}
impl Distro {
pub fn detect<S>(name: S) -> Option<Self>
where
S: AsRef<str>,
{
let name = name.as_ref().to_lowercase();
"###,
);
for (variant, AsciiDistro { pattern, .. }) in &variants {
let patterns = pattern.split('|').map(|s| s.trim());
let mut conds = Vec::new();
for m in patterns {
let stripped = m.trim_matches(['*', '\'', '"']).to_lowercase();
if stripped.contains(['*', '"']) {
if let Some((prefix, suffix)) = stripped.split_once(r#""*""#) {
conds.push(format!(
r#"name.starts_with("{prefix}") && name.ends_with("{suffix}")"#
));
continue;
}
todo!("cannot properly parse: {m}");
}
// Exact matches
if m.trim_matches('*') == m {
conds.push(format!(r#"name == "{stripped}""#));
continue;
}
// Both sides are *
if m.starts_with('*') && m.ends_with('*') {
conds.push(format!(
r#"name.starts_with("{stripped}") || name.ends_with("{stripped}")"#
));
continue;
}
// Ends with *
if m.ends_with('*') {
conds.push(format!(r#"name.starts_with("{stripped}")"#));
continue;
}
// Starts with *
if m.starts_with('*') {
conds.push(format!(r#"name.ends_with("{stripped}")"#));
continue;
}
}
let condition = conds.join(" || ");
write!(
buf,
r###"
if {condition} {{
return Some(Self::{variant});
}}
"###
)
.unwrap();
}
buf.push_str(
r###"
None
}
pub fn ascii_art(&self) -> &str {
let art = match self {
"###,
);
let quotes = "#".repeat(80);
for (variant, AsciiDistro { art, .. }) in &variants {
write!(
buf,
r###"
Self::{variant} => r{quotes}"
{art}
"{quotes},
"###,
)
.unwrap();
}
buf.push_str(
r###"
};
&art[1..art.len().checked_sub(1).unwrap()]
}
}
"###,
);
fs::write(out_path.join("distros.rs"), buf).expect("couldn't write distros.rs");
}
/// Parses ascii distros from neofetch script.
fn parse_ascii_distros<P>(neofetch_path: P) -> Vec<AsciiDistro>
where
P: AsRef<Path>,
{
let neofetch_path = neofetch_path.as_ref();
let nf = {
let nf = fs::read_to_string(neofetch_path).expect("couldn't read neofetch script");
// Get the content of "get_distro_ascii" function
let (_, nf) = nf
.split_once("get_distro_ascii() {\n")
.expect("couldn't find get_distro_ascii function");
let (nf, _) = nf
.split_once("\n}\n")
.expect("couldn't find end of get_distro_ascii function");
let mut nf = nf.replace('\t', &" ".repeat(4));
// Remove trailing spaces
while nf.contains(" \n") {
nf = nf.replace(" \n", "\n");
}
nf
};
let case_re = Regex::new(r"case .*? in\n").expect("couldn't compile case regex");
let eof_re = Regex::new(r"EOF[ \n]*?;;").expect("couldn't compile eof regex");
// Split by blocks
let mut blocks = Vec::new();
for b in case_re.split(&nf) {
blocks.extend(eof_re.split(b).map(|sub| sub.trim()));
}
// Parse blocks
fn parse_block(block: &str) -> Option<AsciiDistro> {
let (block, art) = block.split_once("'EOF'\n")?;
// Join \
//
// > A <backslash> that is not quoted shall preserve the literal value of the
// > following character, with the exception of a <newline>. If a <newline>
// > follows the <backslash>, the shell shall interpret this as line
// > continuation. The <backslash> and <newline> shall be removed before
// > splitting the input into tokens. Since the escaped <newline> is removed
// > entirely from the input and is not replaced by any white space, it cannot
// > serve as a token separator.
// See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_01
let block = block.replace("\\\n", "");
// Get case pattern
let pattern = block
.split('\n')
.next()
.and_then(|pattern| pattern.trim().strip_suffix(')'))?;
// Unescape backslashes here because backslashes are escaped in neofetch
// for printf
let art = art.replace(r"\\", r"\");
Some(AsciiDistro {
pattern: pattern.to_owned(),
art,
})
}
blocks
.iter()
.filter_map(|block| parse_block(block))
.collect()
}
+402
View File
@@ -0,0 +1,402 @@
use std::borrow::Cow;
use std::fmt::Write as _;
use std::ops::Range;
use aho_corasick::AhoCorasick;
use anyhow::{Context as _, Result};
use indexmap::IndexMap;
use tracing::debug;
use unicode_segmentation::UnicodeSegmentation;
use crate::color_util::{
color, ForegroundBackground, NeofetchAsciiIndexedColor, ToAnsiString as _,
};
use crate::neofetch_util::{
ascii_size, ColorAlignment, NEOFETCH_COLORS_AC, NEOFETCH_COLOR_PATTERNS,
};
use crate::presets::ColorProfile;
use crate::types::{AnsiMode, TerminalTheme};
/// Raw ascii art before any processing.
#[derive(Clone, Debug)]
pub struct RawAsciiArt {
pub asc: String,
pub fg: Vec<NeofetchAsciiIndexedColor>,
}
/// Normalized ascii art where every line has the same width.
#[derive(Clone, Debug)]
pub struct NormalizedAsciiArt {
pub lines: Vec<String>,
pub w: u8,
pub h: u8,
pub fg: Vec<NeofetchAsciiIndexedColor>,
}
/// Recolored ascii art with all color codes replaced.
#[derive(Clone, Debug)]
pub struct RecoloredAsciiArt {
pub lines: Vec<String>,
pub w: u8,
pub h: u8,
}
impl RawAsciiArt {
/// Makes sure every line is the same width.
#[tracing::instrument(level = "debug", skip(self))]
pub fn to_normalized(&self) -> Result<NormalizedAsciiArt> {
debug!("normalize ascii");
let (w, h) = ascii_size(&self.asc).context("failed to get ascii size")?;
let lines = self
.asc
.lines()
.map(|line| {
let (line_w, _) = ascii_size(line).unwrap();
let pad = " ".repeat(usize::from(w.checked_sub(line_w).unwrap()));
format!("{line}{pad}")
})
.collect();
Ok(NormalizedAsciiArt {
lines,
w,
h,
fg: self.fg.clone(),
})
}
}
impl NormalizedAsciiArt {
/// Uses a color alignment to recolor the ascii art.
#[tracing::instrument(level = "debug", skip(self), fields(self.w = self.w, self.h = self.h))]
pub fn to_recolored(
&self,
color_align: &ColorAlignment,
color_profile: &ColorProfile,
color_mode: AnsiMode,
theme: TerminalTheme,
) -> Result<RecoloredAsciiArt> {
debug!("recolor ascii");
if self.lines.is_empty() {
return Ok(RecoloredAsciiArt {
lines: self.lines.clone(),
w: 0,
h: 0,
});
}
let reset = color("&~&*", color_mode).expect("color reset should not be invalid");
let lines = match (color_align, self) {
(ColorAlignment::Horizontal, Self { fg, .. }) => {
let Self { lines, .. } = self
.fill_starting()
.context("failed to fill in starting neofetch color codes")?;
let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
// Replace foreground colors
let asc = {
let asc = lines.join("\n");
let mut replacements = NEOFETCH_COLOR_PATTERNS;
let fg_color = color(
match theme {
TerminalTheme::Light => "&0",
TerminalTheme::Dark => "&f",
},
color_mode,
)
.expect("foreground color should not be invalid");
for &fore in fg {
replacements[usize::from(u8::from(fore)).checked_sub(1).unwrap()] =
&fg_color;
}
ac.replace_all(&asc, &replacements)
};
let lines = asc.lines();
// Add new colors
let lines = {
let ColorProfile { colors } = color_profile
.with_length(self.h.try_into().expect("`h` should not be 0"))
.with_context(|| {
format!("failed to spread color profile to length {h}", h = self.h)
})?;
lines.enumerate().map(move |(i, line)| {
let bg_color =
colors[i].to_ansi_string(color_mode, ForegroundBackground::Foreground);
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
let replacements = [&bg_color; N];
ac.replace_all(line, &replacements)
})
};
// Reset colors at end of each line to prevent color bleeding
lines.map(|line| format!("{line}{reset}")).collect()
},
(ColorAlignment::Vertical, Self { fg, .. }) if !fg.is_empty() => {
if self.w == 0 {
return Ok(RecoloredAsciiArt {
lines: self.lines.clone(),
w: 0,
h: self.h,
});
}
let Self { lines, .. } = self
.fill_starting()
.context("failed to fill in starting neofetch color codes")?;
let color_profile = color_profile
.with_length(self.w.try_into().expect("`w` should not be 0"))
.with_context(|| {
format!("failed to spread color profile to length {w}", w = self.w)
})?;
// Apply colors
let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
lines
.into_iter()
.map(|line| {
let line: &str = line.as_ref();
// `AhoCorasick` operates on bytes; we need to map that back to grapheme
// clusters (i.e. a character as seen on the terminal)
// See https://github.com/BurntSushi/aho-corasick/issues/72#issuecomment-821128859
let byte_idx_to_grapheme_idx: IndexMap<usize, usize> = {
let mut m: IndexMap<_, _> = line
.grapheme_indices(true)
.enumerate()
.map(|(gr_idx, (byte_idx, _))| (byte_idx, gr_idx))
.collect();
// Add an extra entry at the end, to support lookup using exclusive
// range end
m.insert(line.len(), m.len());
m
};
let mut matches = ac.find_iter(line).peekable();
let mut dst = String::new();
let mut offset: u8 = 0;
loop {
let current = matches.next();
let next = matches.peek();
let (neofetch_color_idx, span, done) = match (current, next) {
(Some(m), Some(m_next)) => {
let ai_start = m.start().checked_add(3).unwrap();
let ai_end = m.end().checked_sub(1).unwrap();
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[ai_start..ai_end]
.parse()
.expect("neofetch color index should be valid");
if offset == 0 && m.start() > 0 {
dst.push_str(&line[..m.start()]);
}
offset =
offset.checked_add(u8::try_from(m.len()).unwrap()).unwrap();
let mut span = m.span();
span.start = m.end();
span.end = m_next.start();
(neofetch_color_idx, span, false)
},
(Some(m), None) => {
// Last color code
let ai_start = m.start().checked_add(3).unwrap();
let ai_end = m.end().checked_sub(1).unwrap();
let neofetch_color_idx: NeofetchAsciiIndexedColor = line
[ai_start..ai_end]
.parse()
.expect("neofetch color index should be valid");
if offset == 0 && m.start() > 0 {
dst.push_str(&line[..m.start()]);
}
offset =
offset.checked_add(u8::try_from(m.len()).unwrap()).unwrap();
let mut span = m.span();
span.start = m.end();
span.end = line.len();
(neofetch_color_idx, span, true)
},
(None, _) => {
// No color code in the entire line
unreachable!(
"`fill_starting` ensured each line of ascii art starts \
with neofetch color code"
);
},
};
if span.is_empty() {
continue;
}
let txt = &line[span];
if fg.contains(&neofetch_color_idx) {
let fore = color(
match theme {
TerminalTheme::Light => "&0",
TerminalTheme::Dark => "&f",
},
color_mode,
)
.expect("foreground color should not be invalid");
write!(dst, "{fore}{txt}{reset}").unwrap();
} else {
let mut c_range: Range<usize> = span.into();
c_range.start = byte_idx_to_grapheme_idx
.get(&c_range.start)
.unwrap()
.checked_sub(usize::from(offset))
.unwrap();
c_range.end = byte_idx_to_grapheme_idx
.get(&c_range.end)
.unwrap()
.checked_sub(usize::from(offset))
.unwrap();
dst.push_str(
&ColorProfile::new(Vec::from(&color_profile.colors[c_range]))
.color_text(
txt,
color_mode,
ForegroundBackground::Foreground,
false,
)
.context("failed to color text using color profile")?,
);
}
if done {
break;
}
}
Ok(dst)
})
.collect::<Result<_>>()?
},
(ColorAlignment::Vertical, Self { fg, .. }) if fg.is_empty() => {
// Remove existing colors
let asc = {
let asc = self.lines.join("\n");
let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
const REPLACEMENTS: [&str; N] = [""; N];
ac.replace_all(&asc, &REPLACEMENTS)
};
let lines = asc.lines();
// Add new colors
lines
.map(|line| {
let line = color_profile
.color_text(line, color_mode, ForegroundBackground::Foreground, false)
.context("failed to color text using color profile")?;
Ok(line)
})
.collect::<Result<_>>()?
},
(
ColorAlignment::Custom {
colors: custom_colors,
},
_,
) => {
let Self { lines, .. } = self
.fill_starting()
.context("failed to fill in starting neofetch color codes")?;
let ColorProfile { colors } = color_profile.unique_colors();
// Apply colors
let asc = {
let asc = lines.join("\n");
let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
let mut replacements = vec![Cow::from(""); N];
for (&ai, &pi) in custom_colors {
let ai: u8 = ai.into();
let pi: u8 = pi.into();
replacements[usize::from(ai.checked_sub(1).unwrap())] = colors
[usize::from(pi)]
.to_ansi_string(color_mode, ForegroundBackground::Foreground)
.into();
}
ac.replace_all(&asc, &replacements)
};
let lines = asc.lines();
// Reset colors at end of each line to prevent color bleeding
lines.map(|line| format!("{line}{reset}")).collect()
},
_ => {
unreachable!()
},
};
Ok(RecoloredAsciiArt {
lines,
w: self.w,
h: self.h,
})
}
/// Fills the missing starting placeholders.
///
/// e.g. `"${c1}...\n..."` -> `"${c1}...\n${c1}..."`
fn fill_starting(&self) -> Result<Self> {
let ac =
NEOFETCH_COLORS_AC.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
let mut last = None;
let lines =
self.lines
.iter()
.map(|line| {
let line: &str = line.as_ref();
let mut new = String::new();
let mut matches = ac.find_iter(line).peekable();
match matches.peek() {
Some(m)
if m.start() == 0
|| line[0..m.start()].trim_end_matches(' ').is_empty() =>
{
// Line starts with neofetch color code
last = Some(&line[m.span()]);
},
Some(_) => {
new.push_str(last.context(
"failed to find neofetch color code from a previous line",
)?);
},
None => {
new.push_str(last.unwrap_or(NEOFETCH_COLOR_PATTERNS[0]));
},
}
new.push_str(line);
// Get the last placeholder for the next line
if let Some(m) = matches.last() {
last.context("non-space character seen before first color code")?;
last = Some(&line[m.span()]);
}
Ok(new)
})
.collect::<Result<_>>()?;
Ok(Self {
lines,
fg: self.fg.clone(),
..*self
})
}
}
File diff suppressed because it is too large Load Diff
+260
View File
@@ -0,0 +1,260 @@
use std::iter;
use std::path::PathBuf;
use std::str::FromStr as _;
use anyhow::Context as _;
#[cfg(feature = "autocomplete")]
use bpaf::ShellComp;
use bpaf::{construct, long, OptionParser, Parser as _};
use directories::BaseDirs;
use itertools::Itertools as _;
use strum::{VariantArray, VariantNames};
use crate::color_util::{color, Lightness};
use crate::presets::Preset;
use crate::types::{AnsiMode, Backend};
#[derive(Clone, Debug)]
pub struct Options {
pub config: bool,
pub config_file: PathBuf,
pub preset: Option<Preset>,
pub mode: Option<AnsiMode>,
pub backend: Option<Backend>,
pub args: Option<Vec<String>>,
pub scale: Option<f32>,
pub lightness: Option<Lightness>,
pub june: bool,
pub debug: bool,
pub distro: Option<String>,
pub ascii_file: Option<PathBuf>,
pub print_font_logo: bool,
pub test_print: bool,
pub ask_exit: bool,
pub auto_detect_light_dark: Option<bool>,
}
pub fn options() -> OptionParser<Options> {
let config = long("config").short('c').help("Configure hyfetch").switch();
let config_file = long("config-file")
.short('C')
.help("Use another config file")
.argument("CONFIG_FILE");
#[cfg(feature = "autocomplete")]
let config_file = config_file.complete_shell(ShellComp::Nothing);
let config_file = config_file
.fallback_with(|| {
Ok::<_, anyhow::Error>(
BaseDirs::new()
.context("failed to get base dirs")?
.config_dir()
.join("hyfetch.json"),
)
})
.debug_fallback();
let preset = long("preset")
.short('p')
.help(&*format!(
"Use preset
PRESET={{{presets}}}",
presets = <Preset as VariantNames>::VARIANTS
.iter()
.chain(iter::once(&"random"))
.join(",")
))
.argument::<String>("PRESET");
#[cfg(feature = "autocomplete")]
let preset = preset.complete(complete_preset);
let preset = preset
.parse(|s| {
Preset::from_str(&s)
.or_else(|e| {
if s == "random" {
let mut rng = fastrand::Rng::new();
Ok(*rng
.choice(<Preset as VariantArray>::VARIANTS)
.expect("preset iterator should not be empty"))
} else {
Err(e)
}
})
.with_context(|| {
format!(
"PRESET should be one of {{{presets}}}",
presets = <Preset as VariantNames>::VARIANTS
.iter()
.chain(iter::once(&"random"))
.join(",")
)
})
})
.optional();
let mode = long("mode")
.short('m')
.help(&*format!(
"Color mode
MODE={{{modes}}}",
modes = AnsiMode::VARIANTS.join(",")
))
.argument::<String>("MODE");
#[cfg(feature = "autocomplete")]
let mode = mode.complete(complete_mode);
let mode = mode
.parse(|s| {
AnsiMode::from_str(&s).with_context(|| {
format!(
"MODE should be one of {{{modes}}}",
modes = AnsiMode::VARIANTS.join(",")
)
})
})
.optional();
let backend = long("backend")
.short('b')
.help(&*format!(
"Choose a *fetch backend
BACKEND={{{backends}}}",
backends = Backend::VARIANTS.join(",")
))
.argument::<String>("BACKEND");
#[cfg(feature = "autocomplete")]
let backend = backend.complete(complete_backend);
let backend = backend
.parse(|s| {
Backend::from_str(&s).with_context(|| {
format!(
"BACKEND should be one of {{{backends}}}",
backends = Backend::VARIANTS.join(",")
)
})
})
.optional();
let args = long("args")
.help("Additional arguments pass-through to backend")
.argument::<String>("ARGS")
.parse(|s| shell_words::split(&s).context("ARGS should be valid command-line arguments"))
.optional();
let scale = long("c-scale")
.help("Lighten colors by a multiplier")
.argument("SCALE")
.optional();
let lightness = long("c-set-l")
.help("Set lightness value of the colors")
.argument("LIGHTNESS")
.optional();
let june = long("june").help("Show pride month easter egg").switch();
let debug = long("debug").help("Debug mode").switch();
let distro = long("distro")
.help("Test for a specific distro")
.argument("DISTRO")
.optional();
let test_distro = long("test-distro")
.help("Test for a specific distro")
.argument("DISTRO")
.optional();
let distro = construct!([distro, test_distro]);
let ascii_file = long("ascii-file")
.help("Use a specific file for the ascii art")
.argument("ASCII_FILE");
#[cfg(feature = "autocomplete")]
let ascii_file = ascii_file.complete_shell(ShellComp::Nothing);
let ascii_file = ascii_file.optional();
let print_font_logo = long("print-font-logo")
.help("Print the Font Logo / Nerd Font icon of your distro and exit")
.switch();
// hidden
let test_print = long("test-print")
.help("Print the ascii distro and exit")
.switch()
.hide();
let ask_exit = long("ask-exit")
.help("Ask for input before exiting")
.switch()
.hide();
let auto_detect_light_dark = long("auto-detect-light-dark")
.help("Enables hyfetch to detect light/dark terminal background in runtime")
.argument("BOOL")
.optional();
construct!(Options {
config,
config_file,
preset,
mode,
backend,
args,
scale,
lightness,
june,
debug,
distro,
ascii_file,
print_font_logo,
// hidden
test_print,
ask_exit,
auto_detect_light_dark,
})
.to_options()
.header(
&*color(
"&l&bhyfetch&~&L - neofetch with flags <3",
AnsiMode::Ansi256,
)
.expect("header should not contain invalid color codes"),
)
.version(env!("CARGO_PKG_VERSION"))
}
#[cfg(feature = "autocomplete")]
fn complete_preset(input: &String) -> Vec<(String, Option<String>)> {
<Preset as VariantNames>::VARIANTS
.iter()
.chain(iter::once(&"random"))
.filter_map(|&name| {
if name.starts_with(input) {
Some((name.to_owned(), None))
} else {
None
}
})
.collect::<Vec<_>>()
}
#[cfg(feature = "autocomplete")]
fn complete_mode(input: &String) -> Vec<(String, Option<String>)> {
AnsiMode::VARIANTS
.iter()
.filter_map(|&name| {
if name.starts_with(input) {
Some((name.to_owned(), None))
} else {
None
}
})
.collect::<Vec<_>>()
}
#[cfg(feature = "autocomplete")]
fn complete_backend(input: &String) -> Vec<(String, Option<String>)> {
Backend::VARIANTS
.iter()
.filter_map(|&name| {
if name.starts_with(input) {
Some((name.to_owned(), None))
} else {
None
}
})
.collect::<Vec<_>>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_options() {
options().check_invariants(false)
}
}
+433
View File
@@ -0,0 +1,433 @@
use std::io::{self, Write as _};
use std::num::{ParseFloatError, ParseIntError};
use std::str::FromStr;
use std::sync::OnceLock;
use aho_corasick::AhoCorasick;
use ansi_colours::{ansi256_from_grey, rgb_from_ansi256, AsRGB as _};
use anyhow::{anyhow, Context as _, Result};
use deranged::RangedU8;
use palette::color_difference::ImprovedCiede2000 as _;
use palette::{
FromColor as _, IntoColor as _, IntoColorMut as _, Lab, LinSrgb, Okhsl, Srgb, SrgbLuma,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::types::{AnsiMode, TerminalTheme};
const MINECRAFT_COLORS: [(&str, &str); 30] = [
// Minecraft formatting codes
// ==========================
("&0", "\x1b[38;5;0m"),
("&1", "\x1b[38;5;4m"),
("&2", "\x1b[38;5;2m"),
("&3", "\x1b[38;5;6m"),
("&4", "\x1b[38;5;1m"),
("&5", "\x1b[38;5;5m"),
("&6", "\x1b[38;5;3m"),
("&7", "\x1b[38;5;7m"),
("&8", "\x1b[38;5;8m"),
("&9", "\x1b[38;5;12m"),
("&a", "\x1b[38;5;10m"),
("&b", "\x1b[38;5;14m"),
("&c", "\x1b[38;5;9m"),
("&d", "\x1b[38;5;13m"),
("&e", "\x1b[38;5;11m"),
("&f", "\x1b[38;5;15m"),
("&l", "\x1b[1m"), // Enable bold text
("&o", "\x1b[3m"), // Enable italic text
("&n", "\x1b[4m"), // Enable underlined text
("&k", "\x1b[8m"), // Enable hidden text
("&m", "\x1b[9m"), // Enable strikethrough text
("&r", "\x1b[0m"), // Reset everything
// Extended codes (not officially in Minecraft)
// ============================================
("&-", "\n"), // Line break
("&~", "\x1b[39m"), // Reset text color
("&*", "\x1b[49m"), // Reset background color
("&L", "\x1b[22m"), // Disable bold text
("&O", "\x1b[23m"), // Disable italic text
("&N", "\x1b[24m"), // Disable underlined text
("&K", "\x1b[28m"), // Disable hidden text
("&M", "\x1b[29m"), // Disable strikethrough text
];
const RGB_COLOR_PATTERNS: [&str; 2] = ["&gf(", "&gb("];
/// See https://github.com/mina86/ansi_colours/blob/b9feefce10def2ac632b215ecd20830a4fca7836/src/ansi256.rs#L109
const ANSI256_GRAYSCALE_COLORS: [u8; 30] = [
16, 59, 102, 145, 188, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244,
245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
];
static MINECRAFT_COLORS_AC: OnceLock<(AhoCorasick, Box<[&str; 30]>)> = OnceLock::new();
static RGB_COLORS_AC: OnceLock<AhoCorasick> = OnceLock::new();
/// Represents the lightness component in [`Okhsl`].
///
/// The range of valid values is
/// [`Lightness::MIN`]`..=`[`Lightness::MAX`]
///
/// [`Okhsl`]: palette::Okhsl
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
pub struct Lightness(f32);
#[derive(Debug, Error)]
pub enum LightnessError {
#[error(
"invalid lightness {0}, expected value between {min} and {max}",
min = Lightness::MIN,
max = Lightness::MAX
)]
OutOfRange(f32),
}
#[derive(Debug, Error)]
pub enum ParseLightnessError {
#[error("invalid float")]
InvalidFloat(#[from] ParseFloatError),
#[error("invalid lightness")]
InvalidLightness(#[from] LightnessError),
}
/// An indexed color where the color palette is the set of colors used in
/// neofetch ascii art.
///
/// The range of valid values as supported in neofetch is
/// [`NeofetchAsciiIndexedColor::MIN`]`..=`[`NeofetchAsciiIndexedColor::MAX`]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
pub struct NeofetchAsciiIndexedColor(
RangedU8<{ NeofetchAsciiIndexedColor::MIN }, { NeofetchAsciiIndexedColor::MAX }>,
);
/// An indexed color where the color palette is the set of unique colors in a
/// preset.
///
/// The range of valid values depends on the number of unique colors in a
/// certain preset.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
pub struct PresetIndexedColor(u8);
/// Whether the color is for foreground text or background color.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum ForegroundBackground {
Foreground,
Background,
}
pub trait ToAnsiString {
/// Converts RGB to ANSI escape code.
fn to_ansi_string(&self, mode: AnsiMode, foreground_background: ForegroundBackground)
-> String;
}
pub trait Theme {
fn theme(&self) -> TerminalTheme;
}
pub trait ContrastGrayscale {
/// Calculates the grayscale foreground color which provides the highest
/// contrast against this background color.
///
/// The returned color is one of the ANSI 256 (8-bit) grayscale colors.
///
/// See <https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg>
fn contrast_grayscale(&self) -> SrgbLuma<u8>;
}
impl Lightness {
pub const MAX: f32 = 1.0f32;
pub const MIN: f32 = 0.0f32;
pub fn new(value: f32) -> Result<Self, LightnessError> {
if !(Self::MIN..=Self::MAX).contains(&value) {
return Err(LightnessError::OutOfRange(value));
}
Ok(Self(value))
}
}
impl TryFrom<f32> for Lightness {
type Error = LightnessError;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Lightness::new(value)
}
}
impl FromStr for Lightness {
type Err = ParseLightnessError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Lightness::new(s.parse()?)?)
}
}
impl From<Lightness> for f32 {
fn from(value: Lightness) -> Self {
value.0
}
}
impl NeofetchAsciiIndexedColor {
pub const MAX: u8 = 6;
pub const MIN: u8 = 1;
}
impl TryFrom<u8> for NeofetchAsciiIndexedColor {
type Error = deranged::TryFromIntError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(Self(value.try_into()?))
}
}
impl FromStr for NeofetchAsciiIndexedColor {
type Err = deranged::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
impl From<NeofetchAsciiIndexedColor> for u8 {
fn from(value: NeofetchAsciiIndexedColor) -> Self {
value.0.get()
}
}
impl From<u8> for PresetIndexedColor {
fn from(value: u8) -> Self {
Self(value)
}
}
impl FromStr for PresetIndexedColor {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
impl From<PresetIndexedColor> for u8 {
fn from(value: PresetIndexedColor) -> Self {
value.0
}
}
impl ToAnsiString for Srgb<u8> {
fn to_ansi_string(
&self,
mode: AnsiMode,
foreground_background: ForegroundBackground,
) -> String {
let c: u8 = match foreground_background {
ForegroundBackground::Foreground => 38,
ForegroundBackground::Background => 48,
};
match mode {
AnsiMode::Rgb => {
let [r, g, b]: [u8; 3] = (*self).into();
format!("\x1b[{c};2;{r};{g};{b}m")
},
AnsiMode::Ansi256 => {
let rgb: [u8; 3] = (*self).into();
let indexed = rgb.to_ansi256();
format!("\x1b[{c};5;{indexed}m")
},
AnsiMode::Ansi16 => {
unimplemented!();
},
}
}
}
impl ToAnsiString for SrgbLuma<u8> {
fn to_ansi_string(
&self,
mode: AnsiMode,
foreground_background: ForegroundBackground,
) -> String {
let c: u8 = match foreground_background {
ForegroundBackground::Foreground => 38,
ForegroundBackground::Background => 48,
};
match mode {
AnsiMode::Rgb => {
let rgb_f32_color: LinSrgb = self.into_linear().into_color();
let [r, g, b]: [u8; 3] = Srgb::<u8>::from_linear(rgb_f32_color).into();
format!("\x1b[{c};2;{r};{g};{b}m")
},
AnsiMode::Ansi256 => {
let indexed = ansi256_from_grey(self.luma);
format!("\x1b[{c};5;{indexed}m")
},
AnsiMode::Ansi16 => {
unimplemented!();
},
}
}
}
impl Theme for Srgb<u8> {
fn theme(&self) -> TerminalTheme {
let mut rgb_f32_color: LinSrgb = self.into_linear();
{
let okhsl_f32_color: &mut Okhsl = &mut rgb_f32_color.into_color_mut();
if okhsl_f32_color.lightness > 0.5 {
TerminalTheme::Light
} else {
TerminalTheme::Dark
}
}
}
}
impl ContrastGrayscale for Srgb<u8> {
fn contrast_grayscale(&self) -> SrgbLuma<u8> {
let self_lab_f32: Lab = self.into_linear().into_color();
let mut best_contrast = None;
for indexed in ANSI256_GRAYSCALE_COLORS {
let rgb_u8_color: Srgb<u8> = rgb_from_ansi256(indexed).into();
let lab_f32_color: Lab = rgb_u8_color.into_linear().into_color();
let diff = lab_f32_color.improved_difference(self_lab_f32);
best_contrast = match best_contrast {
Some((_, best_diff)) if diff > best_diff => Some((lab_f32_color, diff)),
None => Some((lab_f32_color, diff)),
best => best,
};
}
let (best_lab_f32, _) = best_contrast.expect("`best_contrast` should not be `None`");
SrgbLuma::from_color(best_lab_f32).into_format()
}
}
/// Replaces extended minecraft color codes in message.
///
/// Returns message with escape codes.
pub fn color<S>(msg: S, mode: AnsiMode) -> Result<String>
where
S: AsRef<str>,
{
let msg = msg.as_ref();
let msg = {
let (ac, escape_codes) = MINECRAFT_COLORS_AC.get_or_init(|| {
let (color_codes, escape_codes): (Vec<_>, Vec<_>) =
MINECRAFT_COLORS.into_iter().unzip();
let ac = AhoCorasick::new(color_codes).unwrap();
(
ac,
escape_codes.try_into().expect(
"`MINECRAFT_COLORS` should have the same number of elements as \
`MINECRAFT_COLORS_AC.get_or_init(...).1`",
),
)
});
ac.replace_all(msg, &escape_codes[..])
};
let ac = RGB_COLORS_AC.get_or_init(|| AhoCorasick::new(RGB_COLOR_PATTERNS).unwrap());
let mut dst = String::new();
let mut ret_err = None;
ac.replace_all_with(&msg, &mut dst, |m, _, dst| {
let start = m.end();
let end = msg[start..]
.find(')')
.context("missing closing brace for color code");
let end = match end {
Ok(end) => end,
Err(err) => {
ret_err = Some(err);
return false;
},
};
let code = &msg[start..end];
let foreground_background = if m.pattern().as_usize() == 0 {
ForegroundBackground::Foreground
} else {
ForegroundBackground::Background
};
let rgb: Srgb<u8> = if code.starts_with('#') {
let rgb = code.parse().context("failed to parse hex color");
match rgb {
Ok(rgb) => rgb,
Err(err) => {
ret_err = Some(err);
return false;
},
}
} else {
let rgb: Result<[&str; 3], _> = code
.split(&[',', ';', ' '])
.filter(|x| x.is_empty())
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow!("wrong number of rgb components"));
let rgb = match rgb {
Ok(rgb) => rgb,
Err(err) => {
ret_err = Some(err);
return false;
},
};
let rgb = rgb
.into_iter()
.map(u8::from_str)
.collect::<Result<Vec<_>, _>>()
.context("failed to parse rgb components");
let rgb: [u8; 3] = match rgb {
Ok(rgb) => rgb.try_into().unwrap(),
Err(err) => {
ret_err = Some(err);
return false;
},
};
rgb.into()
};
dst.push_str(&rgb.to_ansi_string(mode, foreground_background));
true
});
if let Some(err) = ret_err {
return Err(err);
}
Ok(dst)
}
/// Prints with color.
pub fn printc<S>(msg: S, mode: AnsiMode) -> Result<()>
where
S: AsRef<str>,
{
writeln!(
io::stdout(),
"{msg}",
msg = color(format!("{msg}&r", msg = msg.as_ref()), mode)
.context("failed to color message")?
)
.context("failed to write message to stdout")
}
/// Clears screen using ANSI escape codes.
pub fn clear_screen(title: Option<&str>, mode: AnsiMode, debug_mode: bool) -> Result<()> {
if !debug_mode {
write!(io::stdout(), "\x1b[2J\x1b[H")
.and_then(|_| io::stdout().flush())
.context("failed to write clear screen sequence to stdout")?;
}
if let Some(title) = title {
printc(format!("\n{title}\n"), mode).context("failed to print title")?;
}
Ok(())
}
+3
View File
@@ -0,0 +1,3 @@
#![allow(non_camel_case_types)]
include!(concat!(env!("OUT_DIR"), "/distros.rs"));
+42
View File
@@ -0,0 +1,42 @@
use crate::neofetch_util::get_distro_name;
use crate::types::Backend;
use crate::utils::get_cache_path;
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::fs::{File};
use std::io::{Read, Write};
const FONT_LOGOS: &str = include_str!(concat!(env!("OUT_DIR"), "/hyfetch/data/font_logos.json"));
pub fn get_font_logo(backend: Backend) -> Result<String> {
// Check if the cache file exists and return its contents if it does
let cache_path = get_cache_path().context("Failed to get cache path")?.join("font_logo");
if cache_path.exists() {
let mut cached_logo = String::new();
File::open(cache_path).context("Failed to open cache file")?
.read_to_string(&mut cached_logo).context("Failed to read from cache file")?;
return Ok(cached_logo);
}
// Deserialize the JSON into a HashMap
let font_logos: HashMap<String, String> = serde_json::from_str::<HashMap<String, String>>(FONT_LOGOS)
.context("Failed to deserialize font logos JSON file")?
.into_iter().map(|(k, v)| (k.to_lowercase(), v)).collect();
// Get the distro name
let distro = get_distro_name(backend).context("Failed to get distro name")?.to_lowercase();
// Find the most likely matching distro from font_logos
let matched_distro = font_logos.keys().find(|&k| distro.contains(k))
.or_else(|| font_logos.keys().find(|k| k.contains(&distro)))
.or_else(|| font_logos.keys().find(|k| k.split_whitespace().any(|part| distro.contains(part))))
.ok_or_else(|| anyhow::anyhow!("No font logo found for distro: {distro}. The supported logos are in https://github.com/Lukas-W/font-logos"))?;
let logo = font_logos.get(matched_distro).unwrap();
// Write the logo to the cache file
let mut cache_file = File::create(cache_path).context("Failed to create cache file")?;
cache_file.write_all(logo.as_bytes()).context("Failed to write logo to cache file")?;
Ok(logo.clone())
}
+11
View File
@@ -0,0 +1,11 @@
pub mod ascii;
pub mod cli_options;
pub mod color_util;
pub mod distros;
pub mod font_logo;
pub mod models;
pub mod neofetch_util;
pub mod presets;
pub mod pride_month;
pub mod types;
pub mod utils;
+119
View File
@@ -0,0 +1,119 @@
use serde::{Deserialize, Serialize};
use crate::color_util::Lightness;
use crate::neofetch_util::ColorAlignment;
use crate::presets::Preset;
use crate::types::{AnsiMode, Backend, TerminalTheme};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Config {
pub preset: Preset,
pub mode: AnsiMode,
pub auto_detect_light_dark: Option<bool>,
pub light_dark: Option<TerminalTheme>,
pub lightness: Option<Lightness>,
pub color_align: ColorAlignment,
pub backend: Backend,
#[serde(default)]
#[serde(with = "self::args_serde")]
pub args: Option<Vec<String>>,
pub distro: Option<String>,
pub pride_month_disable: bool,
}
impl Config {
pub fn default_lightness(theme: TerminalTheme) -> Lightness {
match theme {
TerminalTheme::Dark => {
Lightness::new(0.65).expect("default lightness should not be invalid")
},
TerminalTheme::Light => {
Lightness::new(0.4).expect("default lightness should not be invalid")
},
}
}
}
mod args_serde {
use std::fmt;
use serde::de::{self, value, Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::Serializer;
type Value = Option<Vec<String>>;
pub(super) fn serialize<S>(value: &Value, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(value) => serializer.serialize_some(&shell_words::join(value)),
None => serializer.serialize_none(),
}
}
pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
where
D: Deserializer<'de>,
{
struct StringOrVec;
struct OptionVisitor;
impl<'de> Visitor<'de> for StringOrVec {
type Value = Vec<String>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or list of strings")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
shell_words::split(s).map_err(de::Error::custom)
}
fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
where
S: SeqAccess<'de>,
{
Deserialize::deserialize(value::SeqAccessDeserializer::new(seq))
}
}
impl<'de> Visitor<'de> for OptionVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("option")
}
#[inline]
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
#[inline]
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
#[inline]
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StringOrVec).map(Some)
}
}
deserializer.deserialize_option(OptionVisitor)
}
}
+776
View File
@@ -0,0 +1,776 @@
use std::borrow::Cow;
use std::ffi::OsStr;
#[cfg(feature = "macchina")]
use std::fs;
use std::io::{self, Write as _};
use std::path::PathBuf;
use std::process::Command;
use std::sync::OnceLock;
use std::{env, fmt};
use aho_corasick::AhoCorasick;
use anyhow::{Context as _, Result};
use indexmap::IndexMap;
use itertools::Itertools as _;
#[cfg(windows)]
use anyhow::anyhow;
#[cfg(windows)]
use crate::utils::find_file;
#[cfg(windows)]
use std::path::Path;
#[cfg(windows)]
use normpath::PathExt as _;
#[cfg(windows)]
use same_file::is_same_file;
use serde::{Deserialize, Serialize};
use strum::AsRefStr;
#[cfg(feature = "macchina")]
use toml_edit::{value, DocumentMut, Item, Table};
use tracing::debug;
use unicode_segmentation::UnicodeSegmentation as _;
use which::which;
use crate::ascii::{RawAsciiArt, RecoloredAsciiArt};
use crate::color_util::{printc, NeofetchAsciiIndexedColor, PresetIndexedColor};
use crate::distros::Distro;
use crate::types::{AnsiMode, Backend};
use crate::utils::{find_in_path, get_cache_path, input, process_command_status};
pub const TEST_ASCII: &str = r####################"
### |\___/| ###
### ) ( ###
## =\ /= ##
#### )===( ####
### / \ ###
### | | ###
## / {txt} \ ##
## \ / ##
_/\_\_ _/_/\_
|##| ( ( |##|
|##| ) ) |##|
|##| (_( |##|
"####################;
pub const NEOFETCH_COLOR_PATTERNS: [&str; 6] =
["${c1}", "${c2}", "${c3}", "${c4}", "${c5}", "${c6}"];
pub static NEOFETCH_COLORS_AC: OnceLock<AhoCorasick> = OnceLock::new();
pub const NEOFETCH_SCRIPT: &str = include_str!(concat!(env!("OUT_DIR"), "/neofetch"));
#[derive(Clone, Eq, PartialEq, Debug, AsRefStr, Deserialize, Serialize)]
#[serde(tag = "mode")]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ColorAlignment {
Horizontal,
Vertical,
Custom {
#[serde(rename = "custom_colors")]
#[serde(deserialize_with = "crate::utils::index_map_serde::deserialize")]
colors: IndexMap<NeofetchAsciiIndexedColor, PresetIndexedColor>,
},
}
/// Asks the user to provide an input among a list of options.
pub fn literal_input<'a, S1, S2>(
prompt: S1,
options: &'a [S2],
default: &str,
show_options: bool,
color_mode: AnsiMode,
) -> Result<&'a str>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let prompt = prompt.as_ref();
if show_options {
let options_text = options
.iter()
.map(|o| {
let o = o.as_ref();
if o == default {
format!("&l&n{o}&L&N")
} else {
o.to_owned()
}
})
.collect::<Vec<_>>()
.join("|");
printc(format!("{prompt} ({options_text})"), color_mode)
.context("failed to print input prompt")?;
} else {
printc(format!("{prompt} (default: {default})"), color_mode)
.context("failed to print input prompt")?;
}
loop {
let selection = input(Some("> ")).context("failed to read input")?;
let selection = if selection.is_empty() {
default.to_owned()
} else {
selection.to_lowercase()
};
if let Some(selected) = find_selection(&selection, options) {
writeln!(io::stdout()).context("failed to write to stdout")?;
return Ok(selected);
} else {
let options_text = options.iter().map(AsRef::as_ref).join("|");
writeln!(
io::stdout(),
"Invalid selection! {selection} is not one of {options_text}"
)
.context("failed to write message to stdout")?;
}
}
fn find_selection<'a, S>(sel: &str, options: &'a [S]) -> Option<&'a str>
where
S: AsRef<str>,
{
if sel.is_empty() {
return None;
}
// Find exact match
if let Some(selected) = options.iter().find(|&o| o.as_ref().to_lowercase() == sel) {
return Some(selected.as_ref());
}
// Find starting abbreviation
if let Some(selected) = options
.iter()
.find(|&o| o.as_ref().to_lowercase().starts_with(sel))
{
return Some(selected.as_ref());
}
None
}
}
/// Add the PyPI pacakge path to the PATH environment variable (for this local process only).
/// This is done so that `which` can find the commands inside the PyPI package.
pub fn add_pkg_path() -> Result<()> {
// Get PATH
let pv = &env::var_os("PATH").context("`PATH` env var is not set or invalid")?;
let mut path = env::split_paths(pv).collect::<Vec<_>>();
let exe = env::current_exe().context("failed to get path of current running executable")?;
let base = exe.parent().unwrap();
// Add from bin: ../git, ../fastfetch, ../scripts
let to_add = ["git", "fastfetch", "scripts", "fastfetch/usr/bin"];
if let Some(parent) = base.parent() {
path.extend(to_add.iter().map(|d| parent.join(d)));
}
// Add from cwd: ./hyfetch/git, ./hyfetch/fastfetch, ./hyfetch/scripts
path.extend(to_add.iter().map(|d| PathBuf::from("hyfetch").join(d)));
// Set PATH
env::set_var("PATH", env::join_paths(path).context("failed to join paths")?);
debug!("Added PyPI package path to PATH, PATH={}", env::var("PATH")?);
Ok(())
}
/// Gets the absolute path of the [neofetch] command.
///
/// [neofetch]: https://github.com/hykilpikonna/hyfetch#running-updated-original-neofetch
pub fn neofetch_path() -> Result<PathBuf> {
if let Ok(p) = which("neowofetch") {
return Ok(p);
}
// Instead of doing that, let's write the neofetch script to a temp file
let f: PathBuf = get_cache_path().context("Failed to get cache path")?.join("nf_script.sh");
let mut file = fs::File::create(&f).context("Failed to create neofetch script file")?;
file.write_all(NEOFETCH_SCRIPT.as_bytes())
.context("Failed to write neofetch script to file")?;
Ok(f)
}
/// Gets the absolute path of the [macchina] command.
///
/// [macchina]: https://github.com/Macchina-CLI/macchina
#[cfg(feature = "macchina")]
pub fn macchina_path() -> Result<Option<PathBuf>> {
let macchina_path = {
#[cfg(not(windows))]
{
find_in_path("macchina").context("failed to check existence of `macchina` in `PATH`")?
}
#[cfg(windows)]
{
find_in_path("macchina.exe")
.context("failed to check existence of `macchina.exe` in `PATH`")?
}
};
// Fall back to `macchina.exe` in directory of current executable
#[cfg(windows)]
let macchina_path = macchina_path.map_or_else(
|| {
let current_exe_path: PathBuf = env::current_exe()
.and_then(|p| p.normalize().map(|p| p.into()))
.context("failed to get path of current running executable")?;
let current_exe_dir_path = current_exe_path
.parent()
.expect("parent should not be `None`");
let macchina_path = current_exe_dir_path.join("macchina.exe");
find_file(&macchina_path)
.with_context(|| format!("failed to check existence of file {macchina_path:?}"))
},
|path| Ok(Some(path)),
)?;
Ok(macchina_path)
}
/// Gets the distro ascii of the current distro. Or if distro is specified, get
/// the specific distro's ascii art instead.
#[tracing::instrument(level = "debug")]
pub fn get_distro_ascii<S>(distro: Option<S>, backend: Backend) -> Result<RawAsciiArt>
where
S: AsRef<str> + fmt::Debug,
{
let distro: Cow<_> = if let Some(distro) = distro.as_ref() {
distro.as_ref().into()
} else {
get_distro_name(backend)
.context("failed to get distro name")?
.into()
};
debug!(%distro, "distro name");
// Try new codegen-based detection method
if let Some(distro) = Distro::detect(&distro) {
let asc = distro.ascii_art().to_owned();
let fg = ascii_foreground(&distro);
return Ok(RawAsciiArt { asc, fg });
}
debug!(%distro, "could not find a match for distro; falling back to neofetch");
// Old detection method that calls neofetch
let asc = run_neofetch_command_piped(&["print_ascii", "--ascii_distro", distro.as_ref()])
.context("failed to get ascii art from neofetch")?;
// Unescape backslashes here because backslashes are escaped in neofetch for
// printf
let asc = asc.replace(r"\\", r"\");
Ok(RawAsciiArt {
asc,
fg: Vec::new(),
})
}
#[tracing::instrument(level = "debug", skip(asc), fields(asc.w = asc.w, asc.h = asc.h))]
pub fn run(asc: RecoloredAsciiArt, backend: Backend, args: Option<&Vec<String>>) -> Result<()> {
let asc = asc.lines.join("\n");
match backend {
Backend::Neofetch => run_neofetch(asc, args).context("failed to run neofetch")?,
Backend::Fastfetch => run_fastfetch(asc, args).context("failed to run fastfetch")?,
#[cfg(feature = "macchina")]
Backend::Macchina => run_macchina(asc, args).context("failed to run macchina")?,
}
Ok(())
}
/// Gets distro ascii width and height, ignoring color code.
pub fn ascii_size<S>(asc: S) -> Result<(u8, u8)>
where
S: AsRef<str>,
{
let asc = asc.as_ref();
if asc.is_empty() {
return Ok((0, 0));
}
let asc = {
let ac =
NEOFETCH_COLORS_AC.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
const N: usize = NEOFETCH_COLOR_PATTERNS.len();
const REPLACEMENTS: [&str; N] = [""; N];
ac.replace_all(asc, &REPLACEMENTS)
};
if asc.is_empty() {
return Ok((0, 0));
}
let width = asc
.lines()
.map(|line| line.graphemes(true).count())
.max()
.expect("line iterator should not be empty");
let width: u8 = width.try_into().with_context(|| {
format!(
"`asc` should not have more than {limit} characters per line",
limit = u8::MAX
)
})?;
let height = asc.lines().count();
let height: u8 = height.try_into().with_context(|| {
format!(
"`asc` should not have more than {limit} lines",
limit = u8::MAX
)
})?;
Ok((width, height))
}
/// Gets the absolute path of the bash command.
#[cfg(windows)]
fn bash_path() -> Result<PathBuf> {
// Find `bash.exe` in `PATH`, but exclude the known bad paths
if let Some(bash_path) = find_in_path("bash.exe").context("bash.exe not found")? {
// Check if it's not MSYS bash https://stackoverflow.com/a/58418686/1529493
if !bash_path.ends_with(r"Git\usr\bin\bash.exe") {
// Check if it's not WSL bash
// See https://github.com/hykilpikonna/hyfetch/issues/233
let windir = env::var_os("windir").context("`windir` environ not found")?;
match is_same_file(&bash_path, Path::new(&windir).join(r"System32\bash.exe")) {
Ok(false) => return Ok(bash_path),
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(bash_path),
_ => {}
}
}
}
if let Some(bash_path) = find_in_path("git.exe").context("failed to find `git.exe` in `PATH`")? {
if bash_path.ends_with(r"Git\cmd\git.exe") {
let pth = bash_path.parent().unwrap().parent().unwrap().join(r"bin\bash.exe");
if pth.is_file() {
return Ok(pth);
}
}
}
Err(anyhow!("bash.exe not found"))
}
/// Runs neofetch command, returning the piped stdout output.
fn run_neofetch_command_piped<S>(args: &[S]) -> Result<String>
where
S: AsRef<OsStr> + fmt::Debug,
{
let mut command = make_neofetch_command(args)?;
let output = command
.output()
.context("failed to execute neofetch as child process")?;
debug!(?output, "neofetch output");
process_command_status(&output.status).context("neofetch command exited with error")?;
let out = String::from_utf8(output.stdout)
.context("failed to process neofetch output as it contains invalid UTF-8")?
.trim()
.to_owned();
Ok(out)
}
fn make_neofetch_command<S>(args: &[S]) -> Result<Command>
where
S: AsRef<OsStr>,
{
// Find neofetch script
let neofetch_path = neofetch_path().context("neofetch command not found")?;
debug!(?neofetch_path, "neofetch path");
#[cfg(not(windows))]
{
let mut command = Command::new("bash");
command.arg(neofetch_path);
command.args(args);
Ok(command)
}
#[cfg(windows)]
{
let bash_path = bash_path().context("failed to get bash path")?;
let mut command = Command::new(bash_path);
command.arg(neofetch_path);
command.args(args);
Ok(command)
}
}
/// Runs fastfetch command, returning the piped stdout output.
fn run_fastfetch_command_piped<S>(args: &[S]) -> Result<String>
where
S: AsRef<OsStr> + fmt::Debug,
{
let mut command = make_fastfetch_command(args)?;
let output = command
.output()
.context("failed to execute fastfetch as child process")?;
debug!(?output, "fastfetch output");
process_command_status(&output.status).context("fastfetch command exited with error")?;
let out = String::from_utf8(output.stdout)
.context("failed to process fastfetch output as it contains invalid UTF-8")?
.trim()
.to_owned();
Ok(out)
}
pub fn fastfetch_path() -> Result<PathBuf> {
which("fastfetch").context("fastfetch command not found")
}
fn make_fastfetch_command<S>(args: &[S]) -> Result<Command>
where
S: AsRef<OsStr>,
{
// Find fastfetch executable
let ff_path = fastfetch_path()?;
debug!(?ff_path, "fastfetch path");
let mut command = Command::new(ff_path);
command.env("FFTS_IGNORE_PARENT", "1");
command.args(args);
Ok(command)
}
/// Runs macchina command, returning the piped stdout output.
#[cfg(feature = "macchina")]
fn run_macchina_command_piped<S>(args: &[S]) -> Result<String>
where
S: AsRef<OsStr> + fmt::Debug,
{
let mut command = make_macchina_command(args)?;
let output = command
.output()
.context("failed to execute macchina as child process")?;
debug!(?output, "macchina output");
process_command_status(&output.status).context("macchina command exited with error")?;
let out = String::from_utf8(output.stdout)
.context("failed to process macchina output as it contains invalid UTF-8")?
.trim()
.to_owned();
Ok(out)
}
#[cfg(feature = "macchina")]
fn make_macchina_command<S>(args: &[S]) -> Result<Command>
where
S: AsRef<OsStr>,
{
// Find macchina executable
let macchina_path = macchina_path()
.context("failed to get macchina path")?
.context("macchina command not found")?;
debug!(?macchina_path, "macchina path");
let mut command = Command::new(macchina_path);
command.args(args);
Ok(command)
}
#[tracing::instrument(level = "debug")]
pub fn get_distro_name(backend: Backend) -> Result<String> {
match backend {
Backend::Neofetch => run_neofetch_command_piped(&["ascii_distro_name"])
.context("failed to get distro name from neofetch"),
Backend::Fastfetch => Ok(run_fastfetch_command_piped(&["--logo", "none", "-c", "none", "-s", "OS",])
.context("failed to get distro name from fastfetch")?.replace("OS: ", "")),
#[cfg(feature = "macchina")]
Backend::Macchina => {
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(b"\t\n\t\n")
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Write macchina theme to temp file
let theme_file_path = {
let project_dirs = directories::ProjectDirs::from("", "", "macchina")
.context("failed to get base dirs")?;
let themes_path = project_dirs.config_dir().join("themes");
fs::create_dir_all(&themes_path).with_context(|| {
format!("failed to create macchina themes dir {themes_path:?}")
})?;
let mut temp_file = tempfile::Builder::new()
.suffix("theme.toml")
.tempfile_in(themes_path)
.context("failed to create temp file for macchina theme")?;
let theme_doc = {
let mut doc = DocumentMut::new();
doc["spacing"] = value(0);
doc["padding"] = value(0);
// See https://github.com/Macchina-CLI/macchina/issues/319
// doc["hide_ascii"] = value(true);
doc["separator"] = value("");
doc["custom_ascii"] = Item::Table(Table::from_iter([(
"path",
&*asc_file_path.to_string_lossy(),
)]));
doc["keys"] = Item::Table(Table::from_iter([("os", ""), ("distro", "")]));
doc
};
debug!(%theme_doc, "macchina theme");
temp_file
.write_all(theme_doc.to_string().as_bytes())
.context("failed to write macchina theme to temp file")?;
temp_file.into_temp_path()
};
let args: [&OsStr; 4] = [
"--show".as_ref(),
if cfg!(target_os = "linux") {
"distribution"
} else {
"operating-system"
}
.as_ref(),
"--theme".as_ref(),
theme_file_path
.file_stem()
.expect("file name should not be `None`"),
];
run_macchina_command_piped(&args[..])
.map(|s| {
anstream::adapter::strip_str(&s)
.to_string()
.trim()
.to_owned()
})
.context("failed to get distro name from macchina")
},
}
}
/// Runs neofetch with custom ascii art.
#[tracing::instrument(level = "debug", skip(asc))]
fn run_neofetch(asc: String, args: Option<&Vec<String>>) -> Result<()> {
// Escape backslashes here because backslashes are escaped in neofetch for
// printf
let asc = asc.replace('\\', r"\\");
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(asc.as_bytes())
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Call neofetch
let args = {
let mut v: Vec<Cow<OsStr>> = vec![
OsStr::new("--ascii").into(),
OsStr::new("--source").into(),
OsStr::new(&asc_file_path).into(),
OsStr::new("--ascii_colors").into(),
];
if let Some(args) = args {
v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
}
v
};
let mut command = make_neofetch_command(&args[..])?;
debug!(?command, "neofetch command");
let status = command
.status()
.context("failed to execute neofetch command as child process")?;
process_command_status(&status).context("neofetch command exited with error")?;
Ok(())
}
/// Runs fastfetch with custom ascii art.
#[tracing::instrument(level = "debug", skip(asc))]
fn run_fastfetch(asc: String, args: Option<&Vec<String>>) -> Result<()> {
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(asc.as_bytes())
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Call fastfetch
let args = {
let mut v: Vec<Cow<OsStr>> = vec![
OsStr::new("--file-raw").into(),
OsStr::new(&asc_file_path).into(),
];
if let Some(args) = args {
v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
}
v
};
let mut command = make_fastfetch_command(&args[..])?;
debug!(?command, "fastfetch command");
let status = command
.status()
.context("failed to execute fastfetch command as child process")?;
process_command_status(&status).context("fastfetch command exited with error")?;
Ok(())
}
/// Runs macchina with custom ascii art.
#[cfg(feature = "macchina")]
#[tracing::instrument(level = "debug", skip(asc))]
fn run_macchina(asc: String, args: Option<&Vec<String>>) -> Result<()> {
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(asc.as_bytes())
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Write macchina theme to temp file
let theme_file_path = {
let project_dirs = directories::ProjectDirs::from("", "", "macchina")
.context("failed to get base dirs")?;
let themes_path = project_dirs.config_dir().join("themes");
fs::create_dir_all(&themes_path)
.with_context(|| format!("failed to create macchina themes dir {themes_path:?}"))?;
let mut temp_file = tempfile::Builder::new()
.suffix("theme.toml")
.tempfile_in(themes_path)
.context("failed to create temp file for macchina theme")?;
let theme_doc = {
let mut doc = DocumentMut::new();
doc["custom_ascii"] = Item::Table(Table::from_iter([(
"path",
&*asc_file_path.to_string_lossy(),
)]));
doc
};
debug!(%theme_doc, "macchina theme");
temp_file
.write_all(theme_doc.to_string().as_bytes())
.context("failed to write macchina theme to temp file")?;
temp_file.into_temp_path()
};
let args = {
let mut v: Vec<Cow<OsStr>> = vec![
OsStr::new("--theme").into(),
theme_file_path
.file_stem()
.expect("file name should not be `None`")
.into(),
];
if let Some(args) = args {
v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
}
v
};
let mut command = make_macchina_command(&args[..])?;
debug!(?command, "macchina command");
let status = command
.status()
.context("failed to execute macchina command as child process")?;
process_command_status(&status).context("macchina command exited with error")?;
Ok(())
}
/// Gets the color indices that should be considered as foreground, for a
/// particular distro's ascii art.
fn ascii_foreground(distro: &Distro) -> Vec<NeofetchAsciiIndexedColor> {
let fg: Vec<u8> = match distro {
Distro::Anarchy => vec![2],
Distro::Android => vec![2],
Distro::Antergos => vec![1],
Distro::ArchStrike => vec![2],
Distro::Arkane => vec![1],
Distro::Asahi => vec![5],
Distro::Astra_Linux => vec![2],
Distro::BlackArch => vec![3],
Distro::CelOS => vec![3],
Distro::Chapeau => vec![2],
Distro::Chrom => vec![5],
Distro::Clear_Linux_OS => vec![2],
Distro::Container_Linux_by_CoreOS => vec![3],
Distro::CRUX => vec![3],
Distro::EuroLinux => vec![2],
Distro::eweOS => vec![3],
Distro::Fedora => vec![2],
Distro::Fedora_Sericea => vec![2],
Distro::Fedora_Silverblue => vec![2],
Distro::GalliumOS => vec![2],
Distro::Gentoo => vec![1],
Distro::HarDClanZ => vec![2],
Distro::Kibojoe => vec![3],
Distro::KrassOS => vec![2],
Distro::Kubuntu => vec![2],
Distro::Linux => vec![1],
Distro::LinuxFromScratch => vec![1, 3],
Distro::Lubuntu => vec![2],
Distro::openEuler => vec![2],
Distro::orchid => vec![1],
Distro::Panwah => vec![1],
Distro::Peppermint => vec![2],
Distro::PNM_Linux => vec![2],
Distro::Pop__OS => vec![2],
Distro::Reborn_OS => vec![1],
Distro::SalentOS => vec![4],
Distro::Septor => vec![2],
Distro::Ubuntu_Cinnamon => vec![2],
Distro::Ubuntu_Kylin => vec![2],
Distro::Ubuntu_MATE => vec![2],
Distro::Ubuntu_old => vec![2],
Distro::Ubuntu_Studio => vec![2],
Distro::Ubuntu_Sway => vec![2],
Distro::Ultramarine_Linux => vec![2],
Distro::Univention => vec![2],
Distro::uwuntu => vec![2],
Distro::Vanilla => vec![2],
Distro::VNux => vec![3, 5],
Distro::Void => vec![2],
Distro::Xray_OS => vec![2, 3],
Distro::Xubuntu => vec![2],
_ => Vec::new(),
};
fg.into_iter()
.map(|fore| {
fore.try_into()
.expect("`fore` should be a valid neofetch color index")
})
.collect()
}
+901
View File
@@ -0,0 +1,901 @@
use std::iter;
use std::num::{NonZeroU8, NonZeroUsize};
use anyhow::{anyhow, Context as _, Result};
use indexmap::IndexSet;
use palette::num::ClampAssign as _;
use palette::{IntoColorMut as _, LinSrgb, Okhsl, Srgb};
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, EnumCount, EnumString, VariantArray, VariantNames};
use tracing::debug;
use unicode_segmentation::UnicodeSegmentation as _;
use crate::color_util::{ForegroundBackground, Lightness, ToAnsiString as _};
use crate::types::{AnsiMode, TerminalTheme};
#[derive(
Copy,
Clone,
Hash,
Debug,
AsRefStr,
Deserialize,
EnumCount,
EnumString,
Serialize,
VariantArray,
VariantNames,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum Preset {
Rainbow,
Transgender,
Nonbinary,
Xenogender,
Agender,
Queer,
Genderfluid,
Bisexual,
Pansexual,
Polysexual,
Omnisexual,
Omniromantic,
GayMen,
Lesbian,
Abrosexual,
Asexual,
Aromantic,
Fictosexual,
Aroace1,
Aroace2,
Aroace3,
Greysexual,
Autosexual,
Intergender,
Greygender,
Akiosexual,
Bigender,
Demigender,
Demiboy,
Demigirl,
Transmasculine,
Transfeminine,
Genderfaun,
Demifaun,
Genderfae,
Demifae,
Neutrois,
Biromantic1,
Autoromantic,
Boyflux2,
Girlflux,
Genderflux,
Nullflux,
Hypergender, Hyperboy, Hypergirl, Hyperandrogyne, Hyperneutrois,
Finsexual,
Unlabeled1,
Unlabeled2,
Pangender,
/// High-contrast version of pangender flag
#[serde(rename = "pangender.contrast")]
#[strum(serialize = "pangender.contrast")]
PangenderContrast,
#[serde(rename = "gendernonconforming1")]
#[strum(serialize = "gendernonconforming1")]
GenderNonconforming1,
#[serde(rename = "gendernonconforming2")]
#[strum(serialize = "gendernonconforming2")]
GenderNonconforming2,
Femboy,
Tomboy,
Gynesexual,
Androsexual,
Gendervoid,
Voidgirl,
Voidboy,
NonhumanUnity,
/// For all the dogs
Caninekin,
Plural,
Fraysexual,
Bear,
Butch,
Leather,
Otter,
Twink,
Adipophilia,
Kenochoric,
Veldian,
Solian,
Lunian,
Polyam,
Sapphic,
Androgyne,
Interprogress,
Progress,
Intersex,
OldPolyam,
EqualRights,
Drag,
Pronounfluid,
Pronounflux,
Exipronoun,
Neopronoun,
Neofluid,
Genderqueer,
Cisgender,
/// Colors from Gilbert Baker's original 1978 flag design
Baker,
/// Meme flag
Beiyang,
/// Meme flag
Burger,
/// Meme flag
#[serde(rename = "throatlozenges")]
#[strum(serialize = "throatlozenges")]
ThroatLozenges,
/// Meme flag
Band,
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ColorProfile {
pub colors: Vec<Srgb<u8>>,
}
#[derive(Clone, PartialEq, Debug)]
pub enum AssignLightness {
Replace(Lightness),
ClampMax(Lightness),
ClampMin(Lightness),
}
impl Preset {
pub fn color_profile(&self) -> ColorProfile {
(match self {
Self::Rainbow => ColorProfile::from_hex_colors(vec![
"#E50000", "#FF8D00", "#FFEE00", "#028121", "#004CFF", "#770088",
]),
Self::Transgender => ColorProfile::from_hex_colors(vec![
"#55CDFD", "#F6AAB7", "#FFFFFF", "#F6AAB7", "#55CDFD",
]),
Self::Nonbinary => {
ColorProfile::from_hex_colors(vec!["#FCF431", "#FCFCFC", "#9D59D2", "#282828"])
},
// sourced from https://commons.wikimedia.org/wiki/File:Xenogender_pride_flag.svg
Self::Xenogender => ColorProfile::from_hex_colors(vec![
"#FF6692", "#FF9A98", "#FFB883", "#FBFFA8", "#85BCFF", "#9D85FF", "#A510FF",
]),
Self::Agender => ColorProfile::from_hex_colors(vec![
"#000000", "#BABABA", "#FFFFFF", "#BAF484", "#FFFFFF", "#BABABA", "#000000",
]),
Self::Queer => ColorProfile::from_hex_colors(vec!["#B57FDD", "#FFFFFF", "#49821E"]),
Self::Genderfluid => ColorProfile::from_hex_colors(vec![
"#FE76A2", "#FFFFFF", "#BF12D7", "#000000", "#303CBE",
]),
Self::Bisexual => ColorProfile::from_hex_colors(vec!["#D60270", "#9B4F96", "#0038A8"]),
Self::Pansexual => ColorProfile::from_hex_colors(vec!["#FF1C8D", "#FFD700", "#1AB3FF"]),
Self::Polysexual => {
ColorProfile::from_hex_colors(vec!["#F714BA", "#01D66A", "#1594F6"])
},
// sourced from https://www.flagcolorcodes.com/omnisexual
Self::Omnisexual => ColorProfile::from_hex_colors(vec![
"#FE9ACE", "#FF53BF", "#200044", "#6760FE", "#8EA6FF",
]),
Self::Omniromantic => ColorProfile::from_hex_colors(vec![
"#FEC8E4", "#FDA1DB", "#89739A", "#ABA7FE", "#BFCEFF",
]),
// sourced from https://www.flagcolorcodes.com/gay-men
Self::GayMen => ColorProfile::from_hex_colors(vec![
"#078D70", "#98E8C1", "#FFFFFF", "#7BADE2", "#3D1A78",
]),
Self::Lesbian => ColorProfile::from_hex_colors(vec![
"#D62800", "#FF9B56", "#FFFFFF", "#D462A6", "#A40062",
]),
// used colorpicker to source from https://fyeahaltpride.tumblr.com/post/151704251345/could-you-guys-possibly-make-an-abrosexual-pride
Self::Abrosexual => ColorProfile::from_hex_colors(vec![
"#46D294", "#A3E9CA", "#FFFFFF", "#F78BB3", "#EE1766",
]),
Self::Asexual => {
ColorProfile::from_hex_colors(vec!["#000000", "#A4A4A4", "#FFFFFF", "#810081"])
},
Self::Aromantic => ColorProfile::from_hex_colors(vec![
"#3BA740", "#A8D47A", "#FFFFFF", "#ABABAB", "#000000",
]),
// https://orientation.fandom.com/wiki/Fictosexual
Self::Fictosexual => ColorProfile::from_hex_colors(vec![
"#000000", "#C4C4C4", "#A349A5", "#C4C4C4", "#000000",
]),
// sourced from https://flag.library.lgbt/flags/aroace/
Self::Aroace1 => ColorProfile::from_hex_colors(vec![
"#E28C00", "#ECCD00", "#FFFFFF", "#62AEDC", "#203856",
]),
// sourced from https://flag.library.lgbt/flags/aroace/
Self::Aroace2 => ColorProfile::from_hex_colors(vec![
"#000000", "#810081", "#A4A4A4", "#FFFFFF", "#A8D47A", "#3BA740",
]),
// sourced from https://flag.library.lgbt/flags/aroace/
Self::Aroace3 => ColorProfile::from_hex_colors(vec![
"#3BA740", "#A8D47A", "#FFFFFF", "#ABABAB", "#000000", "#A4A4A4", "#FFFFFF",
"#810081",
]),
// sourced from https://www.flagcolorcodes.com/greysexual
Self::Greysexual => ColorProfile::from_hex_colors(vec![
"#740194", "#AEB1AA", "#FFFFFF", "#AEB1AA", "#740194",
]),
// sourced from https://www.flagcolorcodes.com/autosexual
Self::Autosexual => ColorProfile::from_hex_colors(vec!["#99D9EA", "#7F7F7F"]),
// sourced from https://www.flagcolorcodes.com/intergender
Self::Intergender => {
ColorProfile::from_hex_colors(vec!["#900DC2", "#FFE54F", "#900DC2"])
.and_then(|c| c.with_weights(vec![2, 1, 2]))
},
// sourced from https://www.flagcolorcodes.com/greygender
Self::Greygender => ColorProfile::from_hex_colors(vec![
"#B3B3B3", "#FFFFFF", "#062383", "#FFFFFF", "#535353",
])
.and_then(|c| c.with_weights(vec![2, 1, 2, 1, 2])),
// sourced from https://www.flagcolorcodes.com/akiosexual
Self::Akiosexual => ColorProfile::from_hex_colors(vec![
"#F9485E", "#FEA06A", "#FEF44C", "#FFFFFF", "#000000",
]),
// sourced from https://www.flagcolorcodes.com/bigender
Self::Bigender => ColorProfile::from_hex_colors(vec![
"#C479A2", "#EDA5CD", "#D6C7E8", "#FFFFFF", "#D6C7E8", "#9AC7E8", "#6D82D1",
]),
// yellow sourced from https://lgbtqia.fandom.com/f/p/4400000000000041031
// other colors sourced from demiboy and demigirl flags
Self::Demigender => ColorProfile::from_hex_colors(vec![
"#7F7F7F", "#C4C4C4", "#FBFF75", "#FFFFFF", "#FBFF75", "#C4C4C4", "#7F7F7F",
]),
// sourced from https://www.flagcolorcodes.com/demiboy
Self::Demiboy => ColorProfile::from_hex_colors(vec![
"#7F7F7F", "#C4C4C4", "#9DD7EA", "#FFFFFF", "#9DD7EA", "#C4C4C4", "#7F7F7F",
]),
// sourced from https://www.flagcolorcodes.com/demigirl
Self::Demigirl => ColorProfile::from_hex_colors(vec![
"#7F7F7F", "#C4C4C4", "#FDADC8", "#FFFFFF", "#FDADC8", "#C4C4C4", "#7F7F7F",
]),
// sourced from https://www.flagcolorcodes.com/transmasculine
Self::Transmasculine => ColorProfile::from_hex_colors(vec![
"#FF8ABD", "#CDF5FE", "#9AEBFF", "#74DFFF", "#9AEBFF", "#CDF5FE", "#FF8ABD",
]),
// used colorpicker to source from https://www.deviantart.com/pride-flags/art/Trans-Woman-Transfeminine-1-543925985
// linked from https://gender.fandom.com/wiki/Transfeminine
Self::Transfeminine => ColorProfile::from_hex_colors(vec![
"#73DEFF", "#FFE2EE", "#FFB5D6", "#FF8DC0", "#FFB5D6", "#FFE2EE", "#73DEFF",
]),
// sourced from https://www.flagcolorcodes.com/genderfaun
Self::Genderfaun => ColorProfile::from_hex_colors(vec![
"#FCD689", "#FFF09B", "#FAF9CD", "#FFFFFF", "#8EDED9", "#8CACDE", "#9782EC",
]),
// sourced from https://www.flagcolorcodes.com/demifaun
Self::Demifaun => ColorProfile::from_hex_colors(vec![
"#7F7F7F", "#C6C6C6", "#FCC688", "#FFF19C", "#FFFFFF", "#8DE0D5", "#9682EC",
"#C6C6C6", "#7F7F7F",
])
.and_then(|c| c.with_weights(vec![2, 2, 1, 1, 1, 1, 1, 2, 2])),
// sourced from https://www.flagcolorcodes.com/genderfae
Self::Genderfae => ColorProfile::from_hex_colors(vec![
"#97C3A5", "#C3DEAE", "#F9FACD", "#FFFFFF", "#FCA2C4", "#DB8AE4", "#A97EDD",
]),
// used colorpicker to source form https://www.deviantart.com/pride-flags/art/Demifae-870194777
Self::Demifae => ColorProfile::from_hex_colors(vec![
"#7F7F7F", "#C5C5C5", "#97C3A4", "#C4DEAE", "#FFFFFF", "#FCA2C5", "#AB7EDF",
"#C5C5C5", "#7F7F7F",
])
.and_then(|c| c.with_weights(vec![2, 2, 1, 1, 1, 1, 1, 2, 2])),
// sourced from https://www.flagcolorcodes.com/neutrois
Self::Neutrois => ColorProfile::from_hex_colors(vec!["#FFFFFF", "#1F9F00", "#000000"]),
// sourced from https://www.flagcolorcodes.com/biromantic-alternate-2
Self::Biromantic1 => ColorProfile::from_hex_colors(vec![
"#8869A5", "#D8A7D8", "#FFFFFF", "#FDB18D", "#151638",
]),
// sourced from https://www.flagcolorcodes.com/autoromantic
Self::Autoromantic => ColorProfile::from_hex_colors(
// symbol interpreted
vec!["#99D9EA", "#3DA542", "#7F7F7F"],
)
.and_then(|c| c.with_weights(vec![2, 1, 2])),
// sourced from https://www.flagcolorcodes.com/boyflux-alternate-2
Self::Boyflux2 => ColorProfile::from_hex_colors(vec![
"#E48AE4", "#9A81B4", "#55BFAB", "#FFFFFF", "#A8A8A8", "#81D5EF", "#69ABE5",
"#5276D4",
])
.and_then(|c| c.with_weights(vec![1, 1, 1, 1, 1, 5, 5, 5])),
// sourced from https://commons.wikimedia.org/wiki/File:Girlflux_Pride_Flag.jpg
Self::Girlflux => ColorProfile::from_hex_colors(vec![
"f9e6d7", "f2526c", "bf0311", "e9c587", "bf0311", "f2526c", "f9e6d7",
]),
// sourced from https://www.deviantart.com/pride-flags/art/Genderflux-1-543925589
Self::Genderflux => ColorProfile::from_hex_colors(vec![
"f47694", "f2a2b9", "cecece", "7ce0f7", "3ecdf9", "fff48d",
]),
Self::Nullflux => ColorProfile::from_hex_colors(vec![
"#0B0C0E", "#A28DB9", "#E1D4EF", "#F0E6DD", "#665858",
]),
Self::Hypergender => ColorProfile::from_hex_colors(vec![
"#EFEFEF", "#FFFFFF", "#FBFF75", "#000000", "#FBFF75", "#FFFFFF", "#EFEFEF",
]),
Self::Hyperboy => ColorProfile::from_hex_colors(vec![
"#EFEFEF", "#FFFFFF", "#74D7FE", "#000000", "#74D7FE", "#FFFFFF", "#EFEFEF",
]),
Self::Hypergirl => ColorProfile::from_hex_colors(vec![
"#EFEFEF", "#FFFFFF", "#FC76D3", "#000000", "#FC76D3", "#FFFFFF", "#EFEFEF",
]),
Self::Hyperandrogyne => ColorProfile::from_hex_colors(vec![
"#EFEFEF", "#FFFFFF", "#BB83FF", "#000000", "#BB83FF", "#FFFFFF", "#EFEFEF",
]),
Self::Hyperneutrois => ColorProfile::from_hex_colors(vec![
"#EFEFEF", "#FFFFFF", "#BAFA74", "#000000", "#BAFA74", "#FFFFFF", "#EFEFEF",
]),
// sourced from https://lgbtqia.wiki/wiki/Finsexual
Self::Finsexual => ColorProfile::from_hex_colors(vec![
"#B18EDF", "#D7B1E2", "#F7CDE9", "#F39FCE", "#EA7BB3",
]),
// sourced from https://web.archive.org/web/20221002181913/https://unlabeledinfo.carrd.co/#flags
Self::Unlabeled1 => {
ColorProfile::from_hex_colors(vec!["#EAF8E4", "#FDFDFB", "#E1EFF7", "#F4E2C4"])
},
// sourced from https://web.archive.org/web/20221002181913/https://unlabeledinfo.carrd.co/#flags
Self::Unlabeled2 => ColorProfile::from_hex_colors(vec![
"#250548", "#FFFFFF", "#F7DCDA", "#EC9BEE", "#9541FA", "#7D2557",
]),
Self::Pangender => ColorProfile::from_hex_colors(vec![
"#FFF798", "#FEDDCD", "#FFEBFB", "#FFFFFF", "#FFEBFB", "#FEDDCD", "#FFF798",
]),
// high-contrast version of pangender flag
Self::PangenderContrast => ColorProfile::from_hex_colors(vec![
"#ffe87f", "#fcbaa6", "#fbc9f3", "#FFFFFF", "#fbc9f3", "#fcbaa6", "#ffe87f",
]),
Self::GenderNonconforming1 => ColorProfile::from_hex_colors(vec![
"#50284d", "#96467b", "#5c96f7", "#ffe6f7", "#5c96f7", "#96467b", "#50284d",
])
.and_then(|c| c.with_weights(vec![4, 1, 1, 1, 1, 1, 4])),
Self::GenderNonconforming2 => ColorProfile::from_hex_colors(vec![
"#50284d", "#96467b", "#5c96f7", "#ffe6f7", "#5c96f7", "#96467b", "#50284d",
]),
Self::Femboy => ColorProfile::from_hex_colors(vec![
"#d260a5", "#e4afcd", "#fefefe", "#57cef8", "#fefefe", "#e4afcd", "#d260a5",
]),
Self::Tomboy => ColorProfile::from_hex_colors(vec![
"#2f3fb9", "#613a03", "#fefefe", "#f1a9b7", "#fefefe", "#613a03", "#2f3fb9",
]),
// sourced from https://lgbtqia.fandom.com/wiki/Gynesexual
Self::Gynesexual => {
ColorProfile::from_hex_colors(vec!["#F4A9B7", "#903F2B", "#5B953B"])
},
// sourced from https://lgbtqia.fandom.com/wiki/Androsexual
Self::Androsexual => {
ColorProfile::from_hex_colors(vec!["#01CCFF", "#603524", "#B799DE"])
},
// sourced from: https://gender.fandom.com/wiki/Gendervoid
Self::Gendervoid => ColorProfile::from_hex_colors(vec![
"#081149", "#4B484B", "#000000", "#4B484B", "#081149",
]),
// sourced from: https://gender.fandom.com/wiki/Gendervoid
Self::Voidgirl => ColorProfile::from_hex_colors(vec![
"#180827", "#7A5A8B", "#E09BED", "#7A5A8B", "#180827",
]),
// sourced from: https://gender.fandom.com/wiki/Gendervoid
Self::Voidboy => ColorProfile::from_hex_colors(vec![
"#0B130C", "#547655", "#66B969", "#547655", "#0B130C",
]),
// used https://twitter.com/foxbrained/status/1667621855518236674/photo/1 as source and colorpicked
Self::NonhumanUnity => {
ColorProfile::from_hex_colors(vec!["#177B49", "#FFFFFF", "#593C90"])
},
// used https://www.tumblr.com/zombpawcoins/745062851267493888/caninekin-canine-therian-flag
Self::Caninekin => ColorProfile::from_hex_colors(vec![
"#2d2822", "#543d25", "#9c754d", "#e8dac2", "#cfad8c", "#b77b55", "#954e31",
]),
// used https://pluralpedia.org/w/Plurality#/media/File:Plural-Flag-1.jpg as source and colorpicked
Self::Plural => ColorProfile::from_hex_colors(vec![
"#2D0625", "#543475", "#7675C3", "#89C7B0", "#F3EDBD",
]),
// sampled from https://es.m.wikipedia.org/wiki/Archivo:Fraysexual_flag.jpg
Self::Fraysexual => {
ColorProfile::from_hex_colors(vec!["#226CB5", "#94E7DD", "#FFFFFF", "#636363"])
},
// sourced from https://commons.wikimedia.org/wiki/File:Bear_Brotherhood_flag.svg
Self::Bear => ColorProfile::from_hex_colors(vec![
"#623804", "#D56300", "#FEDD63", "#FEE6B8", "#FFFFFF", "#555555",
]),
// colorpicked from https://commons.wikimedia.org/wiki/File:Butch_Flag.png
Self::Butch => ColorProfile::from_hex_colors(vec![
"#D72800", "#F17623", "#FF9C56", "#FFFDF6", "#FFCE89", "#FEAF02", "#A37000",
]),
// colorpicked from https://commons.wikimedia.org/wiki/File:Leather,_Latex,_and_BDSM_pride_-_Light.svg
Self::Leather => ColorProfile::from_hex_colors(vec![
"#000000", "#252580", "#000000", "#252580", "#FFFFFF", "#252580", "#000000",
"#252580", "#000000",
]),
// colorpicked from https://commons.wikimedia.org/wiki/File:Official_Otter_Pride_Flag_by_Bearbackgear.jpg
Self::Otter => ColorProfile::from_hex_colors(vec![
"#263881", "#5C9DC9", "#FFFFFF", "#3A291D", "#5C9DC9", "#263881",
]),
// colorpicked from https://commons.wikimedia.org/wiki/File:Twink_Pride_Flag_(proposed).svg
Self::Twink => ColorProfile::from_hex_colors(vec!["#FFB2FF", "#FFFFFF", "#FFFF81"]),
// https://en.wikipedia.org/wiki/File:FatFetishFlag.png
Self::Adipophilia => ColorProfile::from_hex_colors(vec![
"#000000", "#E16180", "#FFF9BE", "#603E41", "#000000",
]),
Self::Kenochoric => {
ColorProfile::from_hex_colors(vec!["#000000", "#2E1569", "#824DB7", "#C7A1D6"])
},
Self::Veldian => ColorProfile::from_hex_colors(vec![
"#D182A8", "#FAF6E0", "#69ACBE", "#5D448F", "#3A113E",
]),
Self::Solian => ColorProfile::from_hex_colors(vec![
"#FFF8ED", "#FFE7A8", "#F1B870", "#A56058", "#46281E",
]),
Self::Lunian => ColorProfile::from_hex_colors(vec![
"#2F0E62", "#6F41B1", "#889FDF", "#7DDFD5", "#D2F2E2",
]),
// pulled from https://polyamproud.com/flag
Self::Polyam => ColorProfile::from_hex_colors(vec![
"#FFFFFF", "#FCBF00", "#009FE3", "#E50051", "#340C46",
]),
Self::Sapphic => ColorProfile::from_hex_colors(vec![
"#FD8BA8", "#FBF2FF", "#C76BC5", "#FDD768", "#C76BC5", "#FBF2FF", "#FD8BA8",
]),
Self::Androgyne => ColorProfile::from_hex_colors(vec!["#FE007F", "#9832FF", "#00B8E7"]),
Self::Interprogress => ColorProfile::from_hex_colors(vec![
"#FFD800", "#7902AA", "#FFFFFF", "#FFAFC8", "#74D7EE", "#613915", "#000000",
"#E50000", "#FF8D00", "#FFEE00", "#028121", "#004CFF", "#770088",
]),
Self::Progress => ColorProfile::from_hex_colors(vec![
"#FFFFFF", "#FFAFC8", "#74D7EE", "#613915", "#000000", "#E50000", "#FF8D00",
"#FFEE00", "#028121", "#004CFF", "#770088",
]),
Self::Intersex => ColorProfile::from_hex_colors(vec!["#FFD800", "#7902AA", "#FFD800"])
.and_then(|c| c.with_weights(vec![2, 1, 2])),
Self::OldPolyam => ColorProfile::from_hex_colors(vec![
"#0000FF", "#FF0000", "#FFFF00", "#FF0000", "#000000",
]),
Self::EqualRights => ColorProfile::from_hex_colors(vec![
"#0000FF", "#FFFF00", "#0000FF", "#FFFF00", "#0000FF",
])
.and_then(|c| c.with_weights(vec![2, 1, 2, 1, 2])),
Self::Drag => ColorProfile::from_hex_colors(vec![
"#CC67FF", "#FFFFFF", "#FFA3E3", "#FFFFFF", "#3366FF",
]),
Self::Pronounfluid => ColorProfile::from_hex_colors(vec![
"#FFB3F9", "#FFFFFF", "#D1FDCB", "#C7B0FF", "#000000", "#B8CCFF",
]),
Self::Pronounflux => ColorProfile::from_hex_colors(vec![
"#FDB3F8", "#B6CCFA", "#18DDD3", "#64FF89", "#FF7690", "#FFFFFF",
]),
Self::Exipronoun => {
ColorProfile::from_hex_colors(vec!["#1C3D34", "#FFFFFF", "#321848", "#000000"])
},
Self::Neopronoun => {
ColorProfile::from_hex_colors(vec!["#BCEC64", "#FFFFFF", "#38077A"])
},
Self::Neofluid => ColorProfile::from_hex_colors(vec![
"#FFECA0", "#FFFFFF", "#FFECA0", "#38087A", "#BCEC64",
]),
Self::Genderqueer => ColorProfile::from_hex_colors(vec![
"#B57EDC", "#FFFFFF", "#4A8123"
]),
Self::Cisgender => ColorProfile::from_hex_colors(vec![
"#D70270", "#0038A7"
]),
// used https://gilbertbaker.com/rainbow-flag-color-meanings/ as source and colorpicked
Self::Baker => ColorProfile::from_hex_colors(vec![
"#F23D9E", "#F80A24", "#F78022", "#F9E81F", "#1E972E", "#1B86BC", "#243897", "#6F0A82",
]),
Self::Beiyang => ColorProfile::from_hex_colors(vec![
"#DF1B12", "#FFC600", "#01639D", "#FFFFFF", "#000000",
]),
Self::Burger => ColorProfile::from_hex_colors(vec![
"#F3A26A", "#498701", "#FD1C13", "#7D3829", "#F3A26A",
]),
Self::ThroatLozenges => ColorProfile::from_hex_colors(vec![
"#2759DA", "#03940D", "#F5F100", "#F59B00", "#B71212",
]),
Self::Band => ColorProfile::from_hex_colors(vec![
"#2670C0", "#F5BD00", "#DC0045", "#E0608E"
]),
})
.expect("preset color profiles should be valid")
}
}
impl ColorProfile {
pub fn new(colors: Vec<Srgb<u8>>) -> Self {
Self { colors }
}
pub fn from_hex_colors<S>(hex_colors: Vec<S>) -> Result<Self>
where
S: AsRef<str>,
{
let colors = hex_colors
.into_iter()
.map(|s| s.as_ref().parse())
.collect::<Result<_, _>>()
.context("failed to parse hex colors")?;
Ok(Self::new(colors))
}
/// Maps colors based on weights.
///
/// # Arguments
///
/// * `weights` - Weights of each color (`weights[i]` = how many times
/// `colors[i]` appears)
pub fn with_weights(&self, weights: Vec<u8>) -> Result<Self> {
if weights.len() != self.colors.len() {
debug!(?weights, ?self.colors, "length mismatch between `weights` and `colors`");
return Err(anyhow!(
"`weights` should have the same number of elements as `colors`"
));
}
let mut weighted_colors = Vec::new();
for (i, w) in weights.into_iter().enumerate() {
weighted_colors.extend(iter::repeat(self.colors[i]).take(usize::from(w)));
}
Ok(Self::new(weighted_colors))
}
/// Creates a new color profile, with the colors spread to the specified
/// length.
pub fn with_length(&self, length: NonZeroU8) -> Result<Self> {
let orig_len = self.colors.len();
let orig_len: NonZeroUsize = orig_len.try_into().expect("`colors` should not be empty");
let orig_len: NonZeroU8 = orig_len
.try_into()
.expect("`colors` should not have more than 255 elements");
// TODO: I believe weird things can happen because of this...
// if length < orig_len {
// unimplemented!("compressing length of color profile not implemented");
// }
let center_i = usize::from(orig_len.get() / 2);
// How many copies of each color should be displayed at least?
let repeats = length.get().div_euclid(orig_len.get());
let mut weights = vec![repeats; NonZeroUsize::from(orig_len).get()];
// How many extra spaces left?
let mut extras = length.get().rem_euclid(orig_len.get());
// If there is an odd space left, extend the center by one space
if extras % 2 == 1 {
weights[center_i] = weights[center_i].checked_add(1).unwrap();
extras = extras.checked_sub(1).unwrap();
}
// Add weight to border until there's no space left (extras must be even at this
// point)
let weights_len = weights.len();
for border_i in 0..usize::from(extras / 2) {
weights[border_i] = weights[border_i].checked_add(1).unwrap();
let border_opp = weights_len
.checked_sub(border_i)
.unwrap()
.checked_sub(1)
.unwrap();
weights[border_opp] = weights[border_opp].checked_add(1).unwrap();
}
self.with_weights(weights)
}
/// Colors a text.
///
/// # Arguments
///
/// * `foreground_background` - Whether the color is shown on the foreground
/// text or the background block
/// * `space_only` - Whether to only color spaces
pub fn color_text<S>(
&self,
txt: S,
color_mode: AnsiMode,
foreground_background: ForegroundBackground,
space_only: bool,
) -> Result<String>
where
S: AsRef<str>,
{
let txt = txt.as_ref();
let txt: Vec<&str> = txt.graphemes(true).collect();
let ColorProfile { colors } = {
let length = txt.len();
let length: NonZeroUsize = length.try_into().context("`txt` should not be empty")?;
let length: NonZeroU8 = length.try_into().with_context(|| {
format!(
"`txt` should not have more than {limit} characters",
limit = u8::MAX
)
})?;
self.with_length(length)
.with_context(|| format!("failed to spread color profile to length {length}"))?
};
let mut buf = String::new();
for (i, &gr) in txt.iter().enumerate() {
if space_only && gr != " " {
if i > 0 && txt[i.checked_sub(1).unwrap()] == " " {
buf.push_str("\x1b[39;49m");
}
buf.push_str(gr);
} else {
buf.push_str(&colors[i].to_ansi_string(color_mode, foreground_background));
buf.push_str(gr);
}
}
buf.push_str("\x1b[39;49m");
Ok(buf)
}
/// Creates a new color profile, with the colors lightened by a multiplier.
pub fn lighten(&self, multiplier: f32) -> Self {
let mut rgb_f32_colors: Vec<LinSrgb> =
self.colors.iter().map(|c| c.into_linear()).collect();
{
let okhsl_f32_colors: &mut [Okhsl] = &mut rgb_f32_colors.into_color_mut();
for okhsl_f32_color in okhsl_f32_colors {
okhsl_f32_color.lightness *= multiplier;
}
}
let rgb_u8_colors: Vec<_> = rgb_f32_colors
.into_iter()
.map(Srgb::<u8>::from_linear)
.collect();
Self {
colors: rgb_u8_colors,
}
}
/// Creates a new color profile, with the colors set to the specified
/// [`Okhsl`] lightness value.
pub fn with_lightness(&self, assign_lightness: AssignLightness) -> Self {
let mut rgb_f32_colors: Vec<LinSrgb> =
self.colors.iter().map(|c| c.into_linear()).collect();
{
let okhsl_f32_colors: &mut [Okhsl] = &mut rgb_f32_colors.into_color_mut();
for okhsl_f32_color in okhsl_f32_colors {
match assign_lightness {
AssignLightness::Replace(lightness) => {
okhsl_f32_color.lightness = lightness.into();
},
AssignLightness::ClampMax(lightness) => {
okhsl_f32_color.lightness.clamp_max_assign(lightness.into());
},
AssignLightness::ClampMin(lightness) => {
okhsl_f32_color.lightness.clamp_min_assign(lightness.into());
},
}
}
}
let rgb_u8_colors: Vec<Srgb<u8>> = rgb_f32_colors
.into_iter()
.map(Srgb::<u8>::from_linear)
.collect();
Self {
colors: rgb_u8_colors,
}
}
/// Creates a new color profile, with the colors set to the specified
/// [`Okhsl`] lightness value, adapted to the terminal theme.
pub fn with_lightness_adaptive(&self, lightness: Lightness, theme: TerminalTheme) -> Self {
match theme {
TerminalTheme::Dark => self.with_lightness(AssignLightness::ClampMin(lightness)),
TerminalTheme::Light => self.with_lightness(AssignLightness::ClampMax(lightness)),
}
}
/// Creates another color profile with only the unique colors.
pub fn unique_colors(&self) -> Self {
let unique_colors: IndexSet<[u8; 3]> = self.colors.iter().map(|&c| c.into()).collect();
let unique_colors: Vec<Srgb<u8>> = unique_colors.into_iter().map(|c| c.into()).collect();
Self::new(unique_colors)
}
}
+329
View File
@@ -0,0 +1,329 @@
use std::fmt::Write as _;
use std::io::{self, Write as _};
use std::num::{NonZeroU16, NonZeroUsize, Wrapping};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use std::{cmp, thread};
use anyhow::{anyhow, Context as _, Result};
use crossterm::execute;
use crossterm::terminal::{
BeginSynchronizedUpdate, EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen,
};
use palette::blend::Blend as _;
use palette::{LinSrgba, Srgb, WithAlpha as _};
use strum::VariantArray as _;
use terminal_size::{terminal_size, Height, Width};
use crate::color_util::{color, ForegroundBackground, ToAnsiString as _};
use crate::neofetch_util::ascii_size;
use crate::presets::Preset;
use crate::types::AnsiMode;
const TEXT_ASCII: &str = r"
.======================================================.
| . . .__ . . . , . | |
| |__| _.._ ._ . [__)._.* _| _ |\/| _ ._ -+-|_ | |
| | |(_][_)[_)\_| | [ |(_](/, | |(_)[ ) | [ ) * |
| | | ._| |
'======================================================'
";
const TEXT_ASCII_SMALL: &str = r"
.====================.
| Happy Pride Month! |
'===================='
";
const NOTICE: &str = "Press enter to continue";
pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
let (w, h) = {
let (Width(w), Height(h)) = terminal_size().context("failed to get terminal size")?;
let w: NonZeroU16 = w.try_into().context("terminal width should not be 0")?;
let h: NonZeroU16 = h.try_into().context("terminal height should not be 0")?;
(w, h)
};
let text = &TEXT_ASCII[1..TEXT_ASCII.len().checked_sub(1).unwrap()];
let (text_width, text_height) =
ascii_size(text).expect("text ascii should have valid width and height");
let (text, text_width, text_height) = {
const TEXT_BORDER_WIDTH: u16 = 2;
const NOTICE_BORDER_WIDTH: u16 = 1;
const VERTICAL_MARGIN: u16 = 1;
let notice_w = NOTICE.len();
let notice_w: u8 = notice_w
.try_into()
.expect("`NOTICE` width should fit in `u8`");
let notice_h = NOTICE.lines().count();
let notice_h: u8 = notice_h
.try_into()
.expect("`NOTICE` height should fit in `u8`");
let term_w_min = cmp::max(
u16::from(text_width)
.checked_add(TEXT_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
u16::from(notice_w)
.checked_add(NOTICE_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
);
let term_h_min = u16::from(text_height)
.checked_add(notice_h.into())
.unwrap()
.checked_add(VERTICAL_MARGIN.checked_mul(2).unwrap())
.unwrap();
if w.get() >= term_w_min && h.get() >= term_h_min {
(text, text_width, text_height)
} else {
let text = &TEXT_ASCII_SMALL[1..TEXT_ASCII_SMALL.len().checked_sub(1).unwrap()];
let (text_width, text_height) =
ascii_size(text).expect("text ascii should have valid width and height");
let term_w_min = cmp::max(
u16::from(text_width)
.checked_add(TEXT_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
u16::from(notice_w)
.checked_add(NOTICE_BORDER_WIDTH.checked_mul(2).unwrap())
.unwrap(),
);
let term_h_min = u16::from(text_height)
.checked_add(notice_h.into())
.unwrap()
.checked_add(VERTICAL_MARGIN.checked_mul(2).unwrap())
.unwrap();
if w.get() < term_w_min || h.get() < term_h_min {
return Err(anyhow!(
"terminal size should be at least ({term_w_min} * {term_h_min})"
));
}
(text, text_width, text_height)
}
};
let text_lines: Vec<&str> = text.lines().collect();
const BLOCKS: u8 = 9;
let block_width: NonZeroU16 = w
.get()
.div_euclid(u16::from(BLOCKS))
.try_into()
.with_context(|| format!("terminal width should be at least {BLOCKS}"))?;
let text_start_y = h
.get()
.div_euclid(2)
.checked_sub((text_height / 2).into())
.unwrap();
let text_end_y = text_start_y.checked_add(text_height.into()).unwrap();
let text_start_x = w
.get()
.div_euclid(2)
.checked_sub((text_width / 2).into())
.unwrap();
let text_end_x = text_start_x.checked_add(text_width.into()).unwrap();
let notice_start_x = w
.get()
.checked_sub(
u8::try_from(NOTICE.len())
.expect("`NOTICE` length should fit in `u8`")
.into(),
)
.unwrap()
.checked_sub(1)
.unwrap();
let notice_end_x = w.get().checked_sub(1).unwrap();
let notice_y = h.get().checked_sub(1).unwrap();
// Add every preset to colors
let colors: Vec<Srgb<u8>> = Preset::VARIANTS
.iter()
.flat_map(|p| p.color_profile().colors)
.collect();
let fg: Srgb<u8> = "#FFE09B"
.parse()
.expect("foreground color hex should be valid");
let black = LinSrgba::new(0.0, 0.0, 0.0, 0.5);
let draw_frame = |frame: usize| -> Result<()> {
execute!(io::stdout(), BeginSynchronizedUpdate)
.context("failed to begin synchronized update")?;
let mut buf = String::new();
// Loop over the height
for y in 0..h.get() {
// Print the starting color
write!(
buf,
"{bg}{fg}",
bg = colors[frame
.wrapping_add(y.into())
.div_euclid(block_width.get().into())
.rem_euclid(colors.len())]
.to_ansi_string(color_mode, ForegroundBackground::Background),
fg = fg.to_ansi_string(color_mode, ForegroundBackground::Foreground)
)
.unwrap();
// Loop over the width
for x in 0..w.get() {
let idx = frame
.wrapping_add(x.into())
.wrapping_add(y.into())
.wrapping_add_signed((2.0 * (y as f64 + 0.5 * frame as f64).sin()) as isize);
let y_text = text_start_y <= y && y < text_end_y;
let border = 1u16
.checked_add(
if y == text_start_y || y == text_end_y.checked_sub(1).unwrap() {
0
} else {
1
},
)
.unwrap();
let text_bounds_x1 = text_start_x
.checked_sub(border)
.expect("`text_start_x - border` should not underflow `u16`");
let text_bounds_x2 = text_end_x
.checked_add(border)
.expect("`text_end_x + border` should not overflow `u16`");
let notice_bounds_x1 = notice_start_x
.checked_sub(1)
.expect("`notice_start_x - 1` should not underflow `u16`");
let notice_bounds_x2 = notice_end_x
.checked_add(1)
.expect("`notice_end_x + 1` should not overflow `u16`");
// If it's a switching point
if idx.rem_euclid(NonZeroUsize::from(block_width).get()) == 0
|| x == text_bounds_x1
|| x == text_bounds_x2
|| x == notice_bounds_x1
|| x == notice_bounds_x2
{
// Print the color at the current frame
let ci = idx
.div_euclid(NonZeroUsize::from(block_width).get())
.rem_euclid(colors.len());
let c = colors[ci];
if (y_text && (text_bounds_x1 <= x) && (x < text_bounds_x2))
|| (y == notice_y && notice_bounds_x1 <= x && x < notice_bounds_x2)
{
let c: LinSrgba = c.with_alpha(1.0).into_linear();
let c = Srgb::<u8>::from_linear(c.overlay(black).without_alpha());
write!(
buf,
"{bg}",
bg = c.to_ansi_string(color_mode, ForegroundBackground::Background),
)
.unwrap();
} else {
write!(
buf,
"{bg}",
bg = c.to_ansi_string(color_mode, ForegroundBackground::Background),
)
.unwrap();
}
}
// If text should be printed, print text
if y_text && text_start_x <= x && x < text_end_x {
write!(
buf,
"{text_char}",
text_char = text_lines[usize::from(y.checked_sub(text_start_y).unwrap())]
.chars()
.nth(usize::from(x.checked_sub(text_start_x).unwrap()))
.unwrap(),
)
.unwrap();
} else if y == notice_y && notice_start_x <= x && x < notice_end_x {
write!(
buf,
"{notice_char}",
notice_char = NOTICE
.chars()
.nth(usize::from(x.checked_sub(notice_start_x).unwrap()))
.unwrap(),
)
.unwrap();
} else {
write!(buf, " ").unwrap();
}
}
// New line if it isn't the last line
if y != h.get().checked_sub(1).unwrap() {
writeln!(
buf,
"{reset}",
reset = color("&r", color_mode).expect("reset should be valid"),
)
.unwrap();
}
}
{
let mut stdout = io::stdout().lock();
write!(stdout, "{buf}")
.and_then(|_| stdout.flush())
.context("failed to write to stdout")?;
}
execute!(io::stdout(), EndSynchronizedUpdate)
.context("failed to end synchronized update")?;
Ok(())
};
let key_pressed = Arc::new(AtomicBool::new(false));
// TODO: use non-blocking I/O; no need for another thread
let _handle = thread::spawn({
let key_pressed = Arc::clone(&key_pressed);
move || {
loop {
match io::stdin().lines().next() {
Some(Ok(_)) => {
key_pressed.store(true, Ordering::Release);
break;
},
Some(Err(err)) => {
eprintln!("failed to read line from stdin: {err}");
},
None => {
// EOF
},
}
}
}
});
let mut frame: Wrapping<usize> = Wrapping(0);
const SPEED: u8 = 2;
let frame_delay = Duration::from_secs_f32(2.0 / 25.0);
execute!(io::stdout(), EnterAlternateScreen).context("failed to enter alternate screen")?;
loop {
// Move cursor to the top left corner
print!("\x1B[H");
draw_frame(frame.0)?;
frame += usize::from(SPEED);
thread::sleep(frame_delay);
if key_pressed.load(Ordering::Acquire) {
break;
}
}
execute!(io::stdout(), LeaveAlternateScreen).context("failed to leave alternate screen")?;
Ok(())
}
+70
View File
@@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, EnumString, VariantNames};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, AsRefStr, Deserialize, EnumString, Serialize)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum AnsiMode {
#[serde(rename = "ansi")]
#[serde(skip)]
#[strum(serialize = "ansi")]
#[strum(disabled)]
Ansi16,
#[serde(rename = "8bit")]
#[strum(serialize = "8bit")]
Ansi256,
Rgb,
}
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Hash,
Debug,
AsRefStr,
Deserialize,
EnumString,
Serialize,
VariantNames,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum TerminalTheme {
Light,
Dark,
}
impl Default for TerminalTheme {
fn default() -> Self {
Self::Dark
}
}
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Hash,
Debug,
AsRefStr,
Deserialize,
EnumString,
Serialize,
VariantNames,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum Backend {
Neofetch,
Fastfetch,
#[cfg(feature = "macchina")]
Macchina,
}
// See https://github.com/Peternator7/strum/issues/244
impl VariantNames for AnsiMode {
const VARIANTS: &'static [&'static str] = &["8bit", "rgb"];
}
+231
View File
@@ -0,0 +1,231 @@
use std::io::Write as _;
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt as _;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use std::{env, fs, io};
use anyhow::{anyhow, Context as _, Result};
use directories::ProjectDirs;
#[cfg(windows)]
use normpath::PathExt as _;
use tracing::debug;
pub fn get_cache_path() -> Result<PathBuf> {
let path = ProjectDirs::from("", "", "hyfetch")
.context("failed to get base dirs")?
.cache_dir()
.to_owned();
// Make sure the cache directory exists
if !path.exists() {
fs::create_dir_all(&path).with_context(|| format!("failed to create cache dir {path:?}"))?;
}
Ok(path)
}
/// Reads a string from standard input. The trailing newline is stripped.
///
/// The prompt string, if given, is printed to standard output without a
/// trailing newline before reading input.
pub fn input<S>(prompt: Option<S>) -> Result<String>
where
S: AsRef<str>,
{
if let Some(prompt) = prompt {
write!(io::stdout(), "{prompt}", prompt = prompt.as_ref())
.and_then(|_| io::stdout().flush())
.context("failed to write prompt to stdout")?;
}
io::stdin()
.lines()
.next()
.unwrap_or_else(|| Ok(String::new()))
.context("failed to read line from stdin")
}
/// Finds a command in `PATH`.
///
/// Returns the canonicalized / normalized absolute path of the command.
pub fn find_in_path<P>(program: P) -> Result<Option<PathBuf>>
where
P: AsRef<Path>,
{
let program = program.as_ref();
// Only accept program name, i.e. a relative path with one component
if program.parent() != Some(Path::new("")) {
return Err(anyhow!("invalid command name {program:?}"));
};
let path_env = env::var_os("PATH").context("`PATH` env var is not set or invalid")?;
for search_path in env::split_paths(&path_env) {
let path = search_path.join(program);
let path = find_file(&path)
.with_context(|| format!("failed to check existence of file {path:?}"))?;
if path.is_some() {
return Ok(path);
}
}
Ok(None)
}
/// Finds a file.
///
/// Returns the canonicalized / normalized absolute path of the file.
pub fn find_file<P>(path: P) -> Result<Option<PathBuf>>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let metadata = match fs::metadata(path) {
Ok(metadata) => metadata,
Err(err) if err.kind() == io::ErrorKind::NotFound => {
return Ok(None);
},
Err(err) => {
return Err(err).with_context(|| format!("failed to get metadata for {path:?}"));
},
};
if !metadata.is_file() {
debug!(?path, "path exists but is not a file");
return Ok(None);
}
#[cfg(not(windows))]
{
path.canonicalize()
.with_context(|| format!("failed to canonicalize path {path:?}"))
.map(Some)
}
#[cfg(windows)]
{
path.normalize()
.with_context(|| format!("failed to normalize path {path:?}"))
.map(|p| Some(p.into()))
}
}
pub fn process_command_status(status: &ExitStatus) -> Result<()> {
if status.success() {
return Ok(());
}
let err = if let Some(code) = status.code() {
anyhow!("child process exited with status code: {code}")
} else {
#[cfg(unix)]
{
anyhow!(
"child process terminated by signal: {signal}",
signal = status
.signal()
.expect("either one of status code or signal should be set")
)
}
#[cfg(not(unix))]
{
unimplemented!("status code not expected to be `None` on non-Unix platforms")
}
};
Err(err)
}
pub(crate) mod index_map_serde {
use std::fmt;
use std::hash::Hash;
use std::marker::PhantomData;
use std::str::FromStr;
use indexmap::IndexMap;
use serde::de::{self, DeserializeSeed, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
pub(crate) fn deserialize<'de, D, K, V>(deserializer: D) -> Result<IndexMap<K, V>, D::Error>
where
D: Deserializer<'de>,
K: Eq + Hash + FromStr,
K::Err: fmt::Display,
V: Deserialize<'de>,
{
struct KeySeed<K> {
k: PhantomData<K>,
}
impl<'de, K> DeserializeSeed<'de> for KeySeed<K>
where
K: FromStr,
K::Err: fmt::Display,
{
type Value = K;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(self)
}
}
impl<K> Visitor<'_> for KeySeed<K>
where
K: FromStr,
K::Err: fmt::Display,
{
type Value = K;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
K::from_str(s).map_err(de::Error::custom)
}
}
struct MapVisitor<K, V> {
k: PhantomData<K>,
v: PhantomData<V>,
}
impl<'de, K, V> Visitor<'de> for MapVisitor<K, V>
where
K: Eq + Hash + FromStr,
K::Err: fmt::Display,
V: Deserialize<'de>,
{
type Value = IndexMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map")
}
fn visit_map<A>(self, mut input: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut map = IndexMap::new();
while let Some((k, v)) =
input.next_entry_seed(KeySeed { k: PhantomData }, PhantomData)?
{
map.insert(k, v);
}
Ok(map)
}
}
deserializer.deserialize_map(MapVisitor {
k: PhantomData,
v: PhantomData,
})
}
}
+38 -37
View File
@@ -1,78 +1,79 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH VERSION "1" "October 2024" "Version is 1.99.0" "User Commands"
.TH VERSION: "1" "September 2025" "Version: 2.0.2" "User Commands"
.SH NAME
Version \- manual page for Version is 1.99.0
Version: \- manual page for Version: 2.0.2
.SH SYNOPSIS
.B hyfetch
[\fI\,-c\/\fR] [\fI\,-C=CONFIG_FILE\/\fR] [\fI\,-p=PRESET\/\fR] [\fI\,-m=MODE\/\fR] [\fI\,-b=BACKEND\/\fR] [\fI\,--args=ARGS\/\fR] [\fI\,--c-scale=\/\fR
.SH DESCRIPTION
usage: hyfetch [\-h] [\-c] [\-C CONFIG_FILE]
.IP
[\-p {rainbow,transgender,nonbinary,xenogender,agender,queer,genderfluid,bisexual,pansexual,polysexual,omnisexual,omniromantic,gay\-men,lesbian,abrosexual,asexual,aromantic,aroace1,aroace2,aroace3,autosexual,intergender,greygender,akiosexual,bigender,demigender,demiboy,demigirl,transmasculine,transfeminine,genderfaun,demifaun,genderfae,demifae,neutrois,biromantic1,biromantic2,autoromantic,boyflux2,girlflux,genderflux,finsexual,unlabeled1,unlabeled2,pangender,pangender.contrast,gendernonconforming1,gendernonconforming2,femboy,tomboy,gynesexual,androsexual,gendervoid,voidgirl,voidboy,nonhuman\-unity,plural,fraysexual,bear,butch,leather,otter,twink,kenochoric,veldian,solian,lunian,polyam,sapphic,androgyne,interprogress,progress,intersex,old\-polyam,equal\-rights,drag,pronounfluid,pronounflux,exipronoun,neopronoun,neofluid,genderqueer,beiyang,burger,baker,caninekin,random}]
[\-m {8bit,rgb}]
[\-b {qwqfetch,neofetch,fastfetch,fastfetch\-old}] [\-\-args ARGS]
[\-\-c\-scale SCALE] [\-\-c\-set\-l LIGHT] [\-\-c\-overlay] [\-V] [\-\-june]
[\-\-debug] [\-\-distro DISTRO] [\-\-ascii\-file ASCII_FILE]
[\-\-print\-font\-logo]
SCALE] [\-\-c\-set\-l=LIGHTNESS] [\-\-june] [\-\-debug] ([\-\-distro=DISTRO] | [\-\-test\-distro=DISTRO]) [
\fB\-\-ascii\-file\fR=\fI\,ASCII_FILE]\/\fR [\-\-print\-font\-logo] [\-\-auto\-detect\-light\-dark=BOOL]
.PP
hyfetch \- neofetch with flags <3
.SS "options:"
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.SS "Available options:"
.TP
\fB\-c\fR, \fB\-\-config\fR
Configure hyfetch
.TP
\fB\-C\fR CONFIG_FILE, \fB\-\-config\-file\fR CONFIG_FILE
\fB\-C\fR, \fB\-\-config\-file\fR=\fI\,CONFIG_FILE\/\fR
Use another config file
[default: "/home/azalea/.config/hyfetch.json"]
.TP
\fB\-p\fR {rainbow,transgender,nonbinary,xenogender,agender,queer,genderfluid,bisexual,pansexual,polysexual,omnisexual,omniromantic,gay\-men,lesbian,abrosexual,asexual,aromantic,aroace1,aroace2,aroace3,autosexual,intergender,greygender,akiosexual,bigender,demigender,demiboy,demigirl,transmasculine,transfeminine,genderfaun,demifaun,genderfae,demifae,neutrois,biromantic1,biromantic2,autoromantic,boyflux2,girlflux,genderflux,finsexual,unlabeled1,unlabeled2,pangender,pangender.contrast,gendernonconforming1,gendernonconforming2,femboy,tomboy,gynesexual,androsexual,gendervoid,voidgirl,voidboy,nonhuman\-unity,plural,fraysexual,bear,butch,leather,otter,twink,kenochoric,veldian,solian,lunian,polyam,sapphic,androgyne,interprogress,progress,intersex,old\-polyam,equal\-rights,drag,pronounfluid,pronounflux,exipronoun,neopronoun,neofluid,genderqueer,beiyang,burger,baker,caninekin,random}, \fB\-\-preset\fR {rainbow,transgender,nonbinary,xenogender,agender,queer,genderfluid,bisexual,pansexual,polysexual,omnisexual,omniromantic,gay\-men,lesbian,abrosexual,asexual,aromantic,aroace1,aroace2,aroace3,autosexual,intergender,greygender,akiosexual,bigender,demigender,demiboy,demigirl,transmasculine,transfeminine,genderfaun,demifaun,genderfae,demifae,neutrois,biromantic1,biromantic2,autoromantic,boyflux2,girlflux,genderflux,finsexual,unlabeled1,unlabeled2,pangender,pangender.contrast,gendernonconforming1,gendernonconforming2,femboy,tomboy,gynesexual,androsexual,gendervoid,voidgirl,voidboy,nonhuman\-unity,plural,fraysexual,bear,butch,leather,otter,twink,kenochoric,veldian,solian,lunian,polyam,sapphic,androgyne,interprogress,progress,intersex,old\-polyam,equal\-rights,drag,pronounfluid,pronounflux,exipronoun,neopronoun,neofluid,genderqueer,beiyang,burger,baker,caninekin,random}
\fB\-p\fR, \fB\-\-preset\fR=\fI\,PRESET\/\fR
Use preset
PRESET={rainbow,transgender,nonbinary,xenogender,agender,queer,genderfluid,bisexual,pansexual,polysexual,omnisexual,omniromantic,gay\-men,lesbian,abrosexual,asexual,aromantic,fictosexual,aroace1,aroace2,aroace3,greysexual,autosexual,intergender,greygender,akiosexual,bigender,demigender,demiboy,demigirl,transmasculine,transfeminine,genderfaun,demifaun,genderfae,demifae,neutrois,biromantic1,autoromantic,boyflux2,girlflux,genderflux,nullflux,hypergender,hyperboy,hypergirl,hyperandrogyne,hyperneutrois,finsexual,unlabeled1,unlabeled2,pangender,pangender.contrast,gendernonconforming1,gendernonconforming2,femboy,tomboy,gynesexual,androsexual,gendervoid,voidgirl,voidboy,nonhuman\-unity,caninekin,plural,fraysexual,bear,butch,leather,otter,twink,adipophilia,kenochoric,veldian,solian,lunian,polyam,sapphic,androgyne,interprogress,progress,intersex,old\-polyam,equal\-rights,drag,pronounfluid,pronounflux,exipronoun,neopronoun,neofluid,genderqueer,cisgender,baker,beiyang,burger,throatlozenges,band,random}
.TP
\fB\-m\fR {8bit,rgb}, \fB\-\-mode\fR {8bit,rgb}
Color mode
\fB\-m\fR, \fB\-\-mode\fR=\fI\,MODE\/\fR
Color mode MODE={8bit,rgb}
.TP
\fB\-b\fR {qwqfetch,neofetch,fastfetch,fastfetch\-old}, \fB\-\-backend\fR {qwqfetch,neofetch,fastfetch,fastfetch\-old}
Choose a *fetch backend
\fB\-b\fR, \fB\-\-backend\fR=\fI\,BACKEND\/\fR
Choose a *fetch backend BACKEND={neofetch,fastfetch,macchina}
.TP
\fB\-\-args\fR ARGS
\fB\-\-args\fR=\fI\,ARGS\/\fR
Additional arguments pass\-through to backend
.TP
\fB\-\-c\-scale\fR SCALE
\fB\-\-c\-scale\fR=\fI\,SCALE\/\fR
Lighten colors by a multiplier
.TP
\fB\-\-c\-set\-l\fR LIGHT
\fB\-\-c\-set\-l\fR=\fI\,LIGHTNESS\/\fR
Set lightness value of the colors
.TP
\fB\-\-c\-overlay\fR
Use experimental overlay color adjusting instead of
HSL lightness
.TP
\fB\-V\fR, \fB\-\-version\fR
Check version
.TP
\fB\-\-june\fR
Show pride month easter egg
.TP
\fB\-\-debug\fR
Debug mode
.TP
\fB\-\-distro\fR DISTRO, \fB\-\-test\-distro\fR DISTRO
\fB\-\-distro\fR=\fI\,DISTRO\/\fR
Test for a specific distro
.TP
\fB\-\-ascii\-file\fR ASCII_FILE
\fB\-\-test\-distro\fR=\fI\,DISTRO\/\fR
Test for a specific distro
.TP
\fB\-\-ascii\-file\fR=\fI\,ASCII_FILE\/\fR
Use a specific file for the ascii art
.TP
\fB\-\-print\-font\-logo\fR
Print the Font Logo / Nerd Font icon of your distro
and exit
Print the Font Logo / Nerd Font icon of your distro and exit
.TP
\fB\-\-auto\-detect\-light\-dark\fR=\fI\,BOOL\/\fR
Enables hyfetch to detect light/dark terminal background in
runtime
.TP
\fB\-h\fR, \fB\-\-help\fR
Prints help information
.TP
\fB\-V\fR, \fB\-\-version\fR
Prints version information
.SH "SEE ALSO"
The full documentation for
.B Version
.B Version:
is maintained as a Texinfo manual. If the
.B info
and
.B Version
.B Version:
programs are properly installed at your site, the command
.IP
.B info Version
.B info Version:
.PP
should give you access to the complete manual.
+38 -39
View File
@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH NEOFETCH "1" "October 2024" "Neofetch 7.98.0" "User Commands"
.TH NEOFETCH "1" "September 2025" "Neofetch 8.0.2" "User Commands"
.SH NAME
Neofetch \- manual page for Neofetch 7.98.0
Neofetch \- manual page for Neofetch 8.0.2
.SH SYNOPSIS
.B neofetch
\fI\,func_name --option "value" --option "value"\/\fR
@@ -321,12 +321,12 @@ Amazon, AmogOS, Anarchy, Android, Antergos, antiX, AOSC OS, Aperio
GNU/Linux, Aperture, Apricity, Arch, ArchBox, Archcraft,
archcraft_ascii, archcraft_minimal, ARCHlabs, ArchMerge, ArchStrike,
ArcoLinux, Arkane, ArseLinux, Artix, Arya, Asahi, AsteroidOS, astOS,
Astra Linux, Athena, azos, Bedrock, BigLinux, BigLinux_large,
Bitrig, BlackArch, BlackMesa, blackPanther, BLAG, BlankOn,
BlueLight, Bodhi, bonsai, BSD, BunsenLabs, CachyOS, Calculate,
CalinixOS, Carbs, CBL\-Mariner, CelOS, Center, CentOS, Chakra,
ChaletOS, Chapeau, Chimera, ChonkySealOS, Chrom, Cleanjaro, Clear
Linux OS, ClearOS, Clover, Cobalt, Condres, Container Linux by
Astra Linux, Athena, azos, Bazzite, Bedrock, BigLinux,
BigLinux_large, Bitrig, BlackArch, BlackMesa, blackPanther, BLAG,
BlankOn, BlueLight, Bodhi, bonsai, BSD, BunsenLabs, CachyOS,
Calculate, CalinixOS, Carbs, CBL\-Mariner, CelOS, Center, CentOS,
Chakra, ChaletOS, Chapeau, Chimera, ChonkySealOS, Chrom, Cleanjaro,
Clear Linux OS, ClearOS, Clover, Cobalt, Condres, Container Linux by
CoreOS, CRUX, Crystal Linux, Cucumber, CutefishOS, CuteOS, CyberOS,
dahlia, DarkOs, Darwin, Debian, Deepin, DesaOS, Devuan, DietPi,
digital UNIX, DracOS, DragonFly, Drauger, Droidian, Elementary,
@@ -335,37 +335,36 @@ EvolutionOS, eweOS, Exherbo, Exodia Predator OS, Fedora, Fedora
Kinoite, Fedora Sericea, Fedora Silverblue, Fedora_unicode,
FemboyOS, Feren, Finnix, Floflis, FreeBSD, FreeMiNT, Frugalware,
Funtoo, Furreto, GalliumOS, Garuda, Gentoo, GhostBSD, glaucus,
gNewSense, GNOME, GNU, GoboLinux, GrapheneOS, Grombyang, Guix,
Haiku, HamoniKR, HarDClanZ, Hash, Huayra, Hybrid, HydroOS,
Hyperbola, iglunix, instantOS, Interix, IRIX, Ironclad, Itc,
januslinux, Kaisen, Kali, KaOS, KDE, Kibojoe, Kogaion, Korora,
KrassOS, KSLinux, Kubuntu, LainOS, LangitKetujuh, LaxerOS, LEDE,
LibreELEC, Linspire, Linux, Linux Lite, Linux Mint, Linux Mint Old,
LinuxFromScratch, Live Raizo, LMDE, Lubuntu, Lunar, mac, MacaroniOS,
Mageia, Magix, MagpieOS, MainsailOS, Mandriva, Manjaro, MassOS,
MatuusOS, Maui, Mauna, Meowix, Mer, Minix, MIRACLE LINUX, MX, Namib,
NekOS, Neptune, NetBSD, Netrunner, Nitrux, NixOS, nixos_colorful,
Nobara, NomadBSD, Nurunner, NuTyX, Obarun, OBRevenge, OmniOS, Open
Source Media Center, OpenBSD, openEuler, OpenIndiana, openKylin,
openmamba, OpenMandriva, OpenStage, openSUSE, openSUSE Leap,
openSUSE Tumbleweed, OPNsense, Oracle, orchid, OS Elbrus, PacBSD,
Panwah, Parabola, parch, Pardus, Parrot, Parsix, PCBSD, PCLinuxOS,
pearOS, Pengwin, Pentoo, Peppermint, Peropesis, phyOS, PikaOS, Pisi,
PNM Linux, Pop!_OS, Porteus, PostMarketOS, Profelis SambaBOX,
Proxmox, PuffOS, Puppy, PureOS, Q4OS, Qubes, Qubyt, Quibian, Radix,
Raspbian, ravynOS, Reborn OS, Red Star, Redcore, Redhat, Refracted
Devuan, Regata, Regolith, RhaymOS, rocky, Rosa, Sabayon, sabotage,
Sailfish, SalentOS, Salient OS, Salix, Sasanqua, Scientific, semc,
Septor, Serene, SharkLinux, ShastraOS, Siduction, SkiffOS, Slackel,
Slackware, SliTaz, SmartOS, Soda, Solus, Source Mage, Sparky, Star,
SteamOS, Stock Linux, Sulin, SunOS, SwagArch, t2, Tails, Tatra,
TeArch, TorizonCore, Trisquel, Twister, Ubuntu, Ubuntu Budgie,
Ubuntu Cinnamon, Ubuntu Kylin, Ubuntu MATE, Ubuntu Studio, Ubuntu
Sway, Ubuntu Touch, Ubuntu\-GNOME, ubuntu_old02, Ultramarine Linux,
unicodearch, Univalent, Univention, Uos, UrukOS, uwuntu, Vanilla,
Venom, VNux, Void, VzLinux, wii\-linux\-ngx, Windows, Windows 10,
Windows 11, Windows95, Wrt, Xenia, Xenia2, XFerience, Xray_OS,
Xubuntu, yiffOS, Zorin have ascii logos.
Gloire, gNewSense, GNOME, GNU, GoboLinux, GrapheneOS, Grombyang,
Guix, Haiku, HamoniKR, HarDClanZ, Hash, Huayra, Hybrid, HydroOS,
Hyperbola, iglunix, instantOS, Interix, IRIX, Itc, januslinux,
Kaisen, Kali, KaOS, KDE, Kibojoe, Kogaion, Korora, KrassOS, KSLinux,
Kubuntu, LainOS, LangitKetujuh, LaxerOS, LEDE, LibreELEC, Linspire,
Linux, Linux Lite, Linux Mint, Linux Mint Old, LinuxFromScratch,
Live Raizo, LMDE, Lubuntu, Lunar, mac, MacaroniOS, Mageia, Magix,
MagpieOS, MainsailOS, Mandriva, Manjaro, MassOS, MatuusOS, Maui,
Mauna, Meowix, Mer, Minix, MIRACLE LINUX, MX, Namib, NekOS, Neptune,
NetBSD, Netrunner, Nitrux, NixOS, nixos_colorful, Nobara, NomadBSD,
Nurunner, NuTyX, Obarun, OBRevenge, OmniOS, Open Source Media
Center, OpenBSD, openEuler, OpenIndiana, openKylin, openmamba,
OpenMandriva, OpenStage, openSUSE, openSUSE Leap, openSUSE
Tumbleweed, openSUSE Tumbleweed\-Slowroll, OPNsense, Oracle, orchid,
OS Elbrus, PacBSD, Panwah, Parabola, parch, Pardus, Parrot, Parsix,
PCBSD, PCLinuxOS, pearOS, Pengwin, Pentoo, Peppermint, Peropesis,
phyOS, PikaOS, Pisi, PNM Linux, Pop!_OS, Porteus, PostMarketOS,
Profelis SambaBOX, Proxmox, PuffOS, Puppy, PureOS, Q4OS, Qubes,
Qubyt, Quibian, Radix, Raspbian, ravynOS, Reborn OS, Red Star,
Redcore, Redhat, Refracted Devuan, Regata, Regolith, RhaymOS, Rhino
Linux, rocky, Rosa, Sabayon, sabotage, Sailfish, SalentOS, Salient
OS, Salix, Sasanqua, Scientific, semc, Septor, Serene, SharkLinux,
ShastraOS, Siduction, SkiffOS, Slackel, Slackware, SliTaz, SmartOS,
Soda, Solus, Source Mage, Sparky, Star, SteamOS, Stock Linux, Sulin,
SunOS, SwagArch, t2, Tails, Tatra, TeArch, TorizonCore, Trisquel,
Twister, Ubuntu, Ubuntu Budgie, Ubuntu Cinnamon, Ubuntu Kylin,
Ubuntu MATE, Ubuntu Studio, Ubuntu Sway, Ubuntu Touch, Ubuntu\-GNOME,
ubuntu_old02, Ultramarine Linux, unicodearch, Univalent, Univention,
Uos, UrukOS, uwuntu, Vanilla, Venom, VNux, Void, VzLinux, wii\-linuxngx, Windows, Windows 10, Windows 11, Windows95, Wrt, Xenia, Xenia2,
XFerience, Xray_OS, Xubuntu, yiffOS, Zorin have ascii logos.
.TP
NOTE: arch, dragonfly, Fedora, LangitKetujuh, nixos, redhat, Ubuntu
have 'old' logo variants, use {distro}_old to use them.
+8 -7
View File
@@ -1,9 +1,10 @@
from . import main
from .color_util import printc
import os
from .py import run_py
from .rs import run_rust
if __name__ == '__main__':
try:
main.run()
except KeyboardInterrupt:
printc('&cThe program is interrupted by ^C, exiting...')
exit(0)
if os.environ.get('HYFETCH_PY', False):
run_py()
else:
run_rust()
+1 -1
View File
@@ -1 +1 @@
VERSION = '1.99.0'
VERSION = '2.0.2'
+3 -1
View File
@@ -3,6 +3,8 @@
"Alpine": "",
"AOSC OS": "",
"Apple": "",
"macOS": "",
"iOS": "",
"Archcraft": "",
"ArchLabs": "",
"Arch": "",
@@ -118,4 +120,4 @@
"Xorg": "",
"Zorin OS": "",
"Windows": "\uE62A"
}
}
+26
View File
@@ -0,0 +1,26 @@
# This file is automatically generated. Please do not modify.
from . import AsciiArt
bazzite = AsciiArt(match=r'''"Bazzite"*''', color='5 5', ascii=r"""
${c1} %%%%%%====%%%%%%%%%%
%%%%%%%% %%%%%%%%%%%%%%
%%%%%%%%% %%%%%%%%%%%%%%%%
%%%%%%%%% %%%%%%%%%%%%%%%###
%%%%%%%%% %%%%%%%%%%%%%######
== =======######
== =========#####
%%%%%%%%% %%%%%%%####======#####
%%%%%%%%% %%%%%#######=====#####
%%%%%%%%% %%%#########=====#####
%%%%%%%%% %%##########=====#####
%%%%%%%%%====###########=====######
%%%%%%%%====#########======######
%%%%%%%=====#####========######
%%%%###===============#######
%#######==========#########
#######################
###################
###########
""")
+15 -3
View File
@@ -208,6 +208,10 @@ def detect(name: str) -> AsciiArt | None:
from .bedrock import bedrock
return bedrock
if name.startswith('bazzite'):
from .bazzite import bazzite
return bazzite
if name.startswith('biglinux_large'):
from .biglinux_large import biglinux_large
return biglinux_large
@@ -664,9 +668,9 @@ def detect(name: str) -> AsciiArt | None:
from .interix import interix
return interix
if name.startswith('ironclad'):
from .ironclad import ironclad
return ironclad
if name.startswith('gloire'):
from .gloire import gloire
return gloire
if name.startswith('januslinux') or name.startswith('janus') or name.startswith('ataraxia linux') or name.startswith('ataraxia'):
from .januslinux import januslinux
@@ -1180,6 +1184,10 @@ def detect(name: str) -> AsciiArt | None:
from .rhaymos import rhaymos
return rhaymos
if name.startswith('rhino linux'):
from .rhino_linux import rhino_linux
return rhino_linux
if name.startswith('rocky_small'):
from .rocky_small import rocky_small
return rocky_small
@@ -1312,6 +1320,10 @@ def detect(name: str) -> AsciiArt | None:
from .t2 import t2
return t2
if name.startswith('opensuse tumbleweed-slowroll') or name.startswith('opensuse_slowroll'):
from .opensuse_tumbleweed_slowroll import opensuse_tumbleweed_slowroll
return opensuse_tumbleweed_slowroll
if name.startswith('opensuse tumbleweed') or name.startswith('opensuse_tumbleweed'):
from .opensuse_tumbleweed import opensuse_tumbleweed
return opensuse_tumbleweed
+19
View File
@@ -0,0 +1,19 @@
# This file is automatically generated. Please do not modify.
from . import AsciiArt
gloire = AsciiArt(match=r'''"Gloire"*''', color='5 7 0', ascii=r"""
${c3}
&#BGPPPPPG#&
B5?77!!?YJJ7!7YBB&
&G5YJ77!7JYYYYYBPJ&PY#
#PYYYYYY?!?YYYYY7?7JP5JJ
B?YYYYYY7!!7JYYYYJ!!?JJJ5
&& B7?J?77?7!!!!!77777!7Y5YYBBPGGG&
G77?YBB!!!!!!!!!!!!!JYJ??7JYJJY# PYPPG&
J777JB?!7JJ???!!!7?JYYYYYPJ!7JB
GYYG #JJJJJ??7!!!JYYY5PGB&GB&
#Y!?GB5YYJY5PG###&
GJJP
""")
+1 -1
View File
@@ -3,7 +3,7 @@
from . import AsciiArt
nixos_small = AsciiArt(match=r'''"nixos_small"''', color='4 6', ascii=r"""
${c1} \\ \\ //
${c1} \\ \\ //
==\\__\\/ //
// \\//
==// //==
+18 -16
View File
@@ -3,21 +3,23 @@
from . import AsciiArt
opensuse_leap = AsciiArt(match=r'''"openSUSE Leap"* | "openSUSE_Leap"*''', color='2 7', ascii=r"""
${c2} `-++:`
./oooooo/-
`:oooooooooooo:.
-+oooooooooooooooo+-`
./oooooooooooooooooooooo/-
:oooooooooooooooooooooooooo:
` `-+oooooooooooooooooooo/- `
`:oo/- .:ooooooooooooooo+:` `-+oo/.
`/oooooo:. -/oooooooooo/. ./oooooo/.
`:+ooooo+-` `:+oooo+- `:oooooo+:`
.:oooooo/. .::` -+oooooo/.
-/oooooo:. ./oooooo+-
`:+ooooo+-:+oooooo:`
./oooooooooo/.
-/oooo+:`
`:/.
${c2} ====
======
==== ====+
+==== +====
+===+ ====
==== ====
+=== +====
==== +====
===== ====
+===+ =====
==+ ===== +===+ ===
==== ==== ===== =====
==== ======= ====
==== === ====
====+ ====
==== =====
======
==
""")
+27
View File
@@ -0,0 +1,27 @@
# This file is automatically generated. Please do not modify.
from . import AsciiArt
opensuse_slowroll = AsciiArt(match=r'''"openSUSE Slowroll"* | "openSUSE_Slowroll"*''', color='2 7', ascii=r"""
${c2} _aaaymQQmwaaa,
,wWQQQD????????$QQQQa,.
_wQQB?" ??QQQa,
sQQD^ ?QQ6\
yWW' 4QQg
,QQD .aaaaaaaa ^4Q6
,mQP _wWQW?????YWWQa, 4Qm
jQ@ wWW?' ^4QQc ^$QL
,QQ' jWW' )QW\ ]QQ
|QQ ,QW' ]QQ ^QQ|
|QQ |QQ ]QQ QQ|
|QQ 4Qg ]QQ .QQ|
'QQ6 '$WQac. _QQ( jQQ
]QQw "?QWQQf _mQP ,QQ(
4QQga wQQP ,mQ?
4QQQga, saQWP' jQQf
?QQQQQQwaaaaaaaayWWW?' _mQ@'
?WQQQP?9VWUV???^ _amQP^
"4QQQaa, ,awQQQ?^
"?VQQQQQQQQQQQQQQP?'
""")
+22 -13
View File
@@ -3,18 +3,27 @@
from . import AsciiArt
opensuse_tumbleweed = AsciiArt(match=r'''"openSUSE Tumbleweed"* | "openSUSE_Tumbleweed"*''', color='2 7', ascii=r"""
${c2} ......
.,cdxxxoc,. .:kKMMMNWMMMNk:.
cKMMN0OOOKWMMXo. A ;0MWk:' ':OMMk.
;WMK;' 'lKMMNM, :NMK' 'OMW;
cMW; WMMMN ,XMK' oMM.
.MMc ''^*~l. xMN: KM0
'MM. .NMO oMM
.MM, .kMMl xMN
KM0 .kMM0' .dl>~,. .WMd
'XM0. ,OMMK' OMMM7' .XMK
*WMO:. .;xNMMk' NNNMKl. .xWMx
^ONMMNXMMMKx; V 'xNMWKkxllox0NMWk'
''''' ':dOOXXKOxl'
${c2} JJJJJJJJ
JJJJJJJJJJJJJJ
JJJJJJ =JJJJJJJ
JJJJ =JJJ JJJJ
JJJ =JJJ JJJ
JJJJ =JJJ JJJ
JJJJJJJJJJJJJJJ JJJJ
JJJJJJJJJJJJJJ JJJJ
JJJJ JJJJ
JJJJJ= JJJJ
JJJJJJJJJJJJJJJJJJJJJJJJJJJJJ=
=JJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
JJJJ =JJJJJJ
JJJJ =JJJJ
JJJJ JJJJJJJJJJJJJJ
JJJJ JJJJJJJJJJJJJJJ
JJJJ JJJJ JJJJ
JJJ JJJJ JJJ
JJJJJ JJJJ JJJJ
=JJJJJJJJ JJJJJJ
JJJJJJJJJJJJJJ
JJJJJJJ=
""")
@@ -0,0 +1,27 @@
# This file is automatically generated. Please do not modify.
from . import AsciiArt
opensuse_tumbleweed_slowroll = AsciiArt(match=r'''"openSUSE Tumbleweed-Slowroll"* | "openSUSE_Slowroll"*''', color='2 7', ascii=r"""
${c2} _aaaymQQmwaaa,
,wWQQQD????????$QQQQa,.
_wQQB?" ??QQQa,
sQQD^ ?QQ6\
yWW' 4QQg
,QQD .aaaaaaaa ^4Q6
,mQP _wWQW?????YWWQa, 4Qm
jQ@ wWW?' ^4QQc ^$QL
,QQ' jWW' )QW\ ]QQ
|QQ ,QW' ]QQ ^QQ|
|QQ |QQ ]QQ QQ|
|QQ 4Qg ]QQ .QQ|
'QQ6 '$WQac. _QQ( jQQ
]QQw "?QWQQf _mQP ,QQ(
4QQga wQQP ,mQ?
4QQQga, saQWP' jQQf
?QQQQQQwaaaaaaaayWWW?' _mQ@'
?WQQQP?9VWUV???^ _amQP^
"4QQQaa, ,awQQQ?^
"?VQQQQQQQQQQQQQQP?'
""")
+27
View File
@@ -0,0 +1,27 @@
# This file is automatically generated. Please do not modify.
from . import AsciiArt
rhino_linux = AsciiArt(match=r'''"Rhino Linux"*''', color='5 4', ascii=r"""
${c1}
${c1} ..','.. l.
.,coooooooooc,..ool
;oooooooooooooooooooo.,coo.
;oooooooooooooooooooooooooo ,
.ooooooooooooooooooooooooooo . ,o
oollcccccccccllloooooooooooolcoo..oo
ll;':::::::::::::::ccloooooooooooooo:
.;''';::::::::::::::::::ccloooooooooo
''''',:::::::::::::::::::::::cclllllc
'''''';::::::::::::::::::::::::::::::
${c1}.'''''';::::::::::::::::${c2}ll${c1}:::::::::::
${c1} ''''''';:::::::::::::::${c2}d0${c1}::
${c1}.''''''',;:::::::${c2}cc${c1}::::${c2}0N:'
${c1}''''''''',,;::::${c2}cK${c1}:::${c2}dMX,
${c1}'''''''''''',,,${c2}dMccOMM${c1}:
${c1}.''''''''''''${c2}cWM0WMM${c1}.
${c1}'''''''':${c2}kMMMMM${c1}.
${c2}odxONMMMM
;Kl
""")
+9 -8
View File
@@ -7,7 +7,7 @@ import importlib.util
import json
import random
import traceback
from itertools import permutations
from itertools import permutations, islice
from math import ceil
from . import termenv, neofetch_util, pride_month
@@ -251,7 +251,7 @@ def create_config() -> Config:
slots = list(set(map(int, re.findall('(?<=\\${c)[0-9](?=})', asc))))
while len(pis) < len(slots):
pis += pis
perm = {p[:len(slots)] for p in permutations(pis)}
perm = {p[:len(slots)] for p in islice(permutations(pis), 1000)}
random_count = max(0, ascii_per_row * ascii_rows - len(arrangements))
if random_count > len(perm):
choices = perm
@@ -309,7 +309,7 @@ def create_config() -> Config:
printc('- &bqwqfetch&r: Pure python, &nminimal dependencies&r ' +
('&c(Not installed)' if not has_qwqfetch else ''))
print()
# Use fastfetch as the default backend if it is installed
def_backend = 'neofetch' if ff_path is None else 'fastfetch'
@@ -382,7 +382,7 @@ def run():
GLOBAL_CFG.use_overlay = args.overlay
if args.version:
print(f'Version is {VERSION}')
print(f'Version is 1.99.∞\n(python hyfetch legacy mode)')
return
# Ensure git bash for windows
@@ -414,17 +414,18 @@ def run():
# Check if it's June (pride month)
now = datetime.datetime.now()
june_path = CACHE_PATH / f'animation-displayed-{now.year}'
show_for_june = False
if now.month == 6 and now.year not in config.pride_month_shown and not june_path.is_file() and os.isatty(sys.stdout.fileno()):
args.june = True
show_for_june = True
if args.june and not config.pride_month_disable:
if (args.june or show_for_june) and not config.pride_month_disable:
pride_month.start_animation()
print()
print("Happy pride month!")
print("(You can always view the animation again with `hyfetch --june`)")
print()
if not june_path.is_file():
if not june_path.is_file() and not args.june: # If --june wasn't explicitly specified...
june_path.parent.mkdir(parents=True, exist_ok=True)
june_path.touch()
@@ -440,7 +441,7 @@ def run():
config.backend = args.backend
if args.args:
config.args = args.args
# Random preset
if config.preset == 'random':
config.preset = random.choice(list(PRESETS.keys()))
+5 -3
View File
@@ -333,7 +333,7 @@ def run_qwqfetch(asc: str, args: str = ''):
import qwqfetch
# distro_detector only return a bash variable
# so we use qwqfetch builtin distro detector
print(qwqfetch.get_ascres(asc))
print(qwqfetch.get_ascres(asc))
except ImportError as e: # module not found etc
print("qwqfetch is not installed. Install it by executing:") # use print to output hint directly
print("pip install git+https://github.com/nexplorer-3e/qwqfetch") # TODO: public repo
@@ -379,17 +379,19 @@ def run_fastfetch(asc: str, args: str = '', legacy: bool = False):
"""
# Find fastfetch binary
ff_path = fastfetch_path()
if not ff_path:
printc("&cError: fastfetch binary is not found. Please install fastfetch first.")
exit(127)
# Write temp file
with TemporaryDirectory() as tmp_dir:
tmp_dir = Path(tmp_dir)
path = tmp_dir / 'ascii.txt'
path.write_text(asc, 'utf-8')
os.environ['FFTS_IGNORE_PARENT'] = '1'
# Call fastfetch with the temp file
proc = subprocess.run([str(ff_path), '--raw' if legacy else '--file-raw',
str(path.absolute()), *shlex.split(args)])
+95 -48
View File
@@ -300,6 +300,9 @@ PRESETS: dict[str, ColorProfile] = {
'#ABABAB',
'#000000'
]),
# https://orientation.fandom.com/wiki/Fictosexual
'fictosexual': ColorProfile(["#000000", "#C4C4C4", "#A349A5", "#C4C4C4", "#000000"]),
# aroace1 sourced from https://flag.library.lgbt/flags/aroace/
'aroace1': ColorProfile([
@@ -550,6 +553,27 @@ PRESETS: dict[str, ColorProfile] = {
"3ecdf9",
"fff48d",
]),
# https://lgbtqia.wiki/wiki/Gendernull
'nullflux': ColorProfile([
'#0B0C0E', '#A28DB9', '#E1D4EF', '#F0E6DD', '#665858',
]),
'hypergender': ColorProfile([
"#EFEFEF", "#FFFFFF", "#FBFF75", "#000000", "#FBFF75", "#FFFFFF", "#EFEFEF",
]),
'hyperboy': ColorProfile([
"#EFEFEF", "#FFFFFF", "#74D7FE", "#000000", "#74D7FE", "#FFFFFF", "#EFEFEF",
]),
'hypergirl': ColorProfile([
"#EFEFEF", "#FFFFFF", "#FC76D3", "#000000", "#FC76D3", "#FFFFFF", "#EFEFEF",
]),
'hyperandrogyne': ColorProfile([
"#EFEFEF", "#FFFFFF", "#BB83FF", "#000000", "#BB83FF", "#FFFFFF", "#EFEFEF",
]),
'hyperneutrois': ColorProfile([
"#EFEFEF", "#FFFFFF", "#BAFA74", "#000000", "#BAFA74", "#FFFFFF", "#EFEFEF",
]),
"finsexual": ColorProfile([
"#B18EDF",
@@ -704,54 +728,57 @@ PRESETS: dict[str, ColorProfile] = {
# sourced from https://commons.wikimedia.org/wiki/File:Bear_Brotherhood_flag.svg
'bear': ColorProfile([
'#623804',
'#D56300',
'#FEDD63',
'#FEE6B8',
'#FFFFFF',
'#555555',
'#D56300',
'#FEDD63',
'#FEE6B8',
'#FFFFFF',
'#555555',
]),
# colorpicked from https://commons.wikimedia.org/wiki/File:Butch_Flag.png
'butch': ColorProfile([
'#D72800',
'#F17623',
'#FF9C56',
'#FFFDF6',
'#FFCE89',
'#FEAF02',
'#A37000',
'#D72800',
'#F17623',
'#FF9C56',
'#FFFDF6',
'#FFCE89',
'#FEAF02',
'#A37000',
]),
# colorpicked from https://commons.wikimedia.org/wiki/File:Leather,_Latex,_and_BDSM_pride_-_Light.svg
'leather': ColorProfile([
'#000000',
'#252580',
'#000000',
'#252580',
'#FFFFFF',
'#252580',
'#000000',
'#252580',
'#000000',
'#000000',
'#252580',
'#000000',
'#252580',
'#FFFFFF',
'#252580',
'#000000',
'#252580',
'#000000',
]),
# colorpicked from https://commons.wikimedia.org/wiki/File:Official_Otter_Pride_Flag_by_Bearbackgear.jpg
'otter': ColorProfile([
'#263881',
'#5C9DC9',
'#FFFFFF',
'#3A291D',
'#5C9DC9',
'#263881',
'#263881',
'#5C9DC9',
'#FFFFFF',
'#3A291D',
'#5C9DC9',
'#263881',
]),
# colorpicked from https://commons.wikimedia.org/wiki/File:Twink_Pride_Flag_(proposed).svg
'twink': ColorProfile([
'#FFB2FF',
'#FFFFFF',
'#FFFF81',
'#FFB2FF',
'#FFFFFF',
'#FFFF81',
]),
# https://en.wikipedia.org/wiki/File:FatFetishFlag.png
'adipophilia': ColorProfile(["#000000", "#E16180", "#FFF9BE", "#603E41", "#000000"]),
'kenochoric': ColorProfile([
'#000000',
'#2E1569',
@@ -924,21 +951,9 @@ PRESETS: dict[str, ColorProfile] = {
"#4a8123"
]),
# Meme flags
'beiyang': ColorProfile([
'#DF1B12',
'#FFC600',
'#01639D',
'#FFFFFF',
'#000000',
]),
'burger': ColorProfile([
'#F3A26A',
'#498701',
'#FD1C13',
'#7D3829',
'#F3A26A',
'cisgender': ColorProfile([
"#D70270",
"#0038A7"
]),
# colors from Gilbert Baker's original 1978 flag design
@@ -963,5 +978,37 @@ PRESETS: dict[str, ColorProfile] = {
'#cfad8c',
'#b77b55',
'#954e31'
])
]),
# Meme flags
'beiyang': ColorProfile([
'#DF1B12',
'#FFC600',
'#01639D',
'#FFFFFF',
'#000000',
]),
'burger': ColorProfile([
'#F3A26A',
'#498701',
'#FD1C13',
'#7D3829',
'#F3A26A',
]),
'throatlozenges': ColorProfile([
"#2759DA",
"#03940D",
"#F5F100",
"#F59B00",
"#B71212"
]),
'band': ColorProfile([
"#2670c0",
"#f5bd00",
"#dc0045",
"#e0608e"
]),
}
+2 -2
View File
@@ -105,8 +105,8 @@ def start_animation():
try:
while 1:
# Clear the screen
print("\033[2J\033[H", end="")
# Move cursor to the top left corner
print("\033[H", end="")
draw_frame()
frame += speed
sleep(frame_delay)
+13
View File
@@ -0,0 +1,13 @@
from . import main
from .color_util import printc
def run_py():
try:
main.run()
except KeyboardInterrupt:
printc('&cThe program is interrupted by ^C, exiting...')
exit(0)
if __name__ == '__main__':
run_py()
+27
View File
@@ -0,0 +1,27 @@
import os
import platform
import subprocess
import sys
from pathlib import Path
from .color_util import printc
from .py import run_py
def run_rust():
# Find the rust executable
pd = Path(__file__).parent.joinpath('rust')
pd = pd.joinpath('hyfetch.exe' if platform.system() == 'Windows' else 'hyfetch')
if not pd.exists():
if 'HYFETCH_DONT_WARN_RUST' not in os.environ:
printc('&cThe executable for hyfetch v2 (rust) is not found, falling back to legacy v1.99.∞ (python).\n'
'You can add environment variable HYFETCH_DONT_WARN_RUST=1 to suppress this warning.\n')
run_py()
return
# Run the rust executable, passing in all arguments
subprocess.run([str(pd)] + sys.argv[1:])
if __name__ == '__main__':
run_rust()
+421 -324
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "neowofetch",
"version": "1.99.0",
"version": "2.0.2",
"description": "Updated neofetch",
"repository": {
"type": "git",
+18
View File
@@ -0,0 +1,18 @@
edition = "2021"
# empty_item_single_line = true
# error_on_line_overflow = true
# format_code_in_doc_comments = true
# format_strings = true
# group_imports = "StdExternalCrate"
# imports_granularity = "Module"
# imports_layout = "Mixed"
match_block_trailing_comma = true
newline_style = "Unix"
# normalize_comments = true
# normalize_doc_attributes = true
# overflow_delimited_expr = true
# reorder_impl_items = true
use_field_init_shorthand = true
use_try_shorthand = true
# wrap_comments = true
+8 -3
View File
@@ -6,8 +6,10 @@ from setuptools import setup, find_namespace_packages
HERE = Path(__file__).parent
# Load version without importing it (see issue #192 if you are confused)
for l in (HERE / 'hyfetch' / '__version__.py').read_text().strip().splitlines():
exec(l)
VERSION = [l for l in (HERE / "Cargo.toml").read_text('utf-8').splitlines() if l.startswith("version = ")]
if len(VERSION) != 1:
raise ValueError(f"Cannot determine version from Cargo.toml: {VERSION}")
VERSION = VERSION[0].split('"')[1]
# The text of the README file
README = (HERE / "README.md").read_text('utf-8')
@@ -32,6 +34,7 @@ setup(
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
],
packages=find_namespace_packages(exclude=("tools", "tools.*")),
package_data={'hyfetch': ['hyfetch/*']},
@@ -46,7 +49,9 @@ setup(
],
entry_points={
"console_scripts": [
"hyfetch=hyfetch.main:run",
"hyfetch.v1=hyfetch.__main__:run_py",
"hyfetch.rs=hyfetch.__main__:run_rust",
"hyfetch=hyfetch.__main__:run_rust",
]
},
scripts=['hyfetch/scripts/neowofetch']
+26 -13
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
FASTFETCH_VERSION="2.28.0"
FASTFETCH_VERSION="2.32.1"
FASTFETCH_DL="https://github.com/fastfetch-cli/fastfetch/releases/download/$FASTFETCH_VERSION/"
# Get script directory
@@ -23,6 +23,9 @@ python3 setup.py sdist bdist_wheel
twine check dist/*.tar.gz
twine check dist/*.whl
# Build rust package
"$DIR/build_rust.sh"
# =================
# Build for windows
cd dist
@@ -44,19 +47,24 @@ cp -r git/ wheel/hyfetch/
# Embed fastfetch binary
echo "> Embedding fastfetch binary"
wget -q "$FASTFETCH_DL/fastfetch-windows-i686.zip" -O fastfetch-windows.zip
wget -q "$FASTFETCH_DL/fastfetch-windows-amd64.zip" -O fastfetch-windows.zip
mkdir -p wheel/hyfetch/fastfetch
bsdtar -zxf fastfetch-windows.zip -C wheel/hyfetch/fastfetch
rm -rf fastfetch-windows.zip
# Embed rust binary
echo "> Embedding rust binary"
mkdir -p wheel/hyfetch/rust
cp "$DIR/../target/x86_64-pc-windows-gnu/release/hyfetch.exe" wheel/hyfetch/rust/
# Edit .dist-info/WHEEL "Tag: {platform}" and rehash
sed -i 's/Tag: py3-none-.*/Tag: py3-none-win32/' wheel/*.dist-info/WHEEL
python "$DIR/build_rehash.py" wheel
# Zip to -win32.whl
new_name=${file/-any/-win32}
cd wheel && zip -qq -y -r "../$new_name" * && cd ..
twine check "$new_name"
#new_name=${file/-any/-win32}
#cd wheel && zip -qq -y -r "../$new_name" * && cd ..
#twine check "$new_name"
# Zip to -win_amd64.whl
# Since pypi doesn't allow two identical files with different names to be uploaded
@@ -76,6 +84,7 @@ rm -rf wheel/git
function build_for_platform() {
ff_platform=$1
wheel_platform=$2
rust_platform=$3
echo "Building for $ff_platform"
@@ -91,6 +100,10 @@ function build_for_platform() {
bsdtar -zxf "fastfetch-$ff_platform.zip" -C wheel/hyfetch/fastfetch --strip-components 1
rm -rf "fastfetch-$ff_platform.zip"
# Copy rust binary to wheel/hyfetch/rust
mkdir -p wheel/hyfetch/rust
cp "$DIR/../target/$rust_platform/release/hyfetch" wheel/hyfetch/rust
# Change the file name
new_name=${file/-any/-"$wheel_platform"}
@@ -107,19 +120,19 @@ function build_for_platform() {
# See https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/
# The official fastfetch build uses Ubuntu 20.04 with glibc 2.31
build_for_platform "linux-amd64" "manylinux_2_31_x86_64"
build_for_platform "linux-aarch64" "manylinux_2_31_aarch64"
build_for_platform "linux-armv7l" "manylinux_2_31_armv7l"
# build_for_platform "linux-riscv64" "manylinux_2_31_riscv64"
build_for_platform "linux-amd64" "manylinux_2_31_x86_64" "x86_64-unknown-linux-musl"
build_for_platform "linux-aarch64" "manylinux_2_31_aarch64" "aarch64-unknown-linux-musl"
build_for_platform "linux-armv7l" "manylinux_2_31_armv7l" "armv7-unknown-linux-musleabihf"
# There doesn't seem to be tags for freebsd?
# build_for_platform "freebsd-amd64" "freebsd_x86_64"
# build_for_platform "freebsd-aarch64" "freebsd_aarch64"
build_for_platform "musl-amd64" "musllinux_1_1_x86_64"
build_for_platform "musl-amd64" "musllinux_1_1_x86_64" "x86_64-unknown-linux-musl"
# build_for_platform "musl-aarch64" "musllinux_1_1_aarch64"
# The official fastfetch build uses macOS 12.0
build_for_platform "macos-universal" "macosx_11_0_x86_64"
build_for_platform "macos-universal" "macosx_11_0_arm64"
# TODO: linux_riscv64
build_for_platform "macos-universal" "macosx_11_0_x86_64" "x86_64-apple-darwin"
build_for_platform "macos-universal" "macosx_11_0_arm64" "aarch64-apple-darwin"
# TODO: linux_riscv64 (pypi's platform tag support is not there yet)
# build_for_platform "linux-riscv64" "manylinux_2_31_riscv64"
# Finally, remove temporary files
rm -rf wheel git
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
echo "Building the Docker image..."
docker build -t rust-builder-general tools/rust-builder/general
echo "Running the Docker container..."
docker run --rm -v "$(pwd)":/app rust-builder-general bash -c "cd /app && ./tools/rust-builder/general/build.sh"
echo "Building MacOS Docker image..."
docker build -t rust-builder-osx tools/rust-builder/osx
echo "Running MacOS Docker container..."
docker run --rm -v "$(pwd)":/app rust-builder-osx bash -c "cd /app && ./tools/rust-builder/osx/build.sh"
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# This script does some file path magic to make cargo publish work without having
# to have neofetch & font logos in the cargo root.
# ...okay basically it copies these files over before publishing.
set -euo pipefail
# Get the directory of SRC root which is ../ from this script
SRC_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CARGO_ROOT="$SRC_ROOT/crates/hyfetch"
# Copy neofetch from here to cargo root
cp "$SRC_ROOT/neofetch" "$CARGO_ROOT/"
mkdir -p "$CARGO_ROOT/hyfetch"
cp -r "$SRC_ROOT/hyfetch/data" "$CARGO_ROOT/hyfetch/"
# Build the crate
cargo publish -p hyfetch --allow-dirty "$@"
# Delete the copied files
rm "$CARGO_ROOT/neofetch"
rm -rf "$CARGO_ROOT/hyfetch"
echo "Done!"
+20 -4
View File
@@ -51,8 +51,15 @@ def edit_versions(version: str):
path = Path('hyfetch/__version__.py')
content = [f"VERSION = '{version}'" if l.startswith('VERSION = ') else l for l in path.read_text().split('\n')]
path.write_text('\n'.join(content))
# 3. Cargo.toml
print('Editing Cargo.toml...')
path = Path('Cargo.toml')
content = path.read_text()
content = re.sub(r'(?<=^version = ")[^"]+(?="$)', version, content, flags=re.MULTILINE)
path.write_text(content)
# 3. README.md
# 4. README.md
print('Editing README.md...')
path = Path('README.md')
content = path.read_text()
@@ -61,7 +68,7 @@ def edit_versions(version: str):
content = content[:changelog_i] + f'\n\n### {version}' + content[changelog_i:]
path.write_text(content)
# 4. neofetch script
# 5. neofetch script
print('Editing neofetch...')
path = Path('neofetch')
lines = path.read_text().replace("\t", " ").split('\n')
@@ -93,7 +100,7 @@ def finalize_neofetch():
# 2. Regenerate man page
print('Regenerating neofetch man page...')
Path('docs/neofetch.1').write_text(subprocess.check_output(['help2man', './neofetch']).decode())
Path('docs/hyfetch.1').write_text(subprocess.check_output(['help2man', 'python3 -m hyfetch']).decode())
Path('docs/hyfetch.1').write_text(subprocess.check_output(['help2man', 'cargo run --']).decode())
# 3. Reformat readme links
print('Reformatting readme links...')
@@ -125,7 +132,12 @@ def create_release(v: str):
subprocess.check_call(['git', 'tag', f'neofetch-{NEOFETCH_NEW_VERSION}'])
i = input('Please check the commit is correct. Press y to continue or any other key to cancel.')
assert i == 'y'
if i.lower() != 'y':
print('Aborting...')
subprocess.check_call(['git', 'reset', '--hard', 'HEAD~1'])
subprocess.check_call(['git', 'tag', '-d', v])
subprocess.check_call(['git', 'tag', '-d', f'neofetch-{NEOFETCH_NEW_VERSION}'])
exit(1)
# 4. Push
print('Pushing commits...')
@@ -140,6 +152,10 @@ def deploy():
print('Deploying to pypi...')
subprocess.check_call(['bash', 'tools/deploy.sh'])
print('Done!')
print('Deploying to crates.io...')
subprocess.check_call(['bash', 'tools/deploy-crate.sh'])
print('Done!')
print('Deploying to npm...')
otp = input('Please provide 2FA OTP for NPM: ')
+27
View File
@@ -0,0 +1,27 @@
FROM debian
# Install Rust building environment
RUN apt update && apt install -y curl build-essential gcc libssl-dev bash musl-tools mingw-w64 \
gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# Add toolchains for building for musl[x86_64, aarch64, armv7l, riscv64], windows[x86_64]
RUN /root/.cargo/bin/rustup target add \
x86_64-unknown-linux-musl aarch64-unknown-linux-musl armv7-unknown-linux-musleabihf riscv64gc-unknown-linux-musl \
x86_64-pc-windows-gnu
# Compile musl for multiple architectures
RUN curl "https://musl.libc.org/releases/musl-1.2.5.tar.gz" --output musl.tgz && \
tar -xvf musl.tgz && \
cd musl-*/ && \
./configure --prefix=/musl/aarch64 --exec-prefix=/musl/aarch64 --target=aarch64-linux-gnu --enable-wrapper=all && \
make -j18 install && \
make clean && \
./configure --prefix=/musl/armv7l --exec-prefix=/musl/armv7l --target=arm-linux-gnueabihf --enable-wrapper=all && \
make -j18 install && \
make clean && \
./configure --prefix=/musl/riscv64 --exec-prefix=/musl/riscv64 --target=riscv64-linux-gnu --enable-wrapper=all && \
make -j18 install && \
make clean && \
cd .. && \
rm -rf musl-*
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -e
# Add cargo to path
export PATH="$HOME/.cargo/bin:$PATH"
cd /app
# Set linkers for cross-compilation
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/musl/aarch64/bin/musl-gcc
export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER=/musl/armv7l/bin/musl-gcc
export CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_LINKER=/musl/riscv64/bin/musl-gcc
# Build for windows
cargo build --release --target x86_64-pc-windows-gnu
# Add -C relocation-model=static to the build command
# See https://github.com/rust-lang/rust/issues/95926
export RUSTFLAGS="-C relocation-model=static"
# Build for platforms
cargo build --release --target x86_64-unknown-linux-musl
cargo build --release --target aarch64-unknown-linux-musl
cargo build --release --target armv7-unknown-linux-musleabihf
#cargo build --release --target riscv64gc-unknown-linux-musl
+18
View File
@@ -0,0 +1,18 @@
FROM crazymax/osxcross:11.3-alpine AS osxcross
FROM alpine
# Install Rust building environment
RUN apk add --no-cache curl gcc musl-dev openssl-dev bash clang lld
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# Add toolchains for building for macOS[x86_64, aarch64]
RUN /root/.cargo/bin/rustup toolchain install beta && \
/root/.cargo/bin/rustup default beta
RUN /root/.cargo/bin/rustup target add x86_64-apple-darwin aarch64-apple-darwin
# Copy osxcross toolchain
# RUN apt-get update && apt-get install -y clang lld libc6-dev
ENV PATH="/osxcross/bin:$PATH"
ENV LD_LIBRARY_PATH="/osxcross/lib:$LD_LIBRARY_PATH"
COPY --from=osxcross /osxcross /osxcross
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -e
# Add cargo to path
export PATH="$HOME/.cargo/bin:$PATH"
cd /app
# OSX linker and ar
export OSX_VER=20.4
export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=/osxcross/bin/x86_64-apple-darwin$OSX_VER-clang
export CARGO_TARGET_X86_64_APPLE_DARWIN_AR=/osxcross/bin/x86_64-apple-darwin$OSX_VER-ar
export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=/osxcross/bin/aarch64-apple-darwin$OSX_VER-clang
export CARGO_TARGET_AARCH64_APPLE_DARWIN_AR=/osxcross/bin/aarch64-apple-darwin$OSX_VER-ar
# Build for platforms
# +beta because rust doesn't know which strip to use until version 1.84
cargo +beta build --release --target x86_64-apple-darwin
cargo +beta build --release --target aarch64-apple-darwin