Initial commit
This commit is contained in:
commit
0c9c35b971
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
1251
Cargo.lock
generated
Normal file
1251
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[workspace]
|
||||
members = ["pfetch-extractor"]
|
||||
|
||||
[package]
|
||||
name = "pfetch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Gobidev"]
|
||||
description = "A rewrite of the pfetch system information tool"
|
||||
repository = "https://github.com/Gobidev/pfetch-rs"
|
||||
license = "MIT"
|
||||
keywords = ["fetch", "pfetch", "cli", "system"]
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pfetch-extractor = { path = "./pfetch-extractor" }
|
||||
globset = "0.4.10"
|
||||
dotenv = "0.15.0"
|
||||
glob = "0.3.1"
|
||||
which = "4.4.0"
|
||||
libmacchina = "6"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2023 Adrian Groh
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
149
README.md
Normal file
149
README.md
Normal file
@ -0,0 +1,149 @@
|
||||
<h1 align="center">pfetch-rs</h1>
|
||||
<p align="center">A rewrite of the pfetch system information tool by dylanaraps in Rust</p><br>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/50576978/219375863-579c495d-8db8-4aa9-a4a6-348ecb2c849f.png" width="350px"></p>
|
||||
|
||||
## About
|
||||
|
||||
If you are familiar with the [pfetch](https://github.com/dylanaraps/pfetch)
|
||||
system information tool by [dylanaraps](https://github.com/dylanaraps), this
|
||||
does the exact same thing, but with an about _10x faster_ runtime. _pfetch_ is
|
||||
simple by design with some (but not many) configuration options and a
|
||||
minimalistic look.
|
||||
|
||||
**Supported Platforms:** Linux, Android, DragonflyBSD, FreeBSD, NetBSD, OpenBSD,
|
||||
WSL, Haiku, MacOS, Minix, Solaris, IRIX, SerenityOS
|
||||
|
||||
_Disclaimer: Aside from Linux, all of these platforms are untested. If you run
|
||||
into problems, please open an issue._
|
||||
|
||||
**Included Logos:** Alpine Linux, Android, Arch Linux, ArcoLinux, Artix Linux,
|
||||
Bedrock Linux, Buildroot, CelOS, CentOS, Crystal Linux, dahliaOS, Debian,
|
||||
Devuan, DragonflyBSD, Elementary OS, EndeavourOS, Fedora, FreeBSD, Garuda Linux,
|
||||
Gentoo Linux, Gnu, Guix, Haiku, HydroOS, Hyperbola, instantOS, IRIX, KDE neon,
|
||||
Linux Lite, Linux, Mint, macOS, Mageia, Manjaro, Minix, MX Linux, NetBSD, NixOS,
|
||||
OpenBSD, openSUSE Tumbleweed, openSUSE Leap, OpenWrt, Parabola, Pop!\_OS
|
||||
(updated), PureOS, Raspbian, SerenityOS, Slackware, Solus, Solaris, Ubuntu, Void
|
||||
Linux, Xeonix Linux, Fiwix (new), MorphOS (new), AmogOS (new), Aperio (new)
|
||||
|
||||
For all other distributions, a penguin will be displayed.
|
||||
|
||||
_Credit to [the original pfetch](https://github.com/dylanaraps/pfetch) and
|
||||
[its contributors](https://github.com/dylanaraps/pfetch/graphs/contributors)._
|
||||
|
||||
If you want to add a logo, feel free to make a Pull Request.
|
||||
|
||||
## Status
|
||||
|
||||
This project is still in early development, expect things to not work properly.
|
||||
Please open issues for bugs you are encountering.
|
||||
|
||||
## Installation
|
||||
|
||||
### Cargo
|
||||
|
||||
```sh
|
||||
cargo install pfetch
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
Benchmarks performed on an AMD Ryzen 5 3600. Execution time is measured using
|
||||
[hyperfine](https://github.com/sharkdp/hyperfine) with `-w 4 -m 500 -N` flags
|
||||
|
||||
| Implementation | Mean [ms] | Min [ms] | Max [ms] |
|
||||
| :---------------: | :--------: | :------: | :------: |
|
||||
| POSIX `sh` (bash) | 27.3 ± 0.9 | 25.3 | 23.2 |
|
||||
| POSIX `sh` (dash) | 19.3 ± 0.6 | 18.3 | 24.0 |
|
||||
| Rust | 2.1 ± 0.2 | 1.8 | 3.6 |
|
||||
|
||||
_Note: This is with `pacman` being the only installed package manager.
|
||||
Especially having `nix` installed will have a big impact on performance, as
|
||||
querying installed `nix` packages is very costly._
|
||||
|
||||
## Configuration
|
||||
|
||||
Like the original `pfetch`, `pfetch-rs` is configured through environment
|
||||
variables. Your existing config will probably still work, the only difference is
|
||||
how padding is configured.
|
||||
|
||||
If you want to display a custom logo, you will have to download the source code,
|
||||
make your changes to `./pfetch-extractor/logos.sh` and build the binary with
|
||||
`cargo b --release`.
|
||||
|
||||
```sh
|
||||
# Which information to display.
|
||||
# Default: first example below
|
||||
# Valid: space separated string
|
||||
#
|
||||
# OFF by default: shell editor wm de palette
|
||||
PF_INFO="ascii title os host kernel uptime pkgs memory"
|
||||
|
||||
# Example: Only ASCII.
|
||||
PF_INFO="ascii"
|
||||
|
||||
# Example: Only Information.
|
||||
PF_INFO="title os host kernel uptime pkgs memory"
|
||||
|
||||
# A file containing environment variables to source before running pfetch.
|
||||
# Default: unset
|
||||
# Valid: A shell script
|
||||
PF_SOURCE=""
|
||||
|
||||
# Separator between info name and info data.
|
||||
# Default: unset
|
||||
# Valid: string
|
||||
PF_SEP=":"
|
||||
|
||||
# Enable/Disable colors in output:
|
||||
# Default: 1
|
||||
# Valid: 1 (enabled), 0 (disabled)
|
||||
PF_COLOR=1
|
||||
|
||||
# Color of info names:
|
||||
# Default: unset (auto)
|
||||
# Valid: 0-9
|
||||
PF_COL1=4
|
||||
|
||||
# Color of info data:
|
||||
# Default: unset (auto)
|
||||
# Valid: 0-9
|
||||
PF_COL2=9
|
||||
|
||||
# Color of title data:
|
||||
# Default: unset (auto)
|
||||
# Valid: 0-9
|
||||
PF_COL3=1
|
||||
|
||||
# Alignment paddings (this is different to the original version).
|
||||
# Default: unset (auto)
|
||||
# Valid: int
|
||||
PF_PAD1=""
|
||||
PF_PAD2=""
|
||||
PF_PAD3=""
|
||||
|
||||
# Which ascii art to use.
|
||||
# Default: unset (auto)
|
||||
# Valid: string
|
||||
PF_ASCII="openbsd"
|
||||
|
||||
# The below environment variables control more
|
||||
# than just 'pfetch' and can be passed using
|
||||
# 'HOSTNAME=cool_pc pfetch' to restrict their
|
||||
# usage solely to 'pfetch'.
|
||||
|
||||
# Which user to display.
|
||||
USER=""
|
||||
|
||||
# Which hostname to display.
|
||||
HOSTNAME=""
|
||||
|
||||
# Which editor to display.
|
||||
EDITOR=""
|
||||
|
||||
# Which shell to display.
|
||||
SHELL=""
|
||||
|
||||
# Which desktop environment to display.
|
||||
XDG_CURRENT_DESKTOP=""
|
||||
|
||||
```
|
||||
15
pfetch-extractor/Cargo.toml
Normal file
15
pfetch-extractor/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "pfetch-extractor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Gobidev"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.50"
|
||||
quote = "1.0.23"
|
||||
regex = "1.7.1"
|
||||
709
pfetch-extractor/logos.sh
Normal file
709
pfetch-extractor/logos.sh
Normal file
@ -0,0 +1,709 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Most of the below logos are from pfetch by Dylan Araps licensed under the MIT License
|
||||
# The MIT License (MIT)
|
||||
|
||||
# Copyright (c) 2016-2019 Dylan Araps
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
case ${1:-${PF_ASCII:-${distro:-$os}}} in
|
||||
[Aa]lpine*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c4} /\\ /\\
|
||||
/${c7}/ ${c4}\\ \\
|
||||
/${c7}/ ${c4}\\ \\
|
||||
/${c7}// ${c4}\\ \\
|
||||
${c7}// ${c4}\\ \\
|
||||
${c4}\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Aa]ndroid*)
|
||||
read_ascii 2 <<- EOF
|
||||
${c2} ;, ,;
|
||||
${c2} ';,.-----.,;'
|
||||
${c2} ,' ',
|
||||
${c2} / O O \\
|
||||
${c2}| |
|
||||
${c2}'-----------------'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Aa]rch*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c6} /\\
|
||||
${c6} / \\
|
||||
${c6} /\\ \\
|
||||
${c4} / \\
|
||||
${c4} / ,, \\
|
||||
${c4} / | | -\\
|
||||
${c4} /_-'' ''-_\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Aa]rco*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c4} /\\
|
||||
${c4} / \\
|
||||
${c4} / /\\ \\
|
||||
${c4} / / \\ \\
|
||||
${c4} / / \\ \\
|
||||
${c4} / / _____\\ \\
|
||||
${c4}/_/ \`----.\\_\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Aa]rtix*)
|
||||
read_ascii 6 <<- EOF
|
||||
${c4} /\\
|
||||
${c4} / \\
|
||||
${c4} /\`'.,\\
|
||||
${c4} / ',
|
||||
${c4} / ,\`\\
|
||||
${c4} / ,.'\`. \\
|
||||
${c4}/.,'\` \`'.\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Bb]edrock*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c7}__
|
||||
${c7}\\ \\___
|
||||
${c7} \\ _ \\
|
||||
${c7} \\___/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Bb]uildroot*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c3} ___
|
||||
${c3} / \` \\
|
||||
${c3}| : :|
|
||||
${c3}-. _:__.-
|
||||
${c3} \` ---- \`
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Cc]el[Oo][Ss]*)
|
||||
read_ascii 5 <<- EOF
|
||||
${c5} .////\\\\\//\\.
|
||||
${c5} //_ \\\\
|
||||
${c5} /_ ${c7}##############
|
||||
${c5} // *\\
|
||||
${c7}############### ${c5}|#
|
||||
${c5} \/ */
|
||||
${c5} \* ${c7}##############
|
||||
${c5} */, .//
|
||||
${c5} '_///\\\\\//_'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Cc]ent[Oo][Ss]*)
|
||||
read_ascii 5 <<- EOF
|
||||
${c2} ____${c3}^${c5}____
|
||||
${c2} |\\ ${c3}|${c5} /|
|
||||
${c2} | \\ ${c3}|${c5} / |
|
||||
${c5}<---- ${c4}---->
|
||||
${c4} | / ${c2}|${c3} \\ |
|
||||
${c4} |/__${c2}|${c3}__\\|
|
||||
${c2} v
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Cc]rystal*[Ll]inux)
|
||||
read_ascii 5 <<- EOF
|
||||
${c5} -//.
|
||||
${c5} -//.
|
||||
${c5} -//. .
|
||||
${c5} -//. '//-
|
||||
${c5} /+: :+/
|
||||
${c5} .//' .//.
|
||||
${c5} . .//.
|
||||
${c5} .//.
|
||||
${c5} .//.
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Dd]ahlia*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c1} _
|
||||
${c1} ___/ \\___
|
||||
${c1} | _-_ |
|
||||
${c1} | / \ |
|
||||
${c1}/ | | \\
|
||||
${c1}\\ | | /
|
||||
${c1} | \ _ _ / |
|
||||
${c1} |___ - ___|
|
||||
${c1} \\_/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Dd]ebian*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c1} _____
|
||||
${c1} / __ \\
|
||||
${c1}| / |
|
||||
${c1}| \\___-
|
||||
${c1}-_
|
||||
${c1} --_
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Dd]evuan*)
|
||||
read_ascii 6 <<- EOF
|
||||
${c4} ..:::.
|
||||
${c4} ..-==-
|
||||
${c4} .+#:
|
||||
${c4} =@@
|
||||
${c4} :+%@#:
|
||||
${c4}.:=+#@@%*:
|
||||
${c4}#@@@#=:
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Dd]ragon[Ff]ly*)
|
||||
read_ascii 1 <<- EOF
|
||||
,${c1}_${c7},
|
||||
('-_${c1}|${c7}_-')
|
||||
>--${c1}|${c7}--<
|
||||
(_-'${c1}|${c7}'-_)
|
||||
${c1}|
|
||||
${c1}|
|
||||
${c1}|
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ee]lementary*)
|
||||
read_ascii <<- EOF
|
||||
${c7} _______
|
||||
${c7} / ____ \\
|
||||
${c7}/ | / /\\
|
||||
${c7}|__\\ / / |
|
||||
${c7}\\ /__/ /
|
||||
${c7}\\_______/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ee]ndeavour*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c1}/${c4}\\
|
||||
${c1}/${c4}/ \\${c6}\\
|
||||
${c1}/${c4}/ \\ ${c6}\\
|
||||
${c1}/ ${c4}/ _) ${c6})
|
||||
${c1}/_${c4}/___-- ${c6}__-
|
||||
${c6}/____--
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ff]edora* | [Nn]obara*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c4},'''''.
|
||||
${c4}| ,. |
|
||||
${c4}| | '_'
|
||||
${c4} ,....| |..
|
||||
${c4}.' ,_;| ..'
|
||||
${c4}| | | |
|
||||
${c4}| ',_,' |
|
||||
${c4} '. ,'
|
||||
${c4}'''''
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ff]ree[Bb][Ss][Dd]*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c1}/\\,-'''''-,/\\
|
||||
${c1}\\_) (_/
|
||||
${c1}| |
|
||||
${c1}| |
|
||||
${c1}; ;
|
||||
${c1}'-_____-'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Gg]aruda*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c3} _______
|
||||
${c3} __/ \\_
|
||||
${c3} _/ / \\_
|
||||
${c7} _/ /_________\\
|
||||
${c7}_/ |
|
||||
${c2}\\ ____________
|
||||
${c2} \\_ __/
|
||||
${c2} \\__________/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Gg]entoo*)
|
||||
read_ascii 5 <<- EOF
|
||||
${c5} _-----_
|
||||
${c5}( \\
|
||||
${c5}\\ 0 \\
|
||||
${c7} \\ )
|
||||
${c7} / _/
|
||||
${c7}( _-
|
||||
${c7}\\____-
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Gg][Nn][Uu]*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c2} _-\`\`-, ,-\`\`-_
|
||||
${c2} .' _-_| |_-_ '.
|
||||
${c2}./ /_._ _._\\ \\.
|
||||
${c2}: _/_._\`:'_._\\_ :
|
||||
${c2}\\:._/ ,\` \\ \\ \\_.:/
|
||||
${c2} ,-';'.@) \\ @) \\
|
||||
${c2} ,'/' ..- .\\,-.|
|
||||
${c2} /'/' \\(( \\\` ./ )
|
||||
${c2} '/'' \\_,----'
|
||||
${c2} '/'' ,;/''
|
||||
${c2} \`\`;'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Gg]uix[Ss][Dd]* | [Gg]uix*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c3}|.__ __.|
|
||||
${c3}|__ \\ / __|
|
||||
${c3}\\ \\ / /
|
||||
${c3}\\ \\ / /
|
||||
${c3}\\ \\ / /
|
||||
${c3}\\ \\/ /
|
||||
${c3}\\__/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Hh]aiku*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c3} ,^,
|
||||
${c3} / \\
|
||||
${c3}*--_ ; ; _--*
|
||||
${c3}\\ '" "' /
|
||||
${c3}'. .'
|
||||
${c3}.-'" "'-.
|
||||
${c3}'-.__. .__.-'
|
||||
${c3}|_|
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Hh]ydroOS*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c1}╔╗╔╗──╔╗───╔═╦══╗
|
||||
${c1}║╚╝╠╦╦╝╠╦╦═╣║║══╣
|
||||
${c1}║╔╗║║║╬║╔╣╬║║╠══║
|
||||
${c1}╚╝╚╬╗╠═╩╝╚═╩═╩══╝
|
||||
${c1}───╚═╝
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Hh]yperbola*)
|
||||
read_ascii <<- EOF
|
||||
${c7} |\`__.\`/
|
||||
${c7} \____/
|
||||
${c7} .--.
|
||||
${c7} / \\
|
||||
${c7} / ___ \\
|
||||
${c7}/ .\` \`.\\
|
||||
${c7}/.\` \`.\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ii]glunix*)
|
||||
read_ascii <<- EOF
|
||||
${c7} |
|
||||
${c7} | |
|
||||
${c7} |
|
||||
${c7} | ________
|
||||
${c7} | /\\ | \\
|
||||
${c7} / \\ | \\ |
|
||||
${c7} / \\ \\ |
|
||||
${c7} / \\________\\
|
||||
${c7} \\ / /
|
||||
${c7} \\ / /
|
||||
${c7} \\ / /
|
||||
${c7} \\/________/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ii]nstant[Oo][Ss]*)
|
||||
read_ascii <<- EOF
|
||||
${c7} ,-''-,
|
||||
${c7}: .''. :
|
||||
${c7}: ',,' :
|
||||
${c7} '-____:__
|
||||
${c7} : \`.
|
||||
${c7} \`._.'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ii][Rr][Ii][Xx]*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c1} __
|
||||
${c1} \\ \\ __
|
||||
${c1} \\ \\ / /
|
||||
${c1} \\ v /
|
||||
${c1} / . \\
|
||||
${c1} /_/ \\ \\
|
||||
${c1} \\_\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Kk][Dd][Ee]*[Nn]eon*)
|
||||
read_ascii 6 <<- EOF
|
||||
${c7} .${c6}__${c7}.${c6}__${c7}.
|
||||
${c6} / _${c7}.${c6}_ \\
|
||||
${c6} / / \\ \\
|
||||
${c7} . ${c6}| ${c7}O${c6} | ${c7}.
|
||||
${c6} \\ \\_${c7}.${c6}_/ /
|
||||
${c6} \\${c7}.${c6}__${c7}.${c6}__${c7}.${c6}/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ll]inux*[Ll]ite* | [Ll]ite*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c3} /\\
|
||||
${c3} / \\
|
||||
${c3} / ${c7}/ ${c3}/
|
||||
${c3}> ${c7}/ ${c3}/
|
||||
${c3}\\ ${c7}\\ ${c3}\\
|
||||
${c3}\\_${c7}\\${c3}_\\
|
||||
${c7} \\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ll]inux*[Mm]int* | [Mm]int)
|
||||
read_ascii 2 <<- EOF
|
||||
${c2} ___________
|
||||
${c2}|_ \\
|
||||
${c2}| ${c7}| _____ ${c2}|
|
||||
${c2}| ${c7}| | | | ${c2}|
|
||||
${c2}| ${c7}| | | | ${c2}|
|
||||
${c2}| ${c7}\\__${c7}___/ ${c2}|
|
||||
${c2}\\_________/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ll]inux*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c4} ___
|
||||
${c4}(${c7}.. ${c4}|
|
||||
${c4}(${c5}<> ${c4}|
|
||||
${c4}/ ${c7}__ ${c4}\\
|
||||
${c4}( ${c7}/ \\ ${c4}/|
|
||||
${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4})
|
||||
${c5}\/${c4}-____${c5}\/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Mm]ac[Oo][Ss]* | [Dd]arwin*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c2} .:'
|
||||
${c2} _ :'_
|
||||
${c3} .'\`_\`-'_\`\`.
|
||||
${c1}:________.-'
|
||||
${c1}:_______:
|
||||
${c4} :_______\`-;
|
||||
${c5} \`._.-._.'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Mm]ageia*)
|
||||
read_ascii 2 <<- EOF
|
||||
${c6} *
|
||||
${c6} *
|
||||
${c6} **
|
||||
${c7} /\\__/\\
|
||||
${c7}/ \\
|
||||
${c7}\\ /
|
||||
${c7} \\____/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Mm]anjaro*)
|
||||
read_ascii 2 <<- EOF
|
||||
${c2}||||||||| ||||
|
||||
${c2}||||||||| ||||
|
||||
${c2}|||| ||||
|
||||
${c2}|||| |||| ||||
|
||||
${c2}|||| |||| ||||
|
||||
${c2}|||| |||| ||||
|
||||
${c2}|||| |||| ||||
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Mm]inix*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c4} ,, ,,
|
||||
${c4};${c7},${c4} ', ,' ${c7},${c4};
|
||||
${c4}; ${c7}',${c4} ',,' ${c7},'${c4} ;
|
||||
${c4}; ${c7}',${c4} ${c7},'${c4} ;
|
||||
${c4}; ${c7};, '' ,;${c4} ;
|
||||
${c4}; ${c7};${c4};${c7}',,'${c4};${c7};${c4} ;
|
||||
${c4}', ${c7};${c4};; ;;${c7};${c4} ,'
|
||||
${c4} '${c7};${c4}' '${c7};${c4}'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Mm][Xx]*)
|
||||
read_ascii <<- EOF
|
||||
${c7} \\\\ /
|
||||
${c7} \\\\/
|
||||
${c7} \\\\
|
||||
${c7} /\\/ \\\\
|
||||
${c7} / \\ /\\
|
||||
${c7} / \\/ \\
|
||||
${c7}/__________\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Nn]et[Bb][Ss][Dd]*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c7}\\\\${c3}\`-______,----__
|
||||
${c7} \\\\ ${c3}__,---\`_
|
||||
${c7} \\\\ ${c3}\`.____
|
||||
${c7} \\\\${c3}-______,----\`-
|
||||
${c7} \\\\
|
||||
${c7} \\\\
|
||||
${c7} \\\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Nn]ix[Oo][Ss]*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c4} \\\\ \\\\ //
|
||||
${c4} ==\\\\__\\\\/ //
|
||||
${c4} // \\\\//
|
||||
${c4}==// //==
|
||||
${c4} //\\\\___//
|
||||
${c4}// /\\\\ \\\\==
|
||||
${c4} // \\\\ \\\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Oo]pen[Bb][Ss][Dd]*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c3} _____
|
||||
${c3} \\- -/
|
||||
${c3} \\_/ \\
|
||||
${c3} | ${c7}O O${c3} |
|
||||
${c3} |_ < ) 3 )
|
||||
${c3} / \\ /
|
||||
${c3} /-_____-\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Oo]pen[Ss][Uu][Ss][Ee]*[Tt]umbleweed*)
|
||||
read_ascii 2 <<- EOF
|
||||
${c2} _____ ______
|
||||
${c2} / ____\\ / ____ \\
|
||||
${c2}/ / \`/ / \\ \\
|
||||
${c2}\\ \\____/ /,____/ /
|
||||
${c2} \\______/ \\_____/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Oo]pen[Ss][Uu][Ss][Ee]* | [Oo]pen*SUSE* | SUSE* | suse*)
|
||||
read_ascii 2 <<- EOF
|
||||
${c2} _______
|
||||
${c2}__| __ \\
|
||||
${c2} / .\\ \\
|
||||
${c2} \\__/ |
|
||||
${c2} _______|
|
||||
${c2} \\_______
|
||||
${c2}__________/
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Oo]pen[Ww]rt*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c1} _______
|
||||
${c1}| |.-----.-----.-----.
|
||||
${c1}| - || _ | -__| |
|
||||
${c1}|_______|| __|_____|__|__|
|
||||
${c1} ________|__| __
|
||||
${c1}| | | |.----.| |_
|
||||
${c1}| | | || _|| _|
|
||||
${c1}|________||__| |____|
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Pp]arabola*)
|
||||
read_ascii 5 <<- EOF
|
||||
${c5} __ __ __ _
|
||||
${c5}.\`_//_//_/ / \`.
|
||||
${c5} / .\`
|
||||
${c5} / .\`
|
||||
${c5} /.\`
|
||||
${c5} /\`
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Pp]op!_[Oo][Ss]*)
|
||||
read_ascii 6 <<- EOF
|
||||
${c6} .///////,
|
||||
${c6} //${c7}76767${c6}//////
|
||||
${c6} //${c7}76${c6}//${c7}76${c6}//${c7}767${c6}//
|
||||
${c6} ////${c7}7676'${c6}//${c7}76${c6}////
|
||||
${c6} /////${c7}76${c6}////${c7}7${c6}/////
|
||||
${c6} /////${c7}76${c6}//${c7}76${c6}////
|
||||
${c6} ///${c7}76767676${c6}//
|
||||
${c6} \`///////'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Pp]ure[Oo][Ss]*)
|
||||
read_ascii <<- EOF
|
||||
${c7} _____________
|
||||
${c7}| _________ |
|
||||
${c7}| | | |
|
||||
${c7}| | | |
|
||||
${c7}| |_________| |
|
||||
${c7}|_____________|
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Rr]aspbian*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c2} __ __
|
||||
${c2} (_\\)(/_)
|
||||
${c1} (_(__)_)
|
||||
${c1}(_(_)(_)_)
|
||||
${c1} (_(__)_)
|
||||
${c1} (__)
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ss]erenity[Oo][Ss]*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c7} _____
|
||||
${c1} ,-${c7} -,
|
||||
${c1} ;${c7} ( ;
|
||||
${c1}| ${c7}. \_${c1}.,${c7} |
|
||||
${c1}| ${c7}o _${c1} ',${c7} |
|
||||
${c1} ; ${c7}(_)${c1} )${c7} ;
|
||||
${c1} '-_____-${c7}'
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ss]lackware*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c4} ________
|
||||
${c4} / ______|
|
||||
${c4} | |______
|
||||
${c4} \\______ \\
|
||||
${c4} ______| |
|
||||
${c4}| |________/
|
||||
${c4}|____________
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ss]olus*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c6}
|
||||
${c6} /|
|
||||
${c6} / |\\
|
||||
${c6} / | \\ _
|
||||
${c6} /___|__\\_\\
|
||||
${c6} \\ /
|
||||
${c6} \`-------´
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Ss]un[Oo][Ss] | [Ss]olaris*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c3} . .; .
|
||||
${c3} . :; :: ;: .
|
||||
${c3} .;. .. .. .;.
|
||||
${c3}.. .. .. ..
|
||||
${c3} .;, ,;.
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Uu]buntu*)
|
||||
read_ascii 3 <<- EOF
|
||||
${c3} _
|
||||
${c3} ---(_)
|
||||
${c3} _/ --- \\
|
||||
${c3}(_) | |
|
||||
${c3} \\ --- _/
|
||||
${c3} ---(_)
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Vv]oid*)
|
||||
read_ascii 2 <<- EOF
|
||||
${c2} _______
|
||||
${c2} _ \\______ -
|
||||
${c2}| \\ ___ \\ |
|
||||
${c2}| | / \ | |
|
||||
${c2}| | \___/ | |
|
||||
${c2}| \\______ \\_|
|
||||
${c2} -_______\\
|
||||
EOF
|
||||
;;
|
||||
|
||||
[Xx]eonix*)
|
||||
read_ascii 2 <<- EOF
|
||||
${c2} ___ ___
|
||||
${c2}___ \ \/ / ___
|
||||
${c2}\ \ \ / / /
|
||||
${c2} \ \/ \/ /
|
||||
${c2} \ /\ /
|
||||
${c2} \__/ \__/
|
||||
EOF
|
||||
;;
|
||||
[Ff]iwix*)
|
||||
read_ascii 12 <<- EOF
|
||||
${c6}_____ ${c4}_____
|
||||
${c6}\\ \\ ${c4}\\ \\
|
||||
${c6}\\ \\ ${c4}\\ \\
|
||||
${c6}/ \\ \\ ${c4}\\ \\
|
||||
${c6}( \\ \\ ${c4}\\ \\
|
||||
${c6}( / / ${c4}/ /
|
||||
${c6}\\ / / ${c4}/ /
|
||||
${c6}/ / ${c4}/ /
|
||||
${c6}/____/ ${c4}/____/
|
||||
EOF
|
||||
;;
|
||||
[Mm]orphOS*)
|
||||
read_ascii 1 <<- EOF
|
||||
${c4} __ \/ __
|
||||
${c4} /o \{}/ o\\
|
||||
${c4} \ () /
|
||||
${c4} \`> /\ <\`
|
||||
${c4} (o/\/\o)
|
||||
${c4} ) (
|
||||
EOF
|
||||
;;
|
||||
[Aa]mog[Oo][Ss]*)
|
||||
read_ascii 4 <<- EOF
|
||||
${c7} -///:.
|
||||
${c7} smhhhhmh\`
|
||||
${c7} :NA${c4}mogO${c7}SNs
|
||||
${c7} hNNmmmmNNN
|
||||
${c7} NNNNNNNNNN
|
||||
${c7} :NNNNNNNNNN
|
||||
${c7} mNNssussNNN
|
||||
${c7} sNn: sNNo
|
||||
${c7}+ooo+ sNNo
|
||||
${c7} +oooo\`
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
105
pfetch-extractor/src/lib.rs
Normal file
105
pfetch-extractor/src/lib.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use regex::Regex;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn parse_logos(_input: TokenStream) -> TokenStream {
|
||||
let raw_logos = include_str!("../logos.sh");
|
||||
let raw_logos = raw_logos
|
||||
.split_once("in\n")
|
||||
.expect("Invalid logos.sh file")
|
||||
.1;
|
||||
let raw_logos = raw_logos
|
||||
.split_once("\nesac")
|
||||
.expect("Invalid logos.sh file")
|
||||
.0;
|
||||
|
||||
let mut tux = None;
|
||||
let logos = raw_logos
|
||||
.split(";;\n")
|
||||
.filter_map(|raw_logo| {
|
||||
let (is_tux, logo) = parse_logo(raw_logo)?;
|
||||
if is_tux {
|
||||
tux = Some(logo.clone());
|
||||
}
|
||||
Some(logo)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tux = tux.unwrap();
|
||||
|
||||
quote! { (#tux, [#(#logos),*]) }.into()
|
||||
}
|
||||
|
||||
fn parse_logo(input: &str) -> Option<(bool, proc_macro2::TokenStream)> {
|
||||
let input = input.trim().replace('\t', "");
|
||||
if input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let regex = Regex::new(r"^\(?(.*)\)[\s\S]*read_ascii *(\d)? *(\d)?").unwrap();
|
||||
|
||||
let groups = regex
|
||||
.captures(&input)
|
||||
.expect("Error while parsing logos.sh");
|
||||
|
||||
let pattern = &groups[1];
|
||||
let primary_color = match groups.get(2) {
|
||||
Some(color) => color.as_str().parse::<u8>().unwrap(),
|
||||
None => 7,
|
||||
};
|
||||
let secondary_color = match groups.get(3) {
|
||||
Some(color) => color.as_str().parse::<u8>().unwrap(),
|
||||
None => (primary_color + 1) % 8,
|
||||
};
|
||||
|
||||
let logo = input
|
||||
.split_once("EOF\n")
|
||||
.expect("Could not find start of logo")
|
||||
.1
|
||||
.split_once("\nEOF")
|
||||
.expect("Could not find end of logo")
|
||||
.0;
|
||||
|
||||
let mut logo_parts = vec![];
|
||||
for logo_part in logo.split("${") {
|
||||
if let Some((new_color, rest)) = logo_part.split_once('}') {
|
||||
let new_color: u8 = match new_color {
|
||||
"c0" => 0,
|
||||
"c1" => 1,
|
||||
"c2" => 2,
|
||||
"c3" => 3,
|
||||
"c4" => 4,
|
||||
"c5" => 5,
|
||||
"c6" => 6,
|
||||
"c7" => 7,
|
||||
_ => panic!("Unknown color: {new_color}"),
|
||||
};
|
||||
let rest = rest.replace("\\\\", "\\");
|
||||
let rest = rest.replace("\\`", "`");
|
||||
let lines = rest.split('\n').collect::<Vec<_>>();
|
||||
let last_index = lines.len() - 1;
|
||||
for (index, line) in lines.into_iter().enumerate() {
|
||||
let mut line = line.to_owned();
|
||||
if index != last_index {
|
||||
line += "\n";
|
||||
}
|
||||
logo_parts.push(quote! { (Color(#new_color), #line) });
|
||||
}
|
||||
} else if !logo_part.is_empty() {
|
||||
let logo_part = logo_part.replace("\\\\", "\\");
|
||||
logo_parts.push(quote! { (Color(9), #logo_part) });
|
||||
}
|
||||
}
|
||||
|
||||
Some((
|
||||
pattern == "[Ll]inux*",
|
||||
quote! {
|
||||
Logo {
|
||||
primary_color: Color(#primary_color),
|
||||
secondary_color: Color(#secondary_color),
|
||||
pattern: #pattern,
|
||||
logo_parts: &[#(#logo_parts),*],
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
66
print_all.sh
Executable file
66
print_all.sh
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# A small script to run pfetch (command specified with arguments) with all available logos
|
||||
|
||||
while read -r logo; do
|
||||
PF_ASCII=$logo "$@"
|
||||
done << EOF
|
||||
alpine
|
||||
android
|
||||
arch
|
||||
arco
|
||||
artix
|
||||
bedrock
|
||||
buildroot
|
||||
celos
|
||||
centos
|
||||
crystallinux
|
||||
dahlia
|
||||
debian
|
||||
devuan
|
||||
dragonfly
|
||||
elementary
|
||||
endeavour
|
||||
fedora
|
||||
freebsd
|
||||
garuda
|
||||
gentoo
|
||||
gnu
|
||||
guix
|
||||
haiku
|
||||
hydroOS
|
||||
hyperbola
|
||||
iglunix
|
||||
instantos
|
||||
irix
|
||||
kdeneon
|
||||
linuxlite
|
||||
linuxmint
|
||||
linux
|
||||
macos
|
||||
mageia
|
||||
manjaro
|
||||
minix
|
||||
mx
|
||||
netbsd
|
||||
nixos
|
||||
openbsd
|
||||
opensusetumbleweed
|
||||
opensuse
|
||||
openwrt
|
||||
parabola
|
||||
pop!_os
|
||||
pureos
|
||||
raspbian
|
||||
serenityos
|
||||
slackware
|
||||
solus
|
||||
solaris
|
||||
ubuntu
|
||||
void
|
||||
xeonix
|
||||
fiwix
|
||||
morphos
|
||||
amogos
|
||||
aperio
|
||||
EOF
|
||||
440
src/lib.rs
Normal file
440
src/lib.rs
Normal file
@ -0,0 +1,440 @@
|
||||
use std::{env, fmt::Display, fs, io::Result, process::Command, str::FromStr};
|
||||
|
||||
use glob::glob;
|
||||
use globset::Glob;
|
||||
use libmacchina::{
|
||||
traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _,
|
||||
traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout,
|
||||
};
|
||||
use pfetch_extractor::parse_logos;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Color(pub u8);
|
||||
|
||||
impl Display for Color {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "\x1b[3{}m", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Color {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s.parse::<u8>() {
|
||||
Ok(color) => {
|
||||
if color >= 9 {
|
||||
Err(format!("Invalid color: {color}"))
|
||||
} else {
|
||||
Ok(Color(color))
|
||||
}
|
||||
}
|
||||
Err(_) => Err(format!("Not a valid color: {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Logo {
|
||||
pub primary_color: Color,
|
||||
pub secondary_color: Color,
|
||||
pub pattern: &'static str,
|
||||
pub logo_parts: &'static [(Color, &'static str)],
|
||||
}
|
||||
|
||||
impl Display for Logo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
self.logo_parts
|
||||
.iter()
|
||||
.map(|(color, part)| format!("{color}{part}"))
|
||||
.collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PackageManager {
|
||||
Pacman,
|
||||
Dpkg,
|
||||
Xbps,
|
||||
Apk,
|
||||
Rpm,
|
||||
Flatpak,
|
||||
Crux,
|
||||
Guix,
|
||||
Opkg,
|
||||
Kiss,
|
||||
Portage,
|
||||
Pkgtool,
|
||||
Nix,
|
||||
}
|
||||
|
||||
/// Obtain the amount of installed packages on the system by checking all installed supported package
|
||||
/// managers and adding the amounts
|
||||
pub fn total_packages(package_readout: &PackageReadout) -> usize {
|
||||
match env::consts::OS {
|
||||
"linux" => {
|
||||
let macchina_package_count: Vec<(String, usize)> = package_readout
|
||||
.count_pkgs()
|
||||
.iter()
|
||||
.map(|(macchina_manager, count)| (macchina_manager.to_string(), *count))
|
||||
.collect();
|
||||
[
|
||||
PackageManager::Pacman,
|
||||
PackageManager::Dpkg,
|
||||
PackageManager::Xbps,
|
||||
PackageManager::Apk,
|
||||
PackageManager::Rpm,
|
||||
PackageManager::Flatpak,
|
||||
PackageManager::Crux,
|
||||
PackageManager::Guix,
|
||||
PackageManager::Opkg,
|
||||
PackageManager::Kiss,
|
||||
PackageManager::Portage,
|
||||
PackageManager::Pkgtool,
|
||||
PackageManager::Nix,
|
||||
]
|
||||
.iter()
|
||||
.map(|mngr| packages(mngr, &macchina_package_count))
|
||||
.sum()
|
||||
}
|
||||
"macos" => {
|
||||
// pkgin
|
||||
run_and_count_lines("pkgin", &["list"])
|
||||
// dpkg
|
||||
+ run_and_count_lines("dpkg-query", &["-f", "'.\n'", "-W"])
|
||||
// brew
|
||||
+ if check_if_command_exists("brew") {
|
||||
match glob("/usr/local/Cellar/*") {
|
||||
Ok(files) => files.count(),
|
||||
Err(_) => 0,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
// port
|
||||
+ match run_system_command("port", &["installed"]) {
|
||||
Ok(output) => {
|
||||
// port prints a single line to stdout when no packages are installed
|
||||
if output == "No ports are installed." {
|
||||
0
|
||||
} else {
|
||||
output.lines().count()
|
||||
}
|
||||
},
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
"freebsd" | "dragonfly" => run_and_count_lines("pkg", &["info"]),
|
||||
"openbsd" => match glob("/var/db/pkg/*/") {
|
||||
Ok(files) => files.count(),
|
||||
Err(_) => 0,
|
||||
},
|
||||
"netbsd" => run_and_count_lines("pkg_info", &[]),
|
||||
"solaris" => {
|
||||
run_and_count_lines("pkginfo", &["-i"]) + run_and_count_lines("pkg", &["list"])
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_macchina_package_count(
|
||||
macchina_result: &[(String, usize)],
|
||||
package_manager_name: &str,
|
||||
) -> Option<usize> {
|
||||
macchina_result
|
||||
.iter()
|
||||
.find(|entry| entry.0 == package_manager_name)
|
||||
.map(|entry| entry.1)
|
||||
}
|
||||
|
||||
/// return the amount of packages installed with a given linux package manager
|
||||
/// Return `0` if the package manager is not installed
|
||||
fn packages(pkg_manager: &PackageManager, macchina_package_count: &[(String, usize)]) -> usize {
|
||||
match pkg_manager {
|
||||
// libmacchina has very fast implementations for most package managers, so we use them
|
||||
// where we can, otherwise we fall back to method used by dylans version of pfetch
|
||||
PackageManager::Pacman
|
||||
| PackageManager::Flatpak
|
||||
| PackageManager::Dpkg
|
||||
| PackageManager::Xbps
|
||||
| PackageManager::Apk
|
||||
| PackageManager::Rpm
|
||||
| PackageManager::Portage
|
||||
| PackageManager::Opkg => get_macchina_package_count(
|
||||
macchina_package_count,
|
||||
&format!("{pkg_manager:?}").to_lowercase(),
|
||||
)
|
||||
.unwrap_or(0),
|
||||
PackageManager::Guix => run_and_count_lines("guix", &["package", "--list-installed"]),
|
||||
PackageManager::Crux => {
|
||||
if check_if_command_exists("crux") {
|
||||
run_and_count_lines("pkginfo", &["-i"])
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
PackageManager::Kiss => {
|
||||
if check_if_command_exists("kiss") {
|
||||
match glob("/var/db/kiss/installed/*/") {
|
||||
Ok(files) => files.count(),
|
||||
Err(_) => 0,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
PackageManager::Pkgtool => {
|
||||
if check_if_command_exists("pkgtool") {
|
||||
match glob("/var/log/packages/*") {
|
||||
Ok(files) => files.count(),
|
||||
Err(_) => 0,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
// TODO: nix -q is very slow
|
||||
PackageManager::Nix => {
|
||||
if check_if_command_exists("nix-store") {
|
||||
run_and_count_lines(
|
||||
"nix-store",
|
||||
&["-q", "--requisites", "/run/current-system/sw"],
|
||||
) + run_and_count_lines(
|
||||
"nix-store",
|
||||
&[
|
||||
"-q",
|
||||
"--requisites",
|
||||
&format!("{}/.nix-profile", env::var("HOME").unwrap_or_default()),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_at_hostname(
|
||||
general_readout: &GeneralReadout,
|
||||
username_override: &Option<String>,
|
||||
hostname_override: &Option<String>,
|
||||
) -> Option<String> {
|
||||
let username = match username_override {
|
||||
Some(username) => Ok(username.to_string()),
|
||||
None => general_readout.username(),
|
||||
};
|
||||
let hostname = match hostname_override {
|
||||
Some(hostname) => Ok(hostname.to_string()),
|
||||
None => general_readout.hostname(),
|
||||
};
|
||||
if username.is_err() || hostname.is_err() {
|
||||
None
|
||||
} else {
|
||||
Some(format!(
|
||||
"{}@{}",
|
||||
username.unwrap_or_default(),
|
||||
hostname.unwrap_or_default()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory(memory_readout: &MemoryReadout) -> Option<String> {
|
||||
let total_memory = memory_readout.total();
|
||||
let used_memory = memory_readout.used();
|
||||
if total_memory.is_err() || used_memory.is_err() {
|
||||
None
|
||||
} else {
|
||||
Some(format!(
|
||||
"{}M / {}M",
|
||||
used_memory.unwrap() / 1024,
|
||||
total_memory.unwrap() / 1024
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn os(general_readout: &GeneralReadout) -> Option<String> {
|
||||
match general_readout.distribution() {
|
||||
Ok(distribution) => Some(distribution),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kernel(kernel_readout: &KernelReadout) -> Option<String> {
|
||||
match kernel_readout.os_release() {
|
||||
Ok(kernel_version) => Some(kernel_version),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn seconds_to_string(time: usize) -> String {
|
||||
let days = if time > 86400 {
|
||||
let days_pre = time / 60 / 60 / 24;
|
||||
days_pre.to_string() + "d"
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let hours = if time > 3600 {
|
||||
let hours_pre = (time / 60 / 60) % 24;
|
||||
hours_pre.to_string() + "h"
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let minutes = if time > 60 {
|
||||
let minutes_pre = (time / 60) % 60;
|
||||
minutes_pre.to_string() + "m"
|
||||
} else {
|
||||
"0m".to_string()
|
||||
};
|
||||
format!("{days} {hours} {minutes}").trim_start().to_owned()
|
||||
}
|
||||
|
||||
pub fn uptime(general_readout: &GeneralReadout) -> Option<String> {
|
||||
match general_readout.uptime() {
|
||||
Ok(uptime) => Some(seconds_to_string(uptime)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host() -> Option<String> {
|
||||
const BLACKLIST: &[&str] = &[
|
||||
"To",
|
||||
"Be",
|
||||
"be",
|
||||
"Filled",
|
||||
"filled",
|
||||
"By",
|
||||
"by",
|
||||
"O.E.M.",
|
||||
"OEM",
|
||||
"Not",
|
||||
"Applicable",
|
||||
"Specified",
|
||||
"System",
|
||||
"Product",
|
||||
"Name",
|
||||
"Version",
|
||||
"Undefined",
|
||||
"Default",
|
||||
"string",
|
||||
"INVALID",
|
||||
"<EFBFBD>",
|
||||
"os",
|
||||
"Type1ProductConfigId",
|
||||
"",
|
||||
];
|
||||
|
||||
// get device from system files
|
||||
let product_name =
|
||||
fs::read_to_string("/sys/devices/virtual/dmi/id/product_name").unwrap_or_default();
|
||||
let product_name = product_name.trim();
|
||||
let product_version =
|
||||
fs::read_to_string("/sys/devices/virtual/dmi/id/product_version").unwrap_or_default();
|
||||
let product_version = product_version.trim();
|
||||
let product_model =
|
||||
fs::read_to_string("/sys/firmware/devicetree/base/model").unwrap_or_default();
|
||||
let product_model = product_model.trim();
|
||||
|
||||
let final_str = format!("{product_name} {product_version} {product_model}")
|
||||
.split(' ')
|
||||
.filter(|word| !BLACKLIST.contains(word))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
// if string is empty, display system architecture instead
|
||||
let final_str = if final_str.is_empty() {
|
||||
run_system_command("uname", &["-m"]).unwrap_or("Unknown".to_owned())
|
||||
} else {
|
||||
final_str
|
||||
};
|
||||
if final_str.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(final_str)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logo(logo_name: &str) -> Logo {
|
||||
let (tux, logos) = parse_logos!();
|
||||
logos
|
||||
.into_iter()
|
||||
.find(|logo| {
|
||||
logo.pattern.split('|').any(|glob| {
|
||||
Glob::new(glob.trim())
|
||||
.expect("Invalid logo pattern")
|
||||
.compile_matcher()
|
||||
.is_match(logo_name)
|
||||
})
|
||||
})
|
||||
.unwrap_or(tux)
|
||||
}
|
||||
|
||||
pub fn shell(general_readout: &GeneralReadout) -> Option<String> {
|
||||
match general_readout.shell(
|
||||
libmacchina::traits::ShellFormat::Relative,
|
||||
libmacchina::traits::ShellKind::Default,
|
||||
) {
|
||||
Ok(shell) => Some(shell),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn editor() -> Option<String> {
|
||||
match env::var("VISUAL") {
|
||||
Ok(editor) => Some(editor.trim().to_owned()),
|
||||
Err(_) => match env::var("EDITOR") {
|
||||
Ok(editor) => Some(editor.trim().to_owned()),
|
||||
Err(_) => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wm(general_readout: &GeneralReadout) -> Option<String> {
|
||||
match general_readout.window_manager() {
|
||||
Ok(wm) => Some(wm),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de(general_readout: &GeneralReadout) -> Option<String> {
|
||||
match general_readout.desktop_environment() {
|
||||
Ok(de) => Some(de),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn palette() -> String {
|
||||
(1..7)
|
||||
.map(|num| format!("\x1b[4{num}m "))
|
||||
.collect::<String>()
|
||||
+ "\x1b[0m"
|
||||
}
|
||||
|
||||
fn run_system_command(command: &str, args: &[&str]) -> Result<String> {
|
||||
let mut output =
|
||||
String::from_utf8_lossy(&Command::new(command).args(args).output()?.stdout).into_owned();
|
||||
output.truncate(output.trim_end().len());
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn check_if_command_exists(command: &str) -> bool {
|
||||
which::which(command).is_ok()
|
||||
}
|
||||
|
||||
fn _system_command_error(command: &str, args: &[&str]) -> Result<String> {
|
||||
let mut output =
|
||||
String::from_utf8_lossy(&Command::new(command).args(args).output()?.stderr).into_owned();
|
||||
output.truncate(output.trim_end().len());
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Return the amount of line the output of a system command produces
|
||||
/// Returns `0` if command fails
|
||||
fn run_and_count_lines(command: &str, args: &[&str]) -> usize {
|
||||
run_system_command(command, args)
|
||||
.unwrap_or_default()
|
||||
.lines()
|
||||
.count()
|
||||
}
|
||||
284
src/main.rs
Normal file
284
src/main.rs
Normal file
@ -0,0 +1,284 @@
|
||||
use libmacchina::{
|
||||
traits::GeneralReadout as _, traits::KernelReadout as _, traits::MemoryReadout as _,
|
||||
traits::PackageReadout as _, GeneralReadout, KernelReadout, MemoryReadout, PackageReadout,
|
||||
};
|
||||
use std::{env, fmt::Display, str::FromStr};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum PfetchInfo {
|
||||
Ascii,
|
||||
Title,
|
||||
Os,
|
||||
Host,
|
||||
Kernel,
|
||||
Uptime,
|
||||
Pkgs,
|
||||
Memory,
|
||||
Shell,
|
||||
Editor,
|
||||
Wm,
|
||||
De,
|
||||
Palette,
|
||||
BlankLine,
|
||||
}
|
||||
|
||||
impl Display for PfetchInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", format!("{self:?}").to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PfetchInfo {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(info: &str) -> Result<Self, Self::Err> {
|
||||
match info {
|
||||
"ascii" => Ok(PfetchInfo::Ascii),
|
||||
"title" => Ok(PfetchInfo::Title),
|
||||
"os" => Ok(PfetchInfo::Os),
|
||||
"host" => Ok(PfetchInfo::Host),
|
||||
"kernel" => Ok(PfetchInfo::Kernel),
|
||||
"uptime" => Ok(PfetchInfo::Uptime),
|
||||
"pkgs" => Ok(PfetchInfo::Pkgs),
|
||||
"memory" => Ok(PfetchInfo::Memory),
|
||||
"shell" => Ok(PfetchInfo::Shell),
|
||||
"editor" => Ok(PfetchInfo::Editor),
|
||||
"wm" => Ok(PfetchInfo::Wm),
|
||||
"de" => Ok(PfetchInfo::De),
|
||||
"palette" => Ok(PfetchInfo::Palette),
|
||||
unknown_info => Err(format!("Unknown pfetch info: {unknown_info}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pfetch(info: Vec<(pfetch::Color, String, String)>, logo: pfetch::Logo, logo_enabled: bool) {
|
||||
let raw_logo = if logo_enabled {
|
||||
logo.logo_parts
|
||||
.iter()
|
||||
.map(|(_, part)| *part)
|
||||
.collect::<String>()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let logo = logo.to_string();
|
||||
let mut logo_lines = logo.lines();
|
||||
let raw_logo_lines: Vec<_> = raw_logo.lines().collect();
|
||||
let logo_width = raw_logo_lines
|
||||
.iter()
|
||||
.map(|line| line.chars().count())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let line_amount = usize::max(raw_logo_lines.len(), info.len());
|
||||
|
||||
let info1_width = info
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|(_, line, _)| {
|
||||
if line.starts_with("\x1b[4") {
|
||||
0
|
||||
} else {
|
||||
line.len()
|
||||
}
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
let padding1 = match dotenv::var("PF_PAD1") {
|
||||
Ok(padding0) => padding0.parse::<usize>().unwrap_or(0),
|
||||
Err(_) => 0,
|
||||
};
|
||||
let padding2 = match dotenv::var("PF_PAD2") {
|
||||
Ok(padding1) => padding1.parse::<usize>().unwrap_or(0),
|
||||
Err(_) => 3,
|
||||
};
|
||||
let padding3 = match dotenv::var("PF_PAD3") {
|
||||
Ok(padding2) => padding2.parse::<usize>().unwrap_or(0),
|
||||
Err(_) => 1,
|
||||
};
|
||||
|
||||
let mut pfetch_str = String::new();
|
||||
|
||||
for l in 0..line_amount {
|
||||
pfetch_str += &format!(
|
||||
"{padding1}\x1b[1m{logo}{padding2}{color}{info1}\x1b[0m{separator}{padding3}{color2}{info2}\n",
|
||||
padding1 = " ".repeat(padding1),
|
||||
logo = if logo_enabled {
|
||||
logo_lines.next().unwrap_or("")
|
||||
} else {
|
||||
""
|
||||
},
|
||||
padding2 = " ".repeat(
|
||||
logo_width - raw_logo_lines.get(l).map_or(0, |line| line.chars().count())
|
||||
+ if logo_enabled { padding2 } else { 0 }
|
||||
),
|
||||
color = info.get(l).map_or("".to_owned(), |line| line.0.to_string()),
|
||||
info1 = info.get(l).map_or("", |line| &line.1),
|
||||
separator = info.get(l).map_or("".to_string(), |line|
|
||||
if ! &line.2.is_empty() {
|
||||
dotenv::var("PF_SEP").unwrap_or_default()
|
||||
} else { "".to_string() }
|
||||
),
|
||||
padding3 = " ".repeat(
|
||||
info1_width.saturating_sub(info.get(l).map_or(0, |(_, line, _)| line.len()))
|
||||
+ padding3
|
||||
),
|
||||
color2 = match dotenv::var("PF_COL2") {
|
||||
Ok(newcolor) => {
|
||||
match pfetch::Color::from_str(&newcolor) {
|
||||
Ok(newcolor) => format!("{newcolor}"),
|
||||
Err(_) => "".to_string(),
|
||||
}
|
||||
},
|
||||
Err(_) => "".to_string()
|
||||
},
|
||||
info2 = info.get(l).map_or("", |line| &line.2)
|
||||
)
|
||||
}
|
||||
|
||||
// if colors are disabled, remove them from string
|
||||
if dotenv::var("PF_COLOR").unwrap_or_default() == "0" {
|
||||
pfetch_str = pfetch_str
|
||||
.split("\x1b[")
|
||||
.map(|chunk| chunk.chars().skip(3).collect::<String>())
|
||||
.collect();
|
||||
}
|
||||
println!("{pfetch_str}");
|
||||
}
|
||||
|
||||
struct Readouts {
|
||||
general_readout: GeneralReadout,
|
||||
package_readout: PackageReadout,
|
||||
memory_readout: MemoryReadout,
|
||||
kernel_readout: KernelReadout,
|
||||
}
|
||||
|
||||
fn get_info(info: &PfetchInfo, readouts: &Readouts) -> Option<String> {
|
||||
match info {
|
||||
PfetchInfo::Ascii => None,
|
||||
PfetchInfo::Title => {
|
||||
let hostname_override = match dotenv::var("HOSTNAME") {
|
||||
Ok(hostname) => Some(hostname),
|
||||
Err(_) => None,
|
||||
};
|
||||
let username_override = match dotenv::var("USER") {
|
||||
Ok(username) => Some(username),
|
||||
Err(_) => None,
|
||||
};
|
||||
pfetch::user_at_hostname(
|
||||
&readouts.general_readout,
|
||||
&username_override,
|
||||
&hostname_override,
|
||||
)
|
||||
}
|
||||
PfetchInfo::Os => pfetch::os(&readouts.general_readout),
|
||||
PfetchInfo::Host => pfetch::host(),
|
||||
PfetchInfo::Kernel => pfetch::kernel(&readouts.kernel_readout),
|
||||
PfetchInfo::Uptime => pfetch::uptime(&readouts.general_readout),
|
||||
PfetchInfo::Pkgs => Some(pfetch::total_packages(&readouts.package_readout).to_string()),
|
||||
PfetchInfo::Memory => pfetch::memory(&readouts.memory_readout),
|
||||
PfetchInfo::Shell => match dotenv::var("SHELL") {
|
||||
Ok(shell) => Some(shell),
|
||||
Err(_) => pfetch::shell(&readouts.general_readout),
|
||||
},
|
||||
PfetchInfo::Editor => match dotenv::var("EDITOR") {
|
||||
Ok(editor) => Some(editor),
|
||||
Err(_) => pfetch::editor(),
|
||||
},
|
||||
PfetchInfo::Wm => pfetch::wm(&readouts.general_readout),
|
||||
PfetchInfo::De => match dotenv::var("XDG_CURRENT_DESKTOP") {
|
||||
Ok(de) => Some(de),
|
||||
Err(_) => pfetch::de(&readouts.general_readout),
|
||||
},
|
||||
PfetchInfo::Palette => Some(pfetch::palette()),
|
||||
PfetchInfo::BlankLine => Some("".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// source file specified by env: PF_SOURCE
|
||||
if let Ok(filepath) = dotenv::var("PF_SOURCE") {
|
||||
dotenv::from_path(filepath).unwrap();
|
||||
}
|
||||
|
||||
let enabled_pf_info_base: Vec<PfetchInfo> = match dotenv::var("PF_INFO") {
|
||||
Ok(pfetch_infos) => pfetch_infos
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map(|info| PfetchInfo::from_str(info).unwrap())
|
||||
.collect(),
|
||||
Err(_) => vec![
|
||||
PfetchInfo::Ascii,
|
||||
PfetchInfo::Title,
|
||||
PfetchInfo::Os,
|
||||
PfetchInfo::Host,
|
||||
PfetchInfo::Kernel,
|
||||
PfetchInfo::Uptime,
|
||||
PfetchInfo::Pkgs,
|
||||
PfetchInfo::Memory,
|
||||
],
|
||||
};
|
||||
|
||||
// insert blank lines before and after palettes
|
||||
let mut enabled_pf_info: Vec<PfetchInfo> = vec![];
|
||||
let mut ascii_enabled: bool = false;
|
||||
for info in enabled_pf_info_base {
|
||||
match info {
|
||||
PfetchInfo::Palette => {
|
||||
enabled_pf_info.push(PfetchInfo::BlankLine);
|
||||
enabled_pf_info.push(PfetchInfo::Palette);
|
||||
enabled_pf_info.push(PfetchInfo::BlankLine);
|
||||
}
|
||||
PfetchInfo::Ascii => {
|
||||
ascii_enabled = true;
|
||||
}
|
||||
i => enabled_pf_info.push(i),
|
||||
}
|
||||
}
|
||||
|
||||
let readouts = Readouts {
|
||||
general_readout: GeneralReadout::new(),
|
||||
package_readout: PackageReadout::new(),
|
||||
memory_readout: MemoryReadout::new(),
|
||||
kernel_readout: KernelReadout::new(),
|
||||
};
|
||||
|
||||
let os = get_info(&PfetchInfo::Os, &readouts).unwrap_or_default();
|
||||
|
||||
let logo_override = env::var("PF_ASCII");
|
||||
let logo_name = logo_override.as_ref().unwrap_or(&os);
|
||||
let mut logo = pfetch::logo(logo_name);
|
||||
|
||||
// color overrides
|
||||
if let Ok(newcolor) = dotenv::var("PF_COL1") {
|
||||
if let Ok(newcolor) = pfetch::Color::from_str(&newcolor) {
|
||||
logo.primary_color = newcolor;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(newcolor) = dotenv::var("PF_COL3") {
|
||||
if let Ok(newcolor) = pfetch::Color::from_str(&newcolor) {
|
||||
logo.secondary_color = newcolor;
|
||||
}
|
||||
}
|
||||
|
||||
let gathered_pfetch_info: Vec<(pfetch::Color, String, String)> = enabled_pf_info
|
||||
.iter()
|
||||
.filter_map(|info| {
|
||||
let info_result = get_info(info, &readouts);
|
||||
match info_result {
|
||||
Some(info_str) => match info {
|
||||
PfetchInfo::Title => Some((logo.secondary_color, info_str, "".to_string())),
|
||||
PfetchInfo::Os => Some((logo.primary_color, info.to_string(), os.to_owned())),
|
||||
PfetchInfo::BlankLine => {
|
||||
Some((logo.primary_color, "".to_string(), "".to_string()))
|
||||
}
|
||||
PfetchInfo::Palette => Some((logo.primary_color, info_str, "".to_string())),
|
||||
_ => Some((logo.primary_color, info.to_string(), info_str)),
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
pfetch(gathered_pfetch_info, logo, ascii_enabled);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user