├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── archey4.png └── workflows │ ├── deployment.yml │ └── integration.yml ├── .gitignore ├── CHANGELOG.md ├── COPYRIGHT.md ├── LICENSE ├── README.md ├── apparmor.profile ├── archey.1 ├── archey ├── __init__.py ├── __main__.py ├── _version.py ├── api.py ├── colors.py ├── configuration.py ├── distributions.py ├── entries │ ├── __init__.py │ ├── cpu.py │ ├── custom.py │ ├── desktop_environment.py │ ├── disk.py │ ├── distro.py │ ├── gpu.py │ ├── hostname.py │ ├── kernel.py │ ├── lan_ip.py │ ├── load_average.py │ ├── model.py │ ├── packages.py │ ├── processes.py │ ├── ram.py │ ├── shell.py │ ├── temperature.py │ ├── terminal.py │ ├── uptime.py │ ├── user.py │ ├── wan_ip.py │ └── window_manager.py ├── entry.py ├── environment.py ├── exceptions.py ├── logos │ ├── __init__.py │ ├── alpine.py │ ├── android.py │ ├── arch.py │ ├── armbian.py │ ├── buildroot.py │ ├── bunsenlabs.py │ ├── centos.py │ ├── crunchbang.py │ ├── darwin.py │ ├── debian.py │ ├── devuan.py │ ├── elementary.py │ ├── endeavouros.py │ ├── enso.py │ ├── fedora.py │ ├── freebsd.py │ ├── gentoo.py │ ├── guix.py │ ├── kali.py │ ├── linux.py │ ├── linuxmint.py │ ├── manjaro.py │ ├── moevalent.py │ ├── netbsd.py │ ├── nixos.py │ ├── nobara.py │ ├── openbsd.py │ ├── opensuse.py │ ├── parabola.py │ ├── pop.py │ ├── quirinux.py │ ├── raspbian.py │ ├── rhel.py │ ├── rocky.py │ ├── siduction.py │ ├── slackware.py │ ├── ubuntu.py │ ├── univalent.py │ └── windows.py ├── output.py ├── processes.py ├── py.typed ├── screenshot.py ├── singleton.py ├── test │ ├── __init__.py │ ├── entries │ │ ├── __init__.py │ │ ├── test_archey_cpu.py │ │ ├── test_archey_custom.py │ │ ├── test_archey_desktop_environment.py │ │ ├── test_archey_disk.py │ │ ├── test_archey_distro.py │ │ ├── test_archey_gpu.py │ │ ├── test_archey_hostname.py │ │ ├── test_archey_kernel.py │ │ ├── test_archey_lan_ip.py │ │ ├── test_archey_load_average.py │ │ ├── test_archey_model.py │ │ ├── test_archey_packages.py │ │ ├── test_archey_ram.py │ │ ├── test_archey_shell.py │ │ ├── test_archey_temperature.py │ │ ├── test_archey_terminal.py │ │ ├── test_archey_uptime.py │ │ ├── test_archey_user.py │ │ ├── test_archey_wan_ip.py │ │ └── test_archey_window_manager.py │ ├── test_archey_api.py │ ├── test_archey_colors.py │ ├── test_archey_configuration.py │ ├── test_archey_distributions.py │ ├── test_archey_entry.py │ ├── test_archey_logos.py │ ├── test_archey_output.py │ ├── test_archey_processes.py │ ├── test_archey_singleton.py │ └── test_archey_utility.py └── utility.py ├── config.json ├── dist └── .gitkeep ├── packaging ├── after_install ├── after_remove ├── before_remove └── build.sh ├── pyproject.toml └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | archey.1 -linguist-detectable 2 | packaging/* -linguist-detectable 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # repo: HorlogeSkynet/archey4 2 | # filename: FUNDING.YML 3 | 4 | liberapay: HorlogeSkynet 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve Archey4 4 | title: "[BUG] " 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | 13 | **Expected behavior** 14 | 15 | 16 | **Screenshots** 17 | 18 | 19 | **Environment** 20 | 21 | - Version used (4.X.Y.Z) : 22 | - Method of installation (distribution package, PyPI, Homebrew, sources) : 23 | - Hardware type (laptop, server, Raspberry, hyper-visor) : 24 | - Python version (3.Y.Z) : 25 | - Operating system and version : 26 | - Graphical environment name and version : 27 | - Connectivity (off-line, LAN only, Internet access) : 28 | - AppArmor profile loaded (yes/no, check `aa-status`) : 29 | 30 | **Additional context** 31 | 32 | 33 | **Custom configuration** 34 | 35 | 36 |
Custom configuration 37 |

38 | 39 | ```json 40 | {} 41 | ``` 42 | 43 |

44 |
45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Archey4 4 | title: "[FEATURE] " 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Description 5 | 6 | 7 | 8 | ## Reason and / or context 9 | 10 | 11 | 12 | 13 | ## How has this been tested ? 14 | 15 | 16 | 17 | ## Types of changes : 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] Typo / style fix (non-breaking change which improves readability) 21 | - [ ] New feature (non-breaking change which adds functionality) 22 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 23 | 24 | ## Checklist : 25 | 26 | - [ ] \[IF NEEDED\] I have updated the _README.md_ file accordingly ; 27 | - [ ] \[IF NEEDED\] I have updated the test cases (which pass) accordingly ; 28 | - [ ] \[IF BREAKING\] This pull request targets next Archey version branch ; 29 | - [ ] My changes looks good ; 30 | - [ ] I agree that my code may be modified in the future ; 31 | - [ ] My code follows the code style of this project ([PEP8](https://www.python.org/dev/peps/pep-0008/)). 32 | -------------------------------------------------------------------------------- /.github/archey4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HorlogeSkynet/archey4/f9e98a281bd6832fa7f3abf353dae16853d01f02/.github/archey4.png -------------------------------------------------------------------------------- /.github/workflows/deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Deployment 3 | 4 | on: 5 | # Run when new releases are published. 6 | release: 7 | types: [published] 8 | # Allow manual triggers from GitHub. 9 | workflow_dispatch: 10 | inputs: 11 | tag: 12 | required: true 13 | description: 'Git tag' 14 | 15 | jobs: 16 | bump_homebrew_formula: 17 | runs-on: macos-latest 18 | steps: 19 | - name: Bump Homebrew formula 20 | uses: dawidd6/action-homebrew-bump-formula@v3 21 | with: 22 | token: ${{ secrets.HOMEBREW_BUMP_FORMULA_GITHUB_TOKEN }} 23 | formula: archey4 24 | tag: ${{ github.event.release.tag_name || github.event.inputs.tag }} 25 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Integration 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | python_test: 8 | name: Run against Python ${{ matrix.python-version }} on ${{ matrix.os }} 9 | runs-on: ${{ matrix.os }} 10 | env: 11 | CLICOLOR_FORCE: 1 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: 16 | - 'macOS-latest' 17 | - 'ubuntu-latest' 18 | python-version: 19 | # - '3.7' 20 | # - '3.8' 21 | - '3.9' 22 | # - '3.10' 23 | - '3.11' 24 | # - '3.12' 25 | - '3.13' 26 | - '3.14-dev' 27 | - 'pypy3.9' 28 | include: 29 | - os: 'ubuntu-20.04' 30 | python-version: '3.6' 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: actions/setup-python@v5 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | 39 | - name: Install required dependencies 40 | run: | 41 | python -m pip install --upgrade pip setuptools 42 | 43 | - name: Install module regularly 44 | run: pip install . 45 | 46 | - name: Simple module executions 47 | run: | 48 | time archey 49 | time python -m archey 50 | 51 | - name: Run our test suite 52 | run: python -m unittest 53 | 54 | standalone_build: 55 | name: Standalone builds 56 | runs-on: ubuntu-latest 57 | env: 58 | CLICOLOR_FORCE: 1 59 | 60 | steps: 61 | - uses: actions/checkout@v4 62 | 63 | - uses: actions/setup-python@v5 64 | with: 65 | python-version: "3.11" 66 | 67 | - name: Install required dependencies 68 | run: | 69 | sudo apt update && sudo apt install -y patchelf 70 | python -m pip install --upgrade pip setuptools 71 | pip install nuitka pex stickytape pyinstaller 72 | pip install . 73 | 74 | - name: Standalone building (with Nuitka) 75 | run: | 76 | python -m nuitka \ 77 | --onefile \ 78 | --include-package=archey.logos \ 79 | --output-filename=archey \ 80 | --output-dir=dist \ 81 | --quiet \ 82 | archey/__main__.py 83 | time ./dist/archey 84 | rm dist/archey 85 | 86 | - name: Standalone building (with PEX) 87 | run: | 88 | pex \ 89 | -o dist/archey \ 90 | -m archey \ 91 | . 92 | time ./dist/archey 93 | rm dist/archey 94 | 95 | - name: Standalone building (with Stickytape) 96 | run: | 97 | stickytape \ 98 | --copy-shebang \ 99 | --add-python-path . \ 100 | --output-file dist/archey \ 101 | --add-python-module archey.logos."$(python -c 'import distro; print(distro.id())')" \ 102 | archey/__main__.py 103 | chmod +x dist/archey 104 | time ./dist/archey 105 | rm dist/archey 106 | 107 | - name: Standalone building (with PyInstaller) 108 | run: | 109 | pyinstaller \ 110 | --distpath dist \ 111 | --specpath dist \ 112 | --name archey \ 113 | --onefile archey/__main__.py \ 114 | --hidden-import archey.logos."$(python -c 'import distro; print(distro.id())')" \ 115 | --log-level WARN 116 | time ./dist/archey 117 | rm dist/archey 118 | 119 | python_lint: 120 | name: Lint Python sources 121 | runs-on: ubuntu-latest 122 | 123 | steps: 124 | - uses: actions/checkout@v4 125 | 126 | - uses: actions/setup-python@v5 127 | with: 128 | python-version: "3.11" 129 | 130 | - name: Install required dependencies 131 | run: | 132 | python -m pip install --upgrade pip 133 | pip install pylint pylint-secure-coding-standard mypy black isort 134 | pip install . 135 | 136 | - name: Lint sources against Pylint 137 | run: pylint archey/ 138 | 139 | - name: Lint sources against Mypy 140 | run: mypy archey/ 141 | 142 | - name: Run isort 143 | run: isort --check --diff archey/ 144 | 145 | - name: Run Black 146 | run: black --check --diff archey/ 147 | 148 | shell_lint: 149 | name: Lint packaging shell scripts 150 | runs-on: ubuntu-latest 151 | 152 | steps: 153 | - uses: actions/checkout@v4 154 | 155 | - run: shellcheck packaging/* 156 | 157 | man_lint: 158 | name: Lint manual page 159 | runs-on: ubuntu-latest 160 | 161 | steps: 162 | - uses: actions/checkout@v4 163 | 164 | - run: sudo apt update && sudo apt install -y groff 165 | 166 | - run: | 167 | groff -man -Tascii -z archey.1 2&>1 | tee errors 168 | test ! -s errors 169 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Python ### 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | pytestdebug.log 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | doc/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | #poetry.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | # .env 111 | .env/ 112 | .venv/ 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | pythonenv* 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | 138 | # pytype static type analyzer 139 | .pytype/ 140 | 141 | # operating system-related files 142 | # file properties cache/storage on macOS 143 | *.DS_Store 144 | # thumbnail cache on Windows 145 | Thumbs.db 146 | 147 | # profiling data 148 | .prof 149 | -------------------------------------------------------------------------------- /COPYRIGHT.md: -------------------------------------------------------------------------------- 1 | # Copyrights 2 | 3 | Copyright 2010 Melik Manukyan 4 | Copyright 2010 David Vazgenovich Shakaryan 5 | Copyright 2017-2025 Samuel Forestier 6 | 7 | ## License 8 | 9 | This program IS A FORK of the original Archey project . 10 | 11 | Distributed under the terms of the GNU General Public License v3. 12 | See for the full license text. 13 | 14 | ## Contributors 15 | 16 | * ASCII art by Brett Bohnenkamper 17 | * Changes Jerome Launay 18 | * Fedora support by YeOK 19 | * First IP handling by Normand Cyr (@normcyr) 20 | * First IPv6 support and Windows Subsystems handling by @Si13n7 21 | * Timeout improvements and `hostname` investigations by @AdJaGu 22 | * Workaround for Tinkerboard Linux builds by @thisforeda 23 | * Windows Subsystem Linux file-systems support by @Naragato 24 | * `lsb-release` dropping support guidance by Eli Schwartz 25 | * Major software architecture rework by @lannuttia 26 | * Configurable thresholds for disk and RAM colors by Chris Roth (@czr137) 27 | * BTRFS disk support proper implementation (and much more) by Michael Bromilow (@ingrinder) 28 | * Elementary OS proper ASCII logo integration by @SomethingGeneric 29 | * Pop!\_OS proper ASCII logo integration by @airvue 30 | * Project logo by Brume 31 | * Red Hat logo "The hat" by @helmchen 32 | 33 | ## Maintainers 34 | 35 | * Michael Bromilow (@ingrinder) 36 | * Samuel Forestier (@HorlogeSkynet) 37 | 38 | ## Packager(s) 39 | 40 | * Samuel Forestier (@HorlogeSkynet) 41 | -------------------------------------------------------------------------------- /apparmor.profile: -------------------------------------------------------------------------------- 1 | # Archey4 AppArmor profile 2 | # Copyright (C) 2023-2024 - Michael Bromilow 3 | # Copyright (C) 2023-2024 - Samuel Forestier 4 | 5 | # /!\ DO NOT MODIFY THIS FILE /!\ 6 | # Please edit local profile extension (/etc/apparmor.d/local/usr.bin.archey4). 7 | 8 | abi , 9 | 10 | include 11 | 12 | profile archey4 /usr/{,local/}bin/archey{,4} { 13 | include 14 | include 15 | include 16 | include 17 | include 18 | include 19 | 20 | /usr/bin/ r, 21 | /usr/{,local/}bin/archey{,4} r, 22 | 23 | # configuration files 24 | owner @{HOME}/.config/archey4/*.json r, 25 | /etc/archey4/*.json r, 26 | 27 | # required in order to kill sub-processes in timeout 28 | capability kill, 29 | signal (send), 30 | 31 | # allow running processes listing through ps 32 | /{,usr/}bin/ps PUx, 33 | 34 | # allow distro to parse system data sources 35 | /usr/lib/os-release r, 36 | /etc/*[-_]{release,version} r, 37 | /{,usr/}bin/lsb_release PUx, 38 | /{,usr/}bin/uname PUx, 39 | 40 | # allow screenshot tools execution 41 | /{,usr/}bin/escrotum PUx, 42 | /{,usr/}bin/flameshot PUx, 43 | /{,usr/}bin/gnome-screenshot PUx, 44 | /{,usr/}bin/grim PUx, 45 | /{,usr/}bin/import-im6.q16{,hdri} PUx, 46 | /{,usr/}bin/maim PUx, 47 | /{,usr/}bin/scrot PUx, 48 | /{,usr/}bin/shutter PUx, 49 | /{,usr/}bin/spectacle PUx, 50 | /{,usr/}bin/xfce4-screenshoter PUx, 51 | 52 | # [CPU] entry 53 | /{,usr/}bin/lscpu PUx, 54 | 55 | # [Desktop Environment] entry 56 | /usr/share/xsessions/*.desktop r, 57 | 58 | # [Disk] entry 59 | /{,usr/}bin/df PUx, 60 | 61 | # [GPU] entry 62 | /{,usr/}bin/lspci PUx, 63 | @{sys}/kernel/debug/dri/[0-9]*/{name,v3d_ident} r, 64 | 65 | # [Hostname] entry 66 | /etc/hostname r, 67 | 68 | # [Load Average] entry 69 | @{PROC}/loadavg r, 70 | 71 | # [Model] entry 72 | @{PROC}/device-tree/model r, 73 | @{sys}/devices/virtual/dmi/id/* r, 74 | /{,usr/}bin/systemd-detect-virt PUx, 75 | /{,usr/}{,s}bin/virt-what PUx, 76 | /{,usr/}bin/getprop PUx, 77 | 78 | # [Packages] entry 79 | /{,usr/}bin/ls rix, 80 | /{,usr/}bin/apk PUx, 81 | #/{,usr/}bin/apt PUx, 82 | /{,usr/}bin/dnf PUx, 83 | /{,usr/}bin/dpkg PUx, 84 | /{,usr/}bin/emerge PUx, 85 | /usr/{,local/}bin/flatpak PUx, 86 | /{,usr/}bin/nix-env PUx, 87 | /{,usr/}bin/pacman PUx, 88 | /{,usr/}bin/pacstall PUx, 89 | /{,usr/}bin/pkgin PUx, 90 | /{,usr/}bin/port PUx, 91 | /{,usr/}bin/rpm PUx, 92 | /usr/{,local/}bin/snap PUx, 93 | /{,usr/}bin/yum PUx, 94 | /{,usr/}bin/zypper PUx, 95 | 96 | # [RAM] entry 97 | /{,usr/}bin/free rix, 98 | 99 | # [Temperature] entry 100 | @{sys}/devices/thermal/thermal_zone[0-9]*/temp r, 101 | /{,usr/}bin/sensors PUx, 102 | /{,opt/vc/,usr/}bin/vcgencmd PUx, 103 | 104 | # [Uptime] entry 105 | @{PROC}/uptime r, 106 | /{,usr/}bin/uptime rix, 107 | 108 | # [User] & [Shell] entries 109 | /{,usr/}bin/getent rix, 110 | 111 | # [WAN IP] entry (and potentially [Kernel]) 112 | /{,usr/}bin/dig PUx, 113 | network inet stream, # urllib (HTTP/IP) 114 | network inet6 stream, # urllib (HTTP/IPv6) 115 | 116 | # [Window Manager] entry 117 | /{,usr/}bin/wmctrl PUx, 118 | 119 | # allow profile extension (e.g. for user-defined [Custom] entries) 120 | include if exists 121 | } 122 | -------------------------------------------------------------------------------- /archey.1: -------------------------------------------------------------------------------- 1 | .\" Please, before submitting any change, run: 2 | .\" `groff -man -Tascii -z archey.1` 3 | 4 | .TH ARCHEY4 1 "${DATE}" "archey4 ${VERSION}" "Archey4 man page" 5 | 6 | .SH NAME 7 | archey4 \- A simple system information tool written in Python 8 | 9 | .SH SYNOPSIS 10 | \fBarchey\fR [options] 11 | .br 12 | \fBarchey4\fR [options] 13 | 14 | .SH DESCRIPTION 15 | Archey4 is a \fBmaintained\fR fork of the original Archey Linux system 16 | tool. 17 | .br 18 | The original Archey program had been written by Melik Manukyan 19 | in 2009, and quickly abandoned in 2011. 20 | .br 21 | At first, it only supported Arch Linux distribution, further support 22 | had been added afterwards. 23 | .br 24 | Many forks popped in the wild due to inactivity, but this one attends 25 | since 2017 to succeed where the others failed: 26 | .br 27 | Remain \fImaintained\fR, \fIcommunity-driven\fR and 28 | \fIhighly-compatible\fR with yesterday's and today's systems. 29 | 30 | .SH OPTIONS 31 | .IP "-h, --help" 32 | show help message and exit 33 | 34 | .IP "-c, --config-path PATH" 35 | path to a configuration file, or a directory containing a `config.json` 36 | 37 | .IP "-d, --distribution IDENTIFIER" 38 | supported distribution identifier to show the logo of, pass `unknown` to list them 39 | 40 | .IP "-j, --json" 41 | output entries data to JSON format, use multiple times to increase 42 | indentation 43 | 44 | .IP "-l, --logo-style IDENTIFIER" 45 | alternative logo style identifier to show instead of the distribution default one. 46 | For instance, you can try '\fBretro\fR' to prefer old Apple's logo on Darwin 47 | platforms. Pass '\fBnone\fR' to completely hide distribution logo. 48 | 49 | .IP "-s, --screenshot [FILENAME]" 50 | take a screenshot once execution is done, optionally specify a target 51 | path 52 | 53 | .IP "-v, --version" 54 | show program's version number and exit 55 | 56 | .P 57 | Archey will regularly run in terminal text output mode if no argument 58 | is passed. 59 | 60 | .SH ENVIRONMENT VARIABLES 61 | .IP NO_COLOR 62 | prevent ANSI-colored output, see 63 | 64 | .IP CLICOLOR 65 | prefer ANSI-colored output when the program isn't piped, see 66 | 67 | 68 | .IP CLICOLOR_FORCE 69 | force ANSI-colored output, see 70 | 71 | .IP DO_NOT_TRACK 72 | prevent connections to external services on the Internet, see 73 | 74 | 75 | .SH EXIT STATUS 76 | Archey exits with \fB0\fR on success and \fB1\fR on failure. 77 | .br 78 | On arguments parsing error, it exits with \fB2\fR. 79 | 80 | .SH FILES 81 | .I /etc/archey4/config.json 82 | .br 83 | .I ~/.config/archey4/config.json 84 | .br 85 | .I ./config.json 86 | .PP 87 | Please refer to \fBREADME.md\fR for further documentation about 88 | configuration files. 89 | 90 | .SH BUGS 91 | Please report any bug to or 92 | . 93 | 94 | .SH SEE ALSO 95 | NEOFETCH(1), SCREENFETCH(1) 96 | -------------------------------------------------------------------------------- /archey/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HorlogeSkynet/archey4/f9e98a281bd6832fa7f3abf353dae16853d01f02/archey/__init__.py -------------------------------------------------------------------------------- /archey/_version.py: -------------------------------------------------------------------------------- 1 | """Simple module storing the current project version""" 2 | 3 | __version__ = "4.15.0.0" 4 | -------------------------------------------------------------------------------- /archey/api.py: -------------------------------------------------------------------------------- 1 | """Archey API module""" 2 | 3 | import json 4 | from datetime import datetime 5 | from typing import Sequence 6 | 7 | from archey._version import __version__ 8 | from archey.distributions import Distributions 9 | from archey.entry import Entry 10 | from archey.utility import Utility 11 | 12 | 13 | class API: 14 | """ 15 | This class provides results serialization for external usages. 16 | At the moment, only JSON has been implemented. 17 | Feel free to contribute to add other formats as needed. 18 | """ 19 | 20 | def __init__(self, entries: Sequence[Entry]): 21 | self.entries = entries 22 | 23 | def json_serialization(self, indent: int = 0) -> str: 24 | """ 25 | JSON serialization of entries. 26 | Set `indent` to the number of wanted output indentation tabs (2-space long). 27 | """ 28 | document = { 29 | "data": {entry.name: entry.value for entry in self.entries}, 30 | "meta": { 31 | "version": Utility.version_to_semver_segments(__version__), 32 | "date": datetime.now().isoformat(), 33 | "count": len(self.entries), 34 | "distro": Distributions.get_local().value, 35 | }, 36 | } 37 | 38 | return json.dumps(document, indent=((indent * 2) or None)) 39 | -------------------------------------------------------------------------------- /archey/colors.py: -------------------------------------------------------------------------------- 1 | """Colors enumeration definition""" 2 | 3 | import re 4 | import sys 5 | from bisect import bisect 6 | from enum import Enum 7 | from functools import lru_cache 8 | 9 | from archey.environment import Environment 10 | 11 | # REGEXP compiled pattern matching ANSI/ECMA-48 color escape codes. 12 | ANSI_TEXT_CODES_REGEXP = re.compile(r"\d+(?:(?:;\d+)+)?") 13 | ANSI_ECMA_REGEXP = re.compile(rf"\x1b\[{ANSI_TEXT_CODES_REGEXP.pattern}m") 14 | 15 | 16 | class Style: 17 | """ 18 | Style base-class supporting terminal escape sequences for bold, colour, etc. 19 | Supports an arbitrary number of display attributes. 20 | """ 21 | 22 | def __str__(self): 23 | if self.should_color_output(): 24 | return self.escape_code_from_attrs(";".join(map(str, self.value))) # type: ignore[attr-defined] # pylint: disable=no-member,line-too-long 25 | return "" 26 | 27 | @staticmethod 28 | @lru_cache(maxsize=None) # Python < 3.9, `functools.cache` is not yet available. 29 | def should_color_output() -> bool: 30 | """ 31 | Returns whether or not output should be colored, according to runtime environment. 32 | Current implementation is specific to Archey as it's not standardized (see jcs/no_color#28). 33 | """ 34 | if Environment.CLICOLOR_FORCE: 35 | return True 36 | 37 | if Environment.NO_COLOR: 38 | return False 39 | 40 | return sys.stdout.isatty() and Environment.CLICOLOR 41 | 42 | @staticmethod 43 | def remove_colors(string: str) -> str: 44 | """Simple DRY method to remove any ANSI/ECMA-48 color escape code from passed `string`""" 45 | return ANSI_ECMA_REGEXP.sub("", string) 46 | 47 | @classmethod 48 | def escape_code_from_attrs(cls, display_attrs: str) -> str: 49 | """ 50 | Build and return an ANSI/ECMA-48 escape code string from passed display attributes. 51 | """ 52 | return f"\x1b[{display_attrs}m" 53 | 54 | 55 | class Colors(Style, Enum): 56 | """ 57 | ANSI terminal colors enumeration. 58 | 59 | See 60 | or . 61 | """ 62 | 63 | CLEAR = (0,) 64 | RED_NORMAL = (0, 31) 65 | RED_BRIGHT = (1, 31) 66 | GREEN_NORMAL = (0, 32) 67 | GREEN_BRIGHT = (1, 32) 68 | YELLOW_NORMAL = (0, 33) 69 | YELLOW_BRIGHT = (1, 33) 70 | BLUE_NORMAL = (0, 34) 71 | BLUE_BRIGHT = (1, 34) 72 | MAGENTA_NORMAL = (0, 35) 73 | MAGENTA_BRIGHT = (1, 35) 74 | CYAN_NORMAL = (0, 36) 75 | CYAN_BRIGHT = (1, 36) 76 | WHITE_NORMAL = (0, 37) 77 | WHITE_BRIGHT = (1, 37) 78 | 79 | # Python 3.6 compatibility (bug in enum overriding __str__ from mixin?) 80 | def __str__(self): # pylint: disable=useless-parent-delegation 81 | return super().__str__() 82 | 83 | # Python 3.6 compatibility due to string format changes, see 84 | # (bpo-28794) 85 | def __format__(self, _): 86 | return super().__str__() 87 | 88 | @staticmethod 89 | def get_level_color(value: float, yellow_bpt: float, red_bpt: float) -> "Colors": 90 | """Returns the best level color according to `value` compared to `{yellow,red}_bpt`""" 91 | level_colors = (Colors.GREEN_NORMAL, Colors.YELLOW_NORMAL, Colors.RED_NORMAL) 92 | return level_colors[bisect((yellow_bpt, red_bpt), value)] 93 | 94 | 95 | class Colors8Bit(Style): 96 | """ 97 | ANSI Terminal colors using 8-bit values (256 available) 98 | Instantiated using a `bright` int and `value` int, similar to a `Colors` tuple. 99 | See . 100 | """ 101 | 102 | def __init__(self, bright: int, value: int): 103 | if bright not in (0, 1) or value not in range(0, 255): 104 | raise ValueError("Supplied color is outside the allowed range.") 105 | # `ESC[38;5` selects 8-bit foreground colour 106 | self.value = (bright, 38, 5, value) 107 | -------------------------------------------------------------------------------- /archey/configuration.py: -------------------------------------------------------------------------------- 1 | """Archey configuration module""" 2 | 3 | import json 4 | import logging 5 | import os 6 | from copy import deepcopy 7 | from typing import Any, Dict 8 | 9 | from archey.colors import ANSI_TEXT_CODES_REGEXP 10 | from archey.singleton import Singleton 11 | from archey.utility import Utility 12 | 13 | # Below are default required configuration keys which will be used. 14 | DEFAULT_CONFIG: Dict[str, Any] = { 15 | "allow_overriding": True, 16 | "parallel_loading": True, 17 | "suppress_warnings": False, 18 | "entries_color": "", 19 | "honor_ansi_color": True, 20 | "entries_icon": False, 21 | "default_strings": { 22 | "latest": "latest", 23 | "available": "available", 24 | "no_address": "No Address", 25 | "not_detected": "Not detected", 26 | "virtual_environment": "Virtual Environment", 27 | }, 28 | } 29 | 30 | 31 | class Configuration(metaclass=Singleton): 32 | """ 33 | Values present in `DEFAULT_CONFIG` dictionary are required. 34 | New optional values may be added with `Utility.update_recursive` method. 35 | 36 | If a `config_path` is passed during instantiation, it will be loaded. 37 | """ 38 | 39 | def __init__(self, config_path=None): 40 | # Deep-copy `DEFAULT_CONFIG` so we have a local copy to safely mutate. 41 | self._config = deepcopy(DEFAULT_CONFIG) 42 | 43 | # We will track successfully loaded configuration files stat info. 44 | self._config_files_info = {} 45 | 46 | # If a `config_path` has been specified, (try to) load it directly. 47 | if config_path: 48 | self._load_configuration(config_path) 49 | # If not, load each (optional) configuration file in a "regular" order. 50 | else: 51 | self._load_configuration("/etc/archey4/") 52 | self._load_configuration(os.path.expanduser("~/.config/archey4/")) 53 | self._load_configuration(os.getcwd()) 54 | 55 | # Perform various validations 56 | self._validate_configuration() 57 | 58 | def get(self, key: str, default=None) -> Any: 59 | """ 60 | A binding method to imitate the `dict.get()` behavior. 61 | """ 62 | return self._config.get(key, default) 63 | 64 | def get_config_files_info(self) -> Dict[str, os.stat_result]: 65 | """Return a copy of loaded files stat info data""" 66 | return self._config_files_info.copy() 67 | 68 | def _load_configuration(self, path: str) -> None: 69 | """ 70 | A method handling configuration loading from a JSON file. 71 | It will try to load any `config.json` present under `path`. 72 | """ 73 | # If a previous configuration file has denied overriding... 74 | if not self.get("allow_overriding"): 75 | # ... don't load this one. 76 | return 77 | 78 | # If the specified `path` is a directory, append the file name we are looking for. 79 | if os.path.isdir(path): 80 | path = os.path.join(path, "config.json") 81 | 82 | try: 83 | with open(path, mode="rb") as f_config: 84 | Utility.update_recursive(self._config, json.load(f_config)) 85 | self._config_files_info[path] = os.fstat(f_config.fileno()) 86 | except FileNotFoundError: 87 | return 88 | except (PermissionError, json.JSONDecodeError) as error: 89 | logging.error("%s (%s)", error, path) 90 | return 91 | 92 | # When `suppress_warnings` is set, higher the log level to silence warning messages. 93 | logging.getLogger().setLevel( 94 | logging.ERROR if self.get("suppress_warnings") else logging.WARN 95 | ) 96 | 97 | def _validate_configuration(self) -> None: 98 | # entries_color 99 | entries_color = self._config.get("entries_color") 100 | if entries_color: 101 | if ( 102 | not isinstance(entries_color, str) 103 | or ANSI_TEXT_CODES_REGEXP.fullmatch(entries_color) is None 104 | ): 105 | logging.warning( 106 | "Couldn't validate 'entries_color' configuration option value, ignoring..." 107 | ) 108 | self._config["entries_color"] = DEFAULT_CONFIG["entries_color"] 109 | 110 | def __iter__(self): 111 | """When used as an iterator, directly yield `_config` elements""" 112 | return iter(self._config.items()) 113 | -------------------------------------------------------------------------------- /archey/distributions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Distributions enumeration module. 3 | Operating Systems detection logic. 4 | Interface to `os-release` (through `distro` module). 5 | """ 6 | 7 | import os 8 | import platform 9 | import sys 10 | from contextlib import suppress 11 | from enum import Enum 12 | from functools import lru_cache 13 | from typing import List, Optional 14 | 15 | import distro 16 | 17 | 18 | class Distributions(Enum): 19 | """ 20 | This enumeration lists supported operating systems (keys). 21 | Values contain their respective `distro` identifier. 22 | See . 23 | """ 24 | 25 | ALPINE = "alpine" 26 | ANDROID = "android" 27 | ARCH = "arch" 28 | ARMBIAN = "armbian" 29 | BUILDROOT = "buildroot" 30 | BUNSENLABS = "bunsenlabs" 31 | CENTOS = "centos" 32 | CRUNCHBANG = "crunchbang" 33 | DARWIN = "darwin" 34 | DEBIAN = "debian" 35 | DEVUAN = "devuan" 36 | ELEMENTARY = "elementary" 37 | ENDEAVOUROS = "endeavouros" 38 | ENSO = "enso" 39 | FEDORA = "fedora" 40 | FREEBSD = "freebsd" 41 | GENTOO = "gentoo" 42 | GUIX = "guix" 43 | KALI = "kali" 44 | MANJARO = "manjaro" 45 | MOEVALENT = "moevalent" 46 | NETBSD = "netbsd" 47 | NIXOS = "nixos" 48 | NOBARA = "nobara" 49 | LINUX = "linux" 50 | LINUXMINT = "linuxmint" 51 | OPENBSD = "openbsd" 52 | OPENSUSE = "opensuse" 53 | POP = "pop" 54 | PARABOLA = "parabola" 55 | QUIRINUX = "quirinux" 56 | RASPBIAN = "raspbian" 57 | ROCKY = "rocky" 58 | RHEL = "rhel" 59 | SIDUCTION = "siduction" 60 | SLACKWARE = "slackware" 61 | UBUNTU = "ubuntu" 62 | UNIVALENT = "univalent" 63 | WINDOWS = "windows" 64 | 65 | @staticmethod 66 | def get_identifiers() -> List[str]: 67 | """Simple getter returning current supported distributions identifiers""" 68 | return [d.value for d in Distributions.__members__.values()] 69 | 70 | @staticmethod 71 | @lru_cache(maxsize=None) # Python < 3.9, `functools.cache` is not yet available. 72 | def get_local() -> "Distributions": # pylint: disable=too-many-return-statements 73 | """Entry point of Archey distribution detection logic""" 74 | distribution = Distributions._vendor_detection() 75 | 76 | # In case nothing got detected the "regular" way... 77 | if not distribution: 78 | # Are we running on Darwin (somehow not previously detected by `distro`) ? 79 | if platform.system() == "Darwin": 80 | return Distributions.DARWIN 81 | 82 | # Android systems are currently not being handled by `distro`. 83 | # At first, we imitate the Python standard library, by checking whether CPython 84 | # has been built for Android. 85 | # See 86 | # As a fallback, we mimic Neofetch behavior, by relying on the file-system. 87 | # See 88 | if hasattr(sys, "getandroidapilevel") or ( 89 | os.path.isdir("/system/app") and os.path.isdir("/system/priv-app") 90 | ): 91 | return Distributions.ANDROID 92 | 93 | # If nothing of the above matched, fall-back on the Linux logo. 94 | return Distributions.LINUX 95 | 96 | # Below are brain-dead cases for distributions not properly handled by `distro`. 97 | # One _may_ want to add its own logic to add support for such undetectable systems. 98 | if distribution == Distributions.DEBIAN: 99 | # CrunchBang is tagged as _regular_ Debian by `distro`. 100 | # Below conditions are here to work-around this issue. 101 | # First condition : CrunchBang-Linux and CrunchBang-Monara. 102 | # Second condition : CrunchBang++ (CBPP). 103 | if os.path.isfile("/etc/lsb-release-crunchbang") or os.path.isfile( 104 | "/usr/bin/cbpp-exit" 105 | ): 106 | return Distributions.CRUNCHBANG 107 | 108 | # Armbian is also detected as _regular_ Debian by `distro`, but going directly 109 | # through release info gives us a proper id (see ). 110 | if distro.distro_release_attr("id") == "armbian": 111 | return Distributions.ARMBIAN 112 | 113 | elif distribution == Distributions.UBUNTU: 114 | # Older Pop!_OS releases (< 20.*) didn't ship their own `ID` (from `os-release`). 115 | # Thus, they are detected as "regular" Ubuntu distributions. 116 | # We may here rely on their `NAME` (from `os-release`), which is sufficient. 117 | if Distributions.get_distro_name(pretty=False) == "Pop!_OS": 118 | return Distributions.POP 119 | 120 | return distribution 121 | 122 | @staticmethod 123 | def _vendor_detection() -> Optional["Distributions"]: 124 | """Main distribution detection logic, relying on `distro`, handling _common_ cases""" 125 | # Are we running on Windows ? 126 | if platform.system() == "Windows": 127 | return Distributions.WINDOWS 128 | 129 | # Is `ID` (from `os-release`) well-known and supported ? 130 | with suppress(ValueError): 131 | return Distributions(distro.id()) 132 | 133 | # Is any of `ID_LIKE` (from `os-release`) well-known and supported ? 134 | # See . 135 | for id_like in distro.like().split(" "): 136 | with suppress(ValueError): 137 | return Distributions(id_like) 138 | 139 | # Nothing of the above matched, let's return `None` and let the caller handle it. 140 | return None 141 | 142 | @staticmethod 143 | def get_distro_name(pretty: bool = True) -> Optional[str]: 144 | """Simple wrapper to `distro` to return the current distribution _pretty_ name""" 145 | return distro.name(pretty=pretty) or None 146 | 147 | @staticmethod 148 | def get_ansi_color() -> Optional[str]: 149 | """ 150 | Simple wrapper to `distro` to return the distribution preferred ANSI color. 151 | See . 152 | """ 153 | return distro.os_release_attr("ansi_color") or None 154 | -------------------------------------------------------------------------------- /archey/entries/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HorlogeSkynet/archey4/f9e98a281bd6832fa7f3abf353dae16853d01f02/archey/entries/__init__.py -------------------------------------------------------------------------------- /archey/entries/custom.py: -------------------------------------------------------------------------------- 1 | """Custom entry class""" 2 | 3 | import logging 4 | import os 5 | import stat 6 | from contextlib import suppress 7 | from subprocess import DEVNULL, PIPE, CalledProcessError, run 8 | from typing import List, Union 9 | 10 | from archey.configuration import Configuration 11 | from archey.entry import Entry 12 | 13 | 14 | class Custom(Entry): 15 | """Custom entry gathering info based on configuration options""" 16 | 17 | _ICON = "\uf013" # fa_cog 18 | 19 | def __new__(cls, *_, **kwargs): 20 | # Don't load this entry if a configuration file has too broad permissions. 21 | # We want to mitigate LPE attacks, as arbitrary commands could be run from a configuration 22 | # file under another user's control (with write permissions). 23 | geteuid = getattr(os, "geteuid", None) 24 | for config_path, stat_info in Configuration().get_config_files_info().items(): 25 | if ( 26 | stat_info.st_uid != 0 and geteuid is not None and stat_info.st_uid != geteuid() 27 | ) or stat_info.st_mode & (stat.S_IWGRP | stat.S_IWOTH): 28 | logging.getLogger(cls.__module__).warning( 29 | "Not loading %s entry as %s config file has too broad permissions (%s).", 30 | cls.__name__, 31 | config_path, 32 | stat.filemode(stat_info.st_mode), 33 | ) 34 | return None 35 | 36 | return super().__new__(cls, **kwargs) 37 | 38 | def __init__(self, *args, **kwargs): 39 | super().__init__(*args, **kwargs) 40 | 41 | command: Union[str, List[str]] 42 | 43 | shell = self.options.get("shell", False) 44 | if shell: 45 | command = self.options["command"] 46 | else: 47 | command = self.options["command"] 48 | 49 | log_stderr = self.options.get("log_stderr", True) 50 | 51 | with suppress(CalledProcessError): 52 | proc = run( 53 | command, 54 | stdout=PIPE, 55 | stderr=PIPE if log_stderr else DEVNULL, 56 | shell=shell, 57 | check=self.options.get("check", True), 58 | universal_newlines=True, 59 | ) 60 | if proc.stdout: 61 | self.value = proc.stdout.rstrip().splitlines() 62 | 63 | if log_stderr and proc.stderr: 64 | self._logger.warning("%s", proc.stderr.rstrip()) 65 | 66 | def output(self, output) -> None: 67 | if not self.value: 68 | output.append(self.name, self._default_strings.get("not_detected")) 69 | return 70 | 71 | # Join the results only if `one_line` option is enabled. 72 | if self.options.get("one_line", True): 73 | output.append(self.name, ", ".join(self.value)) 74 | else: 75 | for element in self.value: 76 | output.append(self.name, element) 77 | -------------------------------------------------------------------------------- /archey/entries/desktop_environment.py: -------------------------------------------------------------------------------- 1 | """Desktop environment detection class""" 2 | 3 | import configparser 4 | import os 5 | import platform 6 | import typing 7 | from contextlib import suppress 8 | 9 | from archey.entry import Entry 10 | from archey.processes import Processes 11 | 12 | DE_PROCESSES = { 13 | "cinnamon": "Cinnamon", 14 | "dde-dock": "Deepin", 15 | "fur-box-session": "Fur Box", 16 | "gnome-session": "GNOME", 17 | "gnome-shell": "GNOME", 18 | "ksmserver": "KDE", 19 | "lxqt-session": "LXQt", 20 | "lxsession": "LXDE", 21 | "mate-session": "MATE", 22 | "xfce4-session": "Xfce", 23 | } 24 | 25 | # From : 26 | XDG_DESKTOP_NORMALIZATION = { 27 | "DDE": "Deepin", 28 | "ENLIGHTENMENT": "Enlightenment", 29 | "GNOME-CLASSIC": "GNOME Classic", 30 | "GNOME-FLASHBACK": "GNOME Flashback", 31 | "RAZOR": "Razor-qt", 32 | "TDE": "Trinity", 33 | "X-CINNAMON": "Cinnamon", 34 | } 35 | 36 | # (partly) from : 37 | DE_NORMALIZATION = { 38 | "budgie-desktop": "Budgie", 39 | "cinnamon": "Cinnamon", 40 | "deepin": "Deepin", 41 | "enlightenment": "Enlightenment", 42 | "gnome": "Gnome", 43 | "kde": "KDE", 44 | "lumina": "Lumina", 45 | "lxde": "LXDE", 46 | "lxqt": "LXQt", 47 | "mate": "MATE", 48 | "muffin": "Cinnamon", 49 | "trinity": "Trinity", 50 | "xfce session": "Xfce", 51 | "xfce": "Xfce", 52 | "xfce4": "Xfce", 53 | "xfce5": "Xfce", 54 | } 55 | 56 | 57 | class DesktopEnvironment(Entry): 58 | """ 59 | Return static values for macOS and Windows. 60 | On Linux, use extensive environment variables processing to find known identifiers. 61 | Fallback on running processes to find a known-entry. 62 | """ 63 | 64 | _ICON = "\ue23c" # fae_restore 65 | _PRETTY_NAME = "Desktop Environment" 66 | 67 | def __init__(self, *args, **kwargs): 68 | super().__init__(*args, **kwargs) 69 | 70 | self.value = ( 71 | self._platform_detection() or self._environment_detection() or self._process_detection() 72 | ) 73 | 74 | @staticmethod 75 | def _platform_detection() -> typing.Optional[str]: 76 | # macOS' desktop environment is called "Aqua", 77 | # and could not be detected from processes list. 78 | if platform.system() == "Darwin": 79 | return "Aqua" 80 | 81 | # Same thing for Windows, based on release version. 82 | if platform.system() == "Windows": 83 | windows_release = platform.win32_ver()[0] 84 | if windows_release in ("Vista", "7"): 85 | return "Aero" 86 | if windows_release in ("8", "10"): 87 | return "Metro" 88 | 89 | return None 90 | 91 | @staticmethod 92 | def _environment_detection() -> ( 93 | typing.Optional[str] 94 | ): # pylint: disable=too-many-return-statements 95 | """Implement same algorithm xdg-utils uses""" 96 | # Honor XDG_CURRENT_DESKTOP (if set) 97 | desktop_identifiers = os.getenv("XDG_CURRENT_DESKTOP", "").split(":") 98 | if desktop_identifiers[0]: 99 | return XDG_DESKTOP_NORMALIZATION.get( 100 | desktop_identifiers[0].upper(), desktop_identifiers[0] 101 | ) 102 | 103 | # Honor known environment-specific variables 104 | if "GNOME_DESKTOP_SESSION_ID" in os.environ: 105 | return "GNOME" 106 | if "HYPRLAND_CMD" in os.environ: 107 | return "Hyprland" 108 | if "KDE_FULL_SESSION" in os.environ: 109 | return "KDE" 110 | if "MATE_DESKTOP_SESSION_ID" in os.environ: 111 | return "MATE" 112 | if "TDE_FULL_SESSION" in os.environ: 113 | return "Trinity" 114 | 115 | # Fallback to (known) "DE"/"DESKTOP_SESSION" legacy environment variables 116 | legacy_de = os.getenv("DE", "").lower() 117 | if legacy_de in DE_NORMALIZATION: 118 | return DE_NORMALIZATION[legacy_de] 119 | 120 | desktop_session = os.getenv("DESKTOP_SESSION") 121 | if desktop_session is not None: 122 | # If DESKTOP_SESSION corresponds to a session's desktop entry path, parse and honor it 123 | with suppress(ValueError, OSError, configparser.Error): 124 | desktop_file = os.path.realpath(desktop_session) 125 | if ( 126 | os.path.commonprefix([desktop_file, "/usr/share/xsessions"]) 127 | == "/usr/share/xsessions" 128 | ): 129 | # Don't expect anything from .desktop files and parse them in a best-effort way 130 | config = configparser.ConfigParser(allow_no_value=True, strict=False) 131 | with open(desktop_file, encoding="utf-8") as f_desktop_file: 132 | config.read_string(f_desktop_file.read()) 133 | return ( 134 | # Honor `DesktopNames` option with `X-LightDM-DesktopName` as a fallback 135 | config.get("Desktop Entry", "DesktopNames", fallback=None) 136 | or config.get("Desktop Entry", "X-LightDM-DesktopName") 137 | ).split(";")[0] 138 | 139 | # If not or if file couldn't be read, check whether it corresponds to a known identifier 140 | if desktop_session.lower() in DE_NORMALIZATION: 141 | return DE_NORMALIZATION[desktop_session.lower()] 142 | 143 | return None 144 | 145 | @staticmethod 146 | def _process_detection() -> typing.Optional[str]: 147 | processes = Processes().list 148 | for de_id, de_name in DE_PROCESSES.items(): 149 | if de_id in processes: 150 | return de_name 151 | 152 | return None 153 | -------------------------------------------------------------------------------- /archey/entries/distro.py: -------------------------------------------------------------------------------- 1 | """Distribution and architecture detection class""" 2 | 3 | import platform 4 | from subprocess import check_output 5 | from typing import Optional 6 | 7 | from archey.distributions import Distributions 8 | from archey.entry import Entry 9 | 10 | 11 | class Distro(Entry): 12 | """Uses `distro` and `platform` modules to retrieve distribution and architecture information""" 13 | 14 | _ICON = "\uf17c" # fa_linux 15 | 16 | def __init__(self, *args, **kwargs): 17 | super().__init__(*args, **kwargs) 18 | 19 | if platform.system() == "Darwin": 20 | distro_name = self._fetch_darwin_release() 21 | else: 22 | distro_name = Distributions.get_distro_name() or self._fetch_android_release() 23 | 24 | self.value = {"name": distro_name, "arch": platform.machine()} 25 | 26 | @staticmethod 27 | def _fetch_android_release() -> Optional[str]: 28 | """Simple method to fetch current release on Android systems""" 29 | try: 30 | release = check_output( 31 | ["getprop", "ro.build.version.release"], universal_newlines=True 32 | ).rstrip() 33 | except OSError: 34 | return None 35 | 36 | return f"Android {release}" 37 | 38 | @staticmethod 39 | def _fetch_darwin_release() -> Optional[str]: 40 | """Simple method to fetch current release on Darwin systems""" 41 | # For macOS, let's mimic Python's `platform.platform` internal behavior here. 42 | macos_release = platform.mac_ver()[0] 43 | if macos_release: 44 | return f"macOS {macos_release}" 45 | 46 | return f"Darwin {platform.release()}" 47 | 48 | def output(self, output) -> None: 49 | output.append( 50 | self.name, 51 | f"{{}} {self.value['arch']}".format( 52 | self.value["name"] or self._default_strings.get("not_detected") 53 | ), 54 | ) 55 | -------------------------------------------------------------------------------- /archey/entries/hostname.py: -------------------------------------------------------------------------------- 1 | """Host-name detection class""" 2 | 3 | import platform 4 | from typing import Optional 5 | 6 | from archey.entry import Entry 7 | 8 | 9 | class Hostname(Entry): 10 | """Read system file with fallback on `platform` module to retrieve the system host-name""" 11 | 12 | _ICON = "\U000f0318" # md_lan_connect 13 | 14 | def __init__(self, *args, **kwargs): 15 | super().__init__(*args, **kwargs) 16 | 17 | self.value = self._read_etc_hostname() 18 | if not self.value: 19 | self.value = platform.node() 20 | 21 | @staticmethod 22 | def _read_etc_hostname() -> Optional[str]: 23 | try: 24 | with open("/etc/hostname", encoding="UTF-8") as f_hostname: 25 | return f_hostname.read().rstrip() 26 | except FileNotFoundError: 27 | return None 28 | -------------------------------------------------------------------------------- /archey/entries/kernel.py: -------------------------------------------------------------------------------- 1 | """Kernel information detection class""" 2 | 3 | import json 4 | import platform 5 | from socket import timeout as SocketTimeoutError 6 | from typing import Optional 7 | from urllib.error import URLError 8 | from urllib.request import urlopen 9 | 10 | from archey.entry import Entry 11 | from archey.environment import Environment 12 | from archey.utility import Utility 13 | 14 | 15 | class Kernel(Entry): 16 | """ 17 | Retrieve kernel identity. 18 | [GNU/LINUX] If user-enabled, implement a version comparison against upstream data. 19 | """ 20 | 21 | _ICON = "\uf305" # linux_coreos 22 | 23 | def __init__(self, *args, **kwargs): 24 | super().__init__(*args, **kwargs) 25 | 26 | self.value = { 27 | "name": platform.system(), 28 | "release": platform.release(), 29 | "latest": None, 30 | "is_outdated": None, 31 | } 32 | 33 | # On GNU/Linux systems, if `check_version` has been enabled and `DO_NOT_TRACK` isn't set, 34 | # retrieve the latest kernel release in order to compare the current one against it. 35 | if ( 36 | not self.options.get("check_version") 37 | or self.value["name"] != "Linux" 38 | or Environment.DO_NOT_TRACK 39 | ): 40 | return 41 | 42 | self.value["latest"] = self._fetch_latest_linux_release() 43 | if self.value["latest"]: 44 | self.value["is_outdated"] = Utility.version_to_semver_segments( 45 | self.value["release"] 46 | ) < Utility.version_to_semver_segments(self.value["latest"]) 47 | 48 | @staticmethod 49 | def _fetch_latest_linux_release() -> Optional[str]: 50 | try: 51 | with urlopen("https://www.kernel.org/releases.json") as http_request: 52 | try: 53 | kernel_releases = json.load(http_request) 54 | except json.JSONDecodeError: 55 | return None 56 | except (URLError, SocketTimeoutError): 57 | return None 58 | 59 | return kernel_releases.get("latest_stable", {}).get("version") 60 | 61 | def output(self, output) -> None: 62 | """Display running kernel and latest kernel if possible""" 63 | text_output = " ".join((self.value["name"], self.value["release"])) 64 | 65 | if self.value["latest"]: 66 | if self.value["is_outdated"]: 67 | text_output += f" ({self.value['latest']} {self._default_strings.get('available')})" 68 | else: 69 | text_output += f" ({self._default_strings.get('latest')})" 70 | 71 | output.append(self.name, text_output) 72 | -------------------------------------------------------------------------------- /archey/entries/lan_ip.py: -------------------------------------------------------------------------------- 1 | """Local IP addresses detection class""" 2 | 3 | import ipaddress 4 | from itertools import islice 5 | from typing import Iterator 6 | 7 | try: 8 | import netifaces 9 | except ImportError: 10 | netifaces = None 11 | 12 | from archey.entry import Entry 13 | 14 | 15 | class LanIP(Entry): 16 | """Relies on the `netifaces` module to detect LAN IP addresses""" 17 | 18 | _ICON = "\U000f0a60" # md_ip_network 19 | _PRETTY_NAME = "LAN IP" 20 | 21 | def __init__(self, *args, **kwargs): 22 | super().__init__(*args, **kwargs) 23 | 24 | if not netifaces: 25 | self._logger.warning( 26 | "`netifaces` Python module couldn't be found. " 27 | "Please either install it or explicitly disable `LAN_IP` entry in configuration." 28 | ) 29 | return 30 | 31 | # IPv4 will be enabled by default. 32 | addr_families = [netifaces.AF_INET] 33 | if self.options.get("ipv6_support", True): 34 | addr_families.append(netifaces.AF_INET6) 35 | 36 | max_count = self.options.get("max_count", 2) 37 | # Consistency with other entries' configuration: Infinite count if false. 38 | if max_count is False: 39 | max_count = None 40 | 41 | # Global IP addresses (in RFC1918 terms) will be hidden by default. 42 | show_global = bool(self.options.get("show_global")) 43 | 44 | # Link-local IP addresses (in RFC3927 terms) will be shown by default. 45 | show_link_local = bool(self.options.get("show_link_local", True)) 46 | 47 | self.value = list( 48 | islice( 49 | self._lan_ip_addresses_generator(addr_families, show_global, show_link_local), 50 | max_count, 51 | ) 52 | ) 53 | 54 | @staticmethod 55 | def _lan_ip_addresses_generator( 56 | addr_families: list, show_global: bool, show_link_local: bool 57 | ) -> Iterator[str]: 58 | """Generator yielding local IP address according to passed address families""" 59 | # Loop through all available network interfaces. 60 | for if_name in netifaces.interfaces(): 61 | # Fetch associated addresses elements. 62 | if_addrs = netifaces.ifaddresses(if_name) 63 | 64 | for addr_family in addr_families: 65 | for if_addr in if_addrs.get(addr_family, []): 66 | # IPv6 addresses may contain '%' token separator. 67 | ip_addr = ipaddress.ip_address(if_addr["addr"].split("%")[0]) 68 | 69 | # Filter out loopback and public/link-local IP addresses (if enabled). 70 | if ( 71 | not ip_addr.is_loopback 72 | and (not ip_addr.is_global or show_global) 73 | and (not ip_addr.is_link_local or show_link_local) 74 | ): 75 | # Finally, yield the address compressed representation. 76 | yield ip_addr.compressed 77 | 78 | def output(self, output) -> None: 79 | """Adds the entry to `output` after pretty-formatting the IP address list.""" 80 | # If we found IP addresses, join them together nicely. 81 | # If not, fall back on default strings according to `netifaces` availability. 82 | if self.value: 83 | if not self.options.get("one_line", True): 84 | # One-line output has been disabled, add one IP address per item. 85 | for ip_address in self.value: 86 | output.append(self.name, ip_address) 87 | 88 | return 89 | 90 | text_output = ", ".join(self.value) 91 | 92 | elif netifaces: 93 | text_output = self._default_strings.get("no_address") 94 | else: 95 | text_output = self._default_strings.get("not_detected") 96 | 97 | output.append(self.name, text_output) 98 | -------------------------------------------------------------------------------- /archey/entries/load_average.py: -------------------------------------------------------------------------------- 1 | """System load average detection module""" 2 | 3 | import os 4 | from contextlib import suppress 5 | 6 | from archey.colors import Colors 7 | from archey.entry import Entry 8 | 9 | 10 | class LoadAverage(Entry): 11 | """System load average detection entry""" 12 | 13 | _ICON = "\U000f051f" # md_timer_sand 14 | _PRETTY_NAME = "Load Average" 15 | 16 | def __init__(self, *args, **kwargs): 17 | super().__init__(*args, **kwargs) 18 | 19 | with suppress(AttributeError): 20 | self.value = os.getloadavg() 21 | 22 | def output(self, output) -> None: 23 | if not self.value: 24 | # Fall back on the default behavior if load average values could not be detected. 25 | super().output(output) 26 | return 27 | 28 | # DRY constant thresholds. 29 | decimal_places = self.options.get("decimal_places", 2) 30 | warning_threshold = self.options.get("warning_threshold", 1.0) 31 | danger_threshold = self.options.get("danger_threshold", 2.0) 32 | 33 | output.append( 34 | self.name, 35 | " ".join( 36 | [ 37 | str(Colors.get_level_color(load_avg, warning_threshold, danger_threshold)) 38 | + str(round(load_avg, decimal_places)) 39 | + str(Colors.CLEAR) 40 | for load_avg in self.value 41 | ] 42 | ), 43 | ) 44 | -------------------------------------------------------------------------------- /archey/entries/packages.py: -------------------------------------------------------------------------------- 1 | """Number of installed packages detection class""" 2 | 3 | import os 4 | import typing 5 | from contextlib import suppress 6 | from subprocess import DEVNULL, CalledProcessError, check_output 7 | 8 | from archey.distributions import Distributions 9 | from archey.entry import Entry 10 | 11 | 12 | def get_homebrew_cellar_path() -> str: 13 | """Return Homebrew Cellar path (if available)""" 14 | with suppress(OSError, CalledProcessError): 15 | return check_output(["brew", "--cellar"], stderr=DEVNULL, universal_newlines=True).rstrip() 16 | 17 | return "/usr/local/Cellar/" 18 | 19 | 20 | PACKAGES_TOOLS: typing.Tuple[typing.Dict[str, typing.Any], ...] = ( 21 | {"cmd": ("apk", "list", "--installed")}, 22 | # As of 2020, `apt` is _very_ slow compared to `dpkg` on Debian-based distributions. 23 | # Additional note : `apt`'s CLI is currently not "stable" in Debian terms. 24 | # If `apt` happens to be preferred over `dpkg` in the future, don't forget to remove the latter. 25 | # {"cmd": ("apt", "list", "-qq", "--installed")}, 26 | {"cmd": ("dnf", "list", "installed"), "skew": 1}, 27 | {"cmd": ("dpkg", "--get-selections")}, 28 | {"cmd": ("emerge", "-ep", "world"), "skew": 5}, 29 | {"cmd": ("flatpak", "list"), "skew": 1}, 30 | {"cmd": ("ls", "-1", get_homebrew_cellar_path()), "name": "homebrew"}, 31 | {"cmd": ("nix-env", "-q")}, 32 | {"cmd": ("pacman", "-Q")}, 33 | {"cmd": ("pacstall", "-L")}, 34 | {"cmd": ("pkg_info", "-a")}, 35 | { 36 | "cmd": ("pkg", "-N", "info", "-a"), 37 | # Query `pkg` only on *BSD systems to avoid inconsistencies. 38 | "only_on": (Distributions.FREEBSD, Distributions.NETBSD, Distributions.OPENBSD), 39 | }, 40 | {"cmd": ("pkgin", "list")}, 41 | {"cmd": ("port", "installed"), "skew": 1}, 42 | {"cmd": ("rpm", "-qa")}, 43 | {"cmd": ("ls", "-1", "/var/log/packages/"), "name": "slackware"}, 44 | {"cmd": ("snap", "list", "--all"), "skew": 1}, 45 | {"cmd": ("yum", "list", "installed"), "skew": 2}, 46 | {"cmd": ("zypper", "search", "-i"), "skew": 5}, 47 | ) 48 | 49 | 50 | class Packages(Entry): 51 | """Relies on the first found packages manager to list the installed packages""" 52 | 53 | _ICON = "\ueb29" # cod_package 54 | 55 | def __init__(self, *args, **kwargs): 56 | super().__init__(*args, **kwargs) 57 | 58 | self.value = {} 59 | 60 | for packages_tool in PACKAGES_TOOLS: 61 | packages_tool = typing.cast(dict, packages_tool) 62 | if ( 63 | "only_on" in packages_tool 64 | and Distributions.get_local() not in packages_tool["only_on"] 65 | ): 66 | continue 67 | 68 | try: 69 | results = check_output( 70 | packages_tool["cmd"], 71 | stderr=DEVNULL, 72 | env={ 73 | # Honor current process environment variables as some package managers 74 | # require an extended `PATH`. 75 | **os.environ, 76 | "LANG": "C", 77 | }, 78 | universal_newlines=True, 79 | ) 80 | except (OSError, CalledProcessError): 81 | continue 82 | 83 | # Here we *may* use `\n` as `universal_newlines` has been set. 84 | count = results.count("\n") 85 | 86 | # If any, deduct output skew present due to the packages tool itself. 87 | if "skew" in packages_tool: 88 | count -= packages_tool["skew"] 89 | 90 | pkg_tool_name = packages_tool.get("name", packages_tool["cmd"][0]) 91 | 92 | # For DPKG only, remove any not purged package. 93 | if pkg_tool_name == "dpkg": 94 | count -= results.count("deinstall") 95 | 96 | self.value[pkg_tool_name] = count 97 | 98 | def output(self, output) -> None: 99 | """Adds the entry to `output` after pretty-formatting packages tool counts""" 100 | if not self.value: 101 | # Fall back on the default behavior if no temperatures were detected. 102 | super().output(output) 103 | return 104 | 105 | if self.options.get("combine_total"): 106 | output.append(self.name, str(sum(self.value.values()))) 107 | return 108 | 109 | entries = [] 110 | for pkg_tool_name, count in self.value.items(): 111 | if count > 0 or self.options.get("show_zeros"): 112 | entries.append(f"({pkg_tool_name}) {count}") 113 | 114 | if self.options.get("one_line", True): 115 | # One-line output is enabled : Join the results ! 116 | output.append(self.name, ", ".join(entries)) 117 | else: 118 | # One-line output has been disabled, add one entry per item. 119 | for entry in entries: 120 | output.append(self.name, entry) 121 | -------------------------------------------------------------------------------- /archey/entries/processes.py: -------------------------------------------------------------------------------- 1 | """Processes entry class""" 2 | 3 | from archey.entry import Entry 4 | from archey.processes import Processes as ProcessesUtil 5 | 6 | 7 | class Processes(Entry): 8 | """ 9 | Simple wrapper to `archey.processes` to provide the number of running processes as an entry. 10 | """ 11 | 12 | _ICON = "\ueba2" # cod_server_process 13 | 14 | def __init__(self, *args, **kwargs): 15 | super().__init__(*args, **kwargs) 16 | 17 | self.value = ProcessesUtil().number 18 | -------------------------------------------------------------------------------- /archey/entries/shell.py: -------------------------------------------------------------------------------- 1 | """Shell detection class""" 2 | 3 | import os 4 | from subprocess import CalledProcessError, check_output 5 | from typing import Optional 6 | 7 | from archey.entry import Entry 8 | 9 | 10 | class Shell(Entry): 11 | """ 12 | Simple shell path detection based either on the `SHELL` environment variable or 13 | the local administrative database. 14 | """ 15 | 16 | _ICON = "\U000f018d" # md_console 17 | 18 | def __init__(self, *args, **kwargs): 19 | super().__init__(*args, **kwargs) 20 | 21 | self.value = os.getenv("SHELL") or self._query_name_service_switch() 22 | 23 | @staticmethod 24 | def _query_name_service_switch() -> Optional[str]: 25 | try: 26 | user_id = os.getuid() 27 | except AttributeError: 28 | # Not UNIX... 29 | return None 30 | 31 | try: 32 | shell = ( 33 | check_output(["getent", "passwd", str(user_id)], universal_newlines=True) 34 | .rstrip() 35 | .rsplit(":", maxsplit=1)[-1] 36 | ) 37 | except CalledProcessError: 38 | # Ghost user... 39 | return None 40 | 41 | return shell 42 | -------------------------------------------------------------------------------- /archey/entries/terminal.py: -------------------------------------------------------------------------------- 1 | """Terminal detection class""" 2 | 3 | import os 4 | import re 5 | from typing import Optional 6 | 7 | from archey.colors import Colors, Style 8 | from archey.entry import Entry 9 | 10 | # We detect a terminal by using the following three constants in the order below: 11 | # First, we try using the value in the `TERM_PROGRAM` environment variable. 12 | # Then, we use `COLORTERM_DICT` to try matching a value with the `COLORTERM` one. 13 | # Third, we use `TERM_DICT` to try matching a value with the `TERM` one. 14 | # Finally, we fall back to custom environment variables defined in `ENV_DICT`. 15 | # If none of the above tests find a value, we use whichever value was defined in `$TERM`. 16 | 17 | # All of the keys in `COLORTERM_DICT` and `TERM_DICT` are matched as regular expressions... 18 | # (using `re.match`, i.e. attempting to match once at the beginning of the string), so be careful! 19 | 20 | 21 | # This dictionary contains values for the `COLORTERM` environment variable for terminal emulators 22 | # which do not propagate any other usable environment variable. 23 | # If `COLORTERM` matches one of these values, a normalization is performed with its corresponding 24 | # value (i.e. the respective terminal emulator). 25 | # If the variable does not match any keys in this dictionary, it is ignored. 26 | COLORTERM_DICT = { 27 | r"kmscon": "KMSCON", 28 | r"rxvt": "rxvt", 29 | } 30 | 31 | # This dictionary contains values for the `TERM` environment variable for terminal emulators 32 | # which do not propagate any other usable environment variable. 33 | # If `TERM` matches one of these values, a normalization is performed with its corresponding 34 | # value (i.e. the respective terminal emulator). 35 | # If the variable does not match any keys in this dictionary, it is ignored, 36 | # UNLESS it does not begin with `xterm`, at which point its value is taken as the terminal in use. 37 | # This behavior can be overridden by specifying its exact match here. 38 | TERM_DICT = { 39 | r"xterm-termite": "Termite", 40 | } 41 | 42 | # This dictionary contains environment variables used to detect terminal emulators... 43 | # which do not propagate any usable `COLORTERM`, `TERM`, or `TERM_PROGRAM`. 44 | # When a key is found in environment, a normalization is performed with its corresponding value. 45 | ENV_DICT = { 46 | "ALACRITTY_LOG": "Alacritty", 47 | "GNOME_TERMINAL_SCREEN": "GNOME Terminal", 48 | "GUAKE_TAB_UUID": "Guake", 49 | "KITTY_WINDOW_ID": "Kitty", 50 | "KONSOLE_VERSION": "Konsole", 51 | "MLTERM": "MLTERM", 52 | "TERMINATOR_UUID": "Terminator", 53 | "WT_SESSION": "Windows Terminal", 54 | } 55 | 56 | 57 | class Terminal(Entry): 58 | """ 59 | Simple terminal detection based on the `TERM` environment variable. 60 | It also displays the colors palette afterwards. 61 | """ 62 | 63 | _ICON = "\uf120" # fa_terminal 64 | 65 | def __init__(self, *args, **kwargs): 66 | super().__init__(*args, **kwargs) 67 | 68 | self.value = self._detect_terminal_emulator() 69 | 70 | def _get_colors_palette(self) -> str: 71 | """Build and return a 8-color palette, with Unicode characters if allowed""" 72 | # On systems with non-Unicode locales, we imitate '\u2588' character 73 | # ... with '#' to display the terminal colors palette. 74 | # Archey >= v4.8.0, Unicode is enabled by default. 75 | character = "\u2588" if self.options.get("use_unicode", True) else "#" 76 | 77 | return " ".join( 78 | [ 79 | f"{Colors((0, i))}{character}{Colors((1, i))}{character}{Colors.CLEAR}" 80 | for i in range(37, 30, -1) 81 | ] 82 | ) 83 | 84 | @staticmethod 85 | def _detect_terminal_emulator() -> Optional[str]: 86 | """Try to detect current terminal emulator based on various environment variables""" 87 | # At first, try to honor `TERM_PROGRAM*` environment variables. 88 | # See . 89 | env_term_program = os.getenv("TERM_PROGRAM") 90 | if env_term_program: 91 | env_term_program_version = os.getenv("TERM_PROGRAM_VERSION") 92 | if env_term_program_version: 93 | env_term_program += f" {env_term_program_version}" 94 | 95 | return env_term_program 96 | 97 | # Second, check if we have any matches as defined in our `COLORTERM` constant dict. 98 | env_colorterm = os.getenv("COLORTERM") 99 | if env_colorterm: 100 | for env_value_re, normalized_name in COLORTERM_DICT.items(): 101 | if re.match(env_value_re, env_colorterm): 102 | return normalized_name 103 | 104 | # Third, check if we have any matches defined in our `TERM` constant dict. 105 | env_term = os.getenv("TERM") 106 | if env_term: 107 | for env_value_re, normalized_name in TERM_DICT.items(): 108 | if re.match(env_value_re, env_term): 109 | return normalized_name 110 | 111 | # If we didn't find any match and `TERM` is set to "something special", honor it. 112 | if not env_term.startswith("xterm"): 113 | return env_term 114 | 115 | # If not, try to find a "known identifier" and perform name normalization... 116 | for env_var, normalized_name in ENV_DICT.items(): 117 | if env_var in os.environ: 118 | return normalized_name 119 | 120 | # When nothing of the above matched, falls-back on the regular `TERM` environment variable. 121 | # Note : It _might_ be `None` in very specific environments. 122 | return env_term 123 | 124 | def output(self, output) -> None: 125 | """Adds the entry to `output` after pretty-formatting with colors palette""" 126 | text_output = self.value or self._default_strings.get("not_detected") 127 | if Style.should_color_output(): 128 | text_output += " " + self._get_colors_palette() 129 | 130 | output.append(self.name, text_output) 131 | -------------------------------------------------------------------------------- /archey/entries/user.py: -------------------------------------------------------------------------------- 1 | """User session detection class""" 2 | 3 | import getpass 4 | 5 | from archey.entry import Entry 6 | 7 | 8 | class User(Entry): 9 | """Retrieves the session name of the current logged in user""" 10 | 11 | _ICON = "\uf007" # fa_user 12 | 13 | def __init__(self, *args, **kwargs): 14 | super().__init__(*args, **kwargs) 15 | 16 | try: 17 | self.value = getpass.getuser() 18 | except ImportError: 19 | # From , 20 | # `pwd` module import _might_ fail. 21 | pass 22 | -------------------------------------------------------------------------------- /archey/entries/wan_ip.py: -------------------------------------------------------------------------------- 1 | """Public IP address detection class""" 2 | 3 | from socket import timeout as SocketTimeoutError 4 | from subprocess import DEVNULL, CalledProcessError, TimeoutExpired, check_output 5 | from typing import Optional 6 | from urllib.error import URLError 7 | from urllib.request import urlopen 8 | 9 | from archey.entry import Entry 10 | from archey.environment import Environment 11 | 12 | 13 | class WanIP(Entry): 14 | """Uses different ways to retrieve the public IPv{4,6} addresses""" 15 | 16 | _ICON = "\U000f0a60" # md_ip_network 17 | _PRETTY_NAME = "WAN IP" 18 | 19 | def __init__(self, *args, **kwargs): 20 | super().__init__(*args, **kwargs) 21 | 22 | self.value = [] 23 | 24 | if Environment.DO_NOT_TRACK: 25 | return 26 | 27 | ipv4_addr = self._retrieve_ip_address(4) 28 | if ipv4_addr: 29 | self.value.append(ipv4_addr) 30 | 31 | ipv6_addr = self._retrieve_ip_address(6) 32 | if ipv6_addr: 33 | self.value.append(ipv6_addr) 34 | 35 | def _retrieve_ip_address(self, ip_version: int) -> Optional[str]: 36 | """ 37 | Best effort to retrieve public IP address based on corresponding options. 38 | We are trying special DNS resolutions first for performance and (system) caching purposes. 39 | """ 40 | options = self.options.get(f"ipv{ip_version}", {}) 41 | 42 | # Is retrieval enabled for this IP version ? 43 | if not options and not isinstance(options, dict): 44 | return None 45 | 46 | # Is retrieval via DNS query enabled ? 47 | dns_query = options.get("dns_query", "myip.opendns.com") 48 | if dns_query: 49 | # Run the DNS query. 50 | ip_address = self._run_dns_query( 51 | dns_query, 52 | options.get("dns_resolver", "resolver1.opendns.com"), 53 | ip_version, 54 | options.get("dns_timeout", 1), 55 | ) 56 | # Return IP only if the query was successful 57 | if ip_address is not None: 58 | return ip_address 59 | 60 | # Is retrieval via HTTP(S) request enabled ? 61 | http_url = options.get("http_url", f"https://{ip_version}.ident.me/") 62 | if not http_url: 63 | return None 64 | 65 | # Run the HTTP(S) request. 66 | return self._run_http_request(http_url, options.get("http_timeout", 1)) 67 | 68 | @staticmethod 69 | def _run_dns_query(query: str, resolver: str, ip_version: int, timeout: float) -> Optional[str]: 70 | """Simple wrapper to `dig` command to perform DNS queries""" 71 | try: 72 | ip_address = check_output( 73 | [ 74 | "dig", 75 | "+short", 76 | ("-" + str(ip_version)), 77 | ("AAAA" if ip_version == 6 else "A"), 78 | query, 79 | "@" + resolver, 80 | ], 81 | timeout=timeout, 82 | stderr=DEVNULL, 83 | universal_newlines=True, 84 | ).rstrip() 85 | except (OSError, TimeoutExpired, CalledProcessError): 86 | return None 87 | 88 | # `ip_address` might be empty here. 89 | return ip_address 90 | 91 | @staticmethod 92 | def _run_http_request(server_url: str, timeout: float) -> Optional[str]: 93 | """Simple wrapper to `urllib` module to perform HTTP requests""" 94 | try: 95 | with urlopen(server_url, timeout=timeout) as http_request: 96 | return http_request.read().decode().strip() 97 | except (URLError, SocketTimeoutError): 98 | return None 99 | 100 | def output(self, output) -> None: 101 | """Adds the entry to `output` after pretty-formatting our list of IP addresses.""" 102 | # If we found IP addresses, join them together nicely. 103 | # If not, fall-back on the "No address" string. 104 | if self.value: 105 | if not self.options.get("one_line", True): 106 | # One-line output has been disabled, add one IP address per item. 107 | for ip_address in self.value: 108 | output.append(self.name, ip_address) 109 | 110 | return 111 | 112 | text_output = ", ".join(self.value) 113 | 114 | elif not Environment.DO_NOT_TRACK: 115 | text_output = self._default_strings.get("no_address") 116 | else: 117 | text_output = self._default_strings.get("not_detected") 118 | 119 | output.append(self.name, text_output) 120 | -------------------------------------------------------------------------------- /archey/entries/window_manager.py: -------------------------------------------------------------------------------- 1 | """Windows manager detection class""" 2 | 3 | import os 4 | import platform 5 | import re 6 | from subprocess import DEVNULL, CalledProcessError, check_output 7 | 8 | from archey.entry import Entry 9 | from archey.processes import Processes 10 | 11 | WM_DICT = { 12 | "Amethyst": "Amethyst", 13 | "awesome": "Awesome", 14 | "beryl": "Beryl", 15 | "blackbox": "Blackbox", 16 | "bspwm": "bspwm", 17 | "cinnamon": "Cinnamon", 18 | "chunkwm": "ChunkWM", 19 | "compiz": "Compiz", 20 | "deepin-wm": "Deepin WM", 21 | "dwm": "dwm", 22 | "dwl": "dwl", 23 | "enlightenment": "Enlightenment", 24 | "herbstluftwm": "herbstluftwm", 25 | "fluxbox": "Fluxbox", 26 | "fvwm": "FVWM", 27 | "hyprland": "Hyprland", 28 | "i3": "i3", 29 | "icewm": "IceWM", 30 | "kwin_x11": "KWin", 31 | "kwin_wayland": "KWin", 32 | "metacity": "Metacity", 33 | "musca": "Musca", 34 | "openbox": "Openbox", 35 | "pekwm": "PekWM", 36 | "qtile": "QTile", 37 | "ratpoison": "RatPoison", 38 | "Rectangle": "Rectangle", 39 | "scrotwm": "ScrotWM", 40 | "Spectacle": "Spectacle", 41 | "stumpwm": "StumpWM", 42 | "subtle": "Subtle", 43 | "sway": "Sway", 44 | "monsterwm": "MonsterWM", 45 | "wayfire": "Wayfire", 46 | "wingo": "Wingo", 47 | "wmaker": "Window Maker", 48 | "wmfs": "Wmfs", 49 | "wmii": "wmii", 50 | "xfwm4": "Xfwm", 51 | "xmonad": "Xmonad", 52 | "yabai": "Yabai", 53 | } 54 | 55 | DSP_DICT = { 56 | "x11": "X11", 57 | "wayland": "Wayland", 58 | } 59 | 60 | 61 | class WindowManager(Entry): 62 | """ 63 | Uses `wmctrl` to retrieve some information about the window manager. 64 | If not available, fall back on a simple iteration over the processes. 65 | """ 66 | 67 | _ICON = "\ueae4" # cod_empty_window 68 | _PRETTY_NAME = "Window Manager" 69 | 70 | def __init__(self, *args, **kwargs): 71 | super().__init__(*args, **kwargs) 72 | 73 | name = None 74 | try: 75 | name = re.search( # type: ignore 76 | r"(?<=Name: ).*", 77 | check_output(["wmctrl", "-m"], stderr=DEVNULL, universal_newlines=True), 78 | ).group(0) 79 | except (OSError, CalledProcessError): 80 | processes = Processes().list 81 | for wm_id, wm_name in WM_DICT.items(): 82 | if wm_id in processes: 83 | name = wm_name 84 | break 85 | else: 86 | if platform.system() == "Darwin": 87 | name = "Quartz Compositor" 88 | elif platform.system() == "Windows": 89 | name = "Desktop Window Manager" 90 | 91 | display_server_protocol = DSP_DICT.get(os.getenv("XDG_SESSION_TYPE", "")) 92 | 93 | self.value = { 94 | "name": name, 95 | "display_server_protocol": display_server_protocol, 96 | } 97 | 98 | def output(self, output) -> None: 99 | # No WM could be detected. 100 | if self.value["name"] is None: 101 | output.append(self.name, self._default_strings.get("not_detected")) 102 | return 103 | 104 | text_output = self.value["name"] 105 | if self.value["display_server_protocol"] is not None: 106 | text_output += f" ({self.value['display_server_protocol']})" 107 | 108 | output.append(self.name, text_output) 109 | -------------------------------------------------------------------------------- /archey/entry.py: -------------------------------------------------------------------------------- 1 | """Entry base class""" 2 | 3 | import logging 4 | from abc import ABC as AbstractBaseClass 5 | from abc import abstractmethod 6 | from typing import Optional 7 | 8 | from archey.configuration import Configuration 9 | 10 | 11 | class Entry(AbstractBaseClass): 12 | """Module base class""" 13 | 14 | _ICON: Optional[str] = None 15 | _PRETTY_NAME: Optional[str] = None 16 | 17 | def __new__(cls, *_, **kwargs): 18 | """Hook object instantiation to handle our particular `disabled` config field""" 19 | if kwargs.get("options", {}).pop("disabled", False): 20 | return None 21 | 22 | return super().__new__(cls) 23 | 24 | @abstractmethod 25 | def __init__(self, name: Optional[str] = None, value=None, options: Optional[dict] = None): 26 | configuration = Configuration() 27 | 28 | # Each entry will have always have the following attributes... 29 | # `name`: key (defaults to the instantiated entry class name); 30 | # `value`: value of entry as an appropriate object; 31 | # `options`: configuration options *specific* to an entry instance; 32 | self.name = name or self._PRETTY_NAME or self.__class__.__name__ 33 | self.value = value 34 | self.options = options or {} 35 | 36 | # optionally prepend entry name with an icon 37 | icon = self.options.get("icon", self._ICON) 38 | if icon is not None and configuration.get("entries_icon"): 39 | self.name = f"{icon} {self.name}" 40 | 41 | # Propagates a reference to default strings specified in `Configuration`. 42 | self._default_strings = configuration.get("default_strings") 43 | 44 | # Provision a logger for each entry. 45 | self._logger = logging.getLogger(self.__module__) 46 | 47 | def __bool__(self) -> bool: 48 | return bool(self.value) 49 | 50 | def output(self, output) -> None: 51 | """Output the results to output. Can be overridden by subclasses.""" 52 | if self.value: 53 | # Let's assume we can just use `__str__` on the object in value, 54 | # and create a single-line output with it. 55 | output.append(self.name, str(self.value)) 56 | else: 57 | # If the value is "falsy" leave a generic "Not detected" message for this entry. 58 | output.append(self.name, self._default_strings.get("not_detected")) 59 | -------------------------------------------------------------------------------- /archey/environment.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple class (acting as a singleton) dealing with environment variables. 3 | Variables that should alter Archey _global_ behavior may be DRY-ed here. 4 | """ 5 | 6 | import os 7 | import platform 8 | 9 | from archey.singleton import Singleton 10 | 11 | 12 | class Environment(metaclass=Singleton): 13 | """ 14 | At startup, instantiate this class and set up some attributes 15 | according to their respective environment variable value. 16 | """ 17 | 18 | # See . 19 | NO_COLOR = "NO_COLOR" in os.environ 20 | 21 | # See . 22 | CLICOLOR = os.getenv("CLICOLOR") != "0" 23 | CLICOLOR_FORCE = os.getenv("CLICOLOR_FORCE", "0") != "0" 24 | 25 | # See . 26 | DO_NOT_TRACK = os.getenv("DO_NOT_TRACK") == "1" 27 | 28 | def __init__(self): 29 | if platform.system() == "Darwin": 30 | # Makes future `platform.mac_ver` calls not being _trolled_ by Darwin's kernel 31 | # when opening `/System/Library/CoreServices/SystemVersion.plist` file. 32 | # "Fortunately" for us, `platform` module does not cache these very results. 33 | # See `platform._mac_ver_xml` function and 34 | # . 35 | os.environ["SYSTEM_VERSION_COMPAT"] = "0" 36 | -------------------------------------------------------------------------------- /archey/exceptions.py: -------------------------------------------------------------------------------- 1 | """A very simple module defining our own exceptions""" 2 | 3 | 4 | class ArcheyException(Exception): 5 | """Archey own exception class""" 6 | -------------------------------------------------------------------------------- /archey/logos/__init__.py: -------------------------------------------------------------------------------- 1 | """`__init__` file for the `logos` submodule, containing dedicated utility methods""" 2 | 3 | from importlib import import_module 4 | from types import ModuleType 5 | from typing import List 6 | 7 | 8 | def lazy_load_logo_module(logo_name: str) -> ModuleType: 9 | """ 10 | Utility function returning a logo (as a Python module) lazily-loaded. 11 | It allows us to only load to RAM the distribution logo object that will actually be used. 12 | """ 13 | return import_module(f"{__name__}.{logo_name}") 14 | 15 | 16 | def get_logo_width(logo: List[str], nb_colors: int = 8) -> int: 17 | """ 18 | Utility function computing the real width of a distribution logo. 19 | Rationale : We use placeholders to dynamically set ANSI colors. 20 | Although, they **DO NOT** take width space once the text as been printed. 21 | 22 | For performance purposes we compute the logo length based on its first line. 23 | See `archey.test.test_archey_logos` unit tests for further logos consistency verifications. 24 | 25 | `logo` is supposed to be one of the constants declared above. 26 | `nb_colors` must be greater than or equal to the number of colors used by the logo. 27 | """ 28 | if not logo: 29 | return 0 30 | 31 | # We replace each placeholder by a 0-character string. 32 | return len(logo[0].format(c=[""] * nb_colors)) 33 | -------------------------------------------------------------------------------- /archey/logos/alpine.py: -------------------------------------------------------------------------------- 1 | """Alpine Linux logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.BLUE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} .hddddddddddddddddddddddh. """, 9 | """{c[0]} :dddddddddddddddddddddddddd: """, 10 | """{c[0]} /dddddddddddddddddddddddddddd/ """, 11 | """{c[0]} +dddddddddddddddddddddddddddddd+ """, 12 | """{c[0]} `sdddddddddddddddddddddddddddddddds` """, 13 | """{c[0]} `ydddddddddddd++hdddddddddddddddddddy` """, 14 | """{c[0]} .hddddddddddd+` `+ddddh:-sdddddddddddh.""", 15 | """{c[0]} hdddddddddd+` `+y: .sddddddddddh""", 16 | """{c[0]} ddddddddh+` `//` `.` -sddddddddd""", 17 | """{c[0]} ddddddh+` `/hddh/` `:s- -sddddddd""", 18 | """{c[0]} ddddh+` `/+/dddddh/` `+s- -sddddd""", 19 | """{c[0]} ddd+` `/o` :dddddddh/` `oy- .yddd""", 20 | """{c[0]} hdddyo+ohddyosdddddddddho+oydddy++ohdddh""", 21 | """{c[0]} .hddddddddddddddddddddddddddddddddddddh.""", 22 | """{c[0]} `yddddddddddddddddddddddddddddddddddy` """, 23 | """{c[0]} `sdddddddddddddddddddddddddddddddds` """, 24 | """{c[0]} +dddddddddddddddddddddddddddddd+ """, 25 | """{c[0]} /dddddddddddddddddddddddddddd/ """, 26 | """{c[0]} :dddddddddddddddddddddddddd: """, 27 | """{c[0]} .hddddddddddddddddddddddh. """, 28 | ] 29 | -------------------------------------------------------------------------------- /archey/logos/android.py: -------------------------------------------------------------------------------- 1 | """Android logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.GREEN_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} -o o- """, 9 | """{c[0]} +hydNNNNdyh+ """, 10 | """{c[0]} +mMMMMMMMMMMMMm+ """, 11 | """{c[0]} `dMM{c[1]}m:{c[0]}NMMMMMMN{c[1]}:m{c[0]}MMd` """, 12 | """{c[0]} hMMMMMMMMMMMMMMMMMMh """, 13 | """{c[0]} .. yyyyyyyyyyyyyyyyyyyy .. """, 14 | """{c[0]} .mMMm`MMMMMMMMMMMMMMMMMMMM`mMMm.""", 15 | """{c[0]} :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:""", 16 | """{c[0]} :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:""", 17 | """{c[0]} :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:""", 18 | """{c[0]} :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM:""", 19 | """{c[0]} -MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM-""", 20 | """{c[0]} +yy+ MMMMMMMMMMMMMMMMMMMM +yy+ """, 21 | """{c[0]} mMMMMMMMMMMMMMMMMMMm """, 22 | """{c[0]} `/++MMMMh++hMMMM++/` """, 23 | """{c[0]} MMMMo oMMMM """, 24 | """{c[0]} MMMMo oMMMM """, 25 | """{c[0]} oNMm- -mMNs """, 26 | ] 27 | -------------------------------------------------------------------------------- /archey/logos/arch.py: -------------------------------------------------------------------------------- 1 | """Arch Linux logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.CYAN_BRIGHT, Colors.CYAN_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} + """, 9 | """{c[0]} # """, 10 | """{c[0]} ### """, 11 | """{c[0]} ##### """, 12 | """{c[0]} ###### """, 13 | """{c[0]} ; #####; """, 14 | """{c[0]} +##.##### """, 15 | """{c[0]} +########## """, 16 | """{c[0]} ######{c[1]}#####{c[0]}##; """, 17 | """{c[0]} ###{c[1]}############{c[0]}+ """, 18 | """{c[0]} #{c[1]}###### #######{c[0]} """, 19 | """{c[0]} {c[1]}.######; ;###;`".{c[0]} """, 20 | """{c[0]} {c[1]}.#######; ;#####.{c[0]} """, 21 | """{c[0]} {c[1]}#########. .########`{c[0]} """, 22 | """{c[0]} {c[1]}######' '######{c[0]} """, 23 | """{c[0]} {c[1]};#### ####;{c[0]} """, 24 | """{c[0]} {c[1]}##' '##{c[0]} """, 25 | """{c[0]} {c[1]}#' `#{c[0]}""", 26 | ] 27 | -------------------------------------------------------------------------------- /archey/logos/armbian.py: -------------------------------------------------------------------------------- 1 | """Armbian logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.WHITE_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[1]} .. """, 9 | """{c[1]} `:]x**j-,' """, 10 | """{c[1]} .,+t***********z\\<" """, 11 | """{c[1]} ?******************; """, 12 | """{c[1]} '*n` .'`^,;;,^`'. ,cc. """, 13 | """{c[1]} -<. .[l """, 14 | """{c[1]} // ^^ ^^ \\\\ """, 15 | """{c[1]} !^ {c[0]}^^{c[1]} ": """, 16 | """{c[1]} 'tt}}` {c[0]}!~]rj_{c[1]} ")t/. """, 17 | """{c[1]} Itttt?' {c[0]}~~]rr]{c[1]} `{{tttt, """, 18 | """{c[1]} \\tttttt!""I{c[0]}_]r({c[1]}\"\"\"~tttttt1 """, 19 | """{c[1]} '_tttttttttttt{c[0]})f{c[1]}tttttttttttti. """, 20 | """{c[1]} \\*ztttttttttttttttttttttttttf**[ """, 21 | """{c[1]} l**c)tttttttttttttttttttttttt(z**, """, 22 | """{c[1]} .z*x.`tttttttttttttttttttttttt.`u*n""", 23 | """{c[1]} >` (tttttttttttttttttttttt] "I """, 24 | """{c[1]} ,tttttttttttttttttttttt` """, 25 | """{c[1]} ./ttttt{c[0]}f{c[1]}tttttttt{c[0]}f{c[1]}ttttt( """, 26 | """{c[1]} 'I){c[0]}))(\\()({c[1]}tt{c[0]}))|\\()({c[1]}{{;' """, 27 | """{c[1]} {c[0]}.~~~~~~~|)~~~~~~~<{c[1]} """, 28 | """{c[1]} '{c[0]}[)))))1{c[1]}|({c[0]}))))))){c[1]}? """, 29 | """{c[1]} {c[0]}",,,"{c[1]} {c[0]}",,,^{c[1]} """, 30 | ] 31 | 32 | # Taken from official patch for Neofetch (see ). 33 | COLORS_CHIPSET = COLORS 34 | 35 | LOGO_CHIPSET = [ 36 | """{c[0]} █ █ █ █ █ █ █ █ █ █ █ """, 37 | """{c[0]} ███████████████████████ """, 38 | """{c[0]} ▄▄██ ██▄▄""", 39 | """{c[0]} ▄▄██ ███████████ ██▄▄""", 40 | """{c[0]} ▄▄██ ██ ██ ██▄▄""", 41 | """{c[0]} ▄▄██ ██ ██ ██▄▄""", 42 | """{c[0]} ▄▄██ ██ ██ ██▄▄""", 43 | """{c[0]} ▄▄██ █████████████ ██▄▄""", 44 | """{c[0]} ▄▄██ ██ ██ ██▄▄""", 45 | """{c[0]} ▄▄██ ██ ██ ██▄▄""", 46 | """{c[0]} ▄▄██ ██ ██ ██▄▄""", 47 | """{c[0]} ▄▄██ ██▄▄""", 48 | """{c[0]} ███████████████████████ """, 49 | """{c[0]} █ █ █ █ █ █ █ █ █ █ █ """, 50 | ] 51 | -------------------------------------------------------------------------------- /archey/logos/buildroot.py: -------------------------------------------------------------------------------- 1 | """Buildroot logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.YELLOW_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} .:::::::::::.. """, 9 | """{c[0]} .::-----::::::::::.. """, 10 | """{c[0]} .:----:::::::::::::::::. """, 11 | """{c[0]} .-------::::::::....:::::::. """, 12 | """{c[0]} :=-------::::::::.......::::::. """, 13 | """{c[0]} :==---------:::::::.......:::::: """, 14 | """{c[0]} .====--------::::::::.....:::::::. """, 15 | """{c[0]} .=====--------::::::::::::::::::-: """, 16 | """{c[0]} :=======-------------:::::::::---:. """, 17 | """{c[0]} .-========-----------------:::----:. """, 18 | """{c[0]} .-==========-----------------------::. """, 19 | """{c[0]} -=====-===---------------------------:. """, 20 | """{c[0]} .:-===--====-------------------------:::. """, 21 | """{c[0]} ..::----------:::::-::::::::::::::::::. """, 22 | """{c[0]} ..:------:::::::::::::::::::::----:""", 23 | """{c[0]} ..:------:::::::::::::::---:::..""", 24 | """{c[0]} ..:-----::::::::::...... """, 25 | ] 26 | -------------------------------------------------------------------------------- /archey/logos/bunsenlabs.py: -------------------------------------------------------------------------------- 1 | """Bunsenlabs logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.WHITE_BRIGHT, Colors.YELLOW_BRIGHT, Colors.YELLOW_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} .{c[1]}..{c[0]}+hhy+-` """, 9 | """{c[0]} `+hd{c[1]}-{c[0]}+dddd{c[2]}hyso{c[0]}+//: """, 10 | """{c[0]} `+dddd{c[1]}:-{c[0]}sdh/. """, 11 | """{c[0]} -hdddddh{c[1]}-.{c[2]}/:{c[0]} """, 12 | """{c[0]} /ddddddddd{c[1]}:```{c[0]} """, 13 | """{c[0]} :ddddddddddd/ """, 14 | """{c[0]} `hdddddddddddd+ """, 15 | """{c[0]} /dddddddddddddd: """, 16 | """{c[0]} odddds..sddddddh """, 17 | """{c[0]} oddd/ /dddddd: """, 18 | """{c[0]} +dd+ +ddddd+ """, 19 | """{c[0]} .dd` `ddddd+ """, 20 | """{c[0]} oh ydddd: """, 21 | """{c[0]} `o sdddh` """, 22 | """{c[0]} yddd: """, 23 | """{c[0]} `dddo """, 24 | """{c[0]} :s :dds """, 25 | """{c[0]} yd/ yd+ """, 26 | """{c[0]} `sddy :h- """, 27 | """{c[0]} `sddys` :` """, 28 | """{c[0]} -hdy+`y+yo./+/:- """, 29 | """{c[0]} ... .o++oso+/ """, 30 | ] 31 | -------------------------------------------------------------------------------- /archey/logos/centos.py: -------------------------------------------------------------------------------- 1 | """CentOS logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [ 6 | Colors.WHITE_BRIGHT, 7 | Colors.YELLOW_NORMAL, 8 | Colors.GREEN_BRIGHT, 9 | Colors.BLUE_NORMAL, 10 | Colors.MAGENTA_BRIGHT, 11 | ] 12 | 13 | # pylint: disable=line-too-long 14 | LOGO = [ 15 | """{c[0]} {c[1]}..{c[0]} """, 16 | """{c[0]} {c[1]}.PLTJ.{c[0]} """, 17 | """{c[0]} {c[1]}<><><><>{c[0]} """, 18 | """{c[0]} {c[2]}KKSSV' 4KKK{c[0]} {c[1]}LJ{c[0]} {c[4]}KKKL.'VSSKK{c[0]} """, 19 | """{c[0]} {c[2]}KKV' 4KKKKK{c[0]} {c[1]}LJ{c[0]} {c[4]}KKKKAL 'VKK{c[0]} """, 20 | """{c[0]} {c[2]}V' ' 'VKKKK{c[0]} {c[1]}LJ{c[0]} {c[4]}KKKKV' ' 'V{c[0]} """, 21 | """{c[0]} {c[2]}.4MA.' 'VKK{c[0]} {c[1]}LJ{c[0]} {c[4]}KKV' '.4Mb.{c[0]} """, 22 | """{c[0]} {c[4]}.{c[0]} {c[2]}KKKKKA.' 'V{c[0]} {c[1]}LJ{c[0]} {c[4]}V' '.4KKKKK{c[0]} {c[3]}.{c[0]} """, 23 | """{c[0]} {c[4]}.4D{c[0]} {c[2]}KKKKKKKA.''{c[0]} {c[1]}LJ{c[0]} {c[4]}''.4KKKKKKK{c[0]} {c[3]}FA.{c[0]} """, 24 | """{c[0]} {c[4]}{c[0]}""", 25 | """{c[0]} {c[4]}'VD{c[0]} {c[3]}KKKKKKKK'..{c[0]} {c[2]}LJ{c[0]} {c[1]}..'KKKKKKKK{c[0]} {c[3]}FV{c[0]} """, 26 | """{c[0]} {c[4]}'{c[0]} {c[3]}VKKKKK'. .4{c[0]} {c[2]}LJ{c[0]} {c[1]}K. .'KKKKKV{c[0]} {c[3]}'{c[0]} """, 27 | """{c[0]} {c[3]} 'VK'. .4KK{c[0]} {c[2]}LJ{c[0]} {c[1]}KKA. .'KV' {c[0]} """, 28 | """{c[0]} {c[3]}A. . .4KKKK{c[0]} {c[2]}LJ{c[0]} {c[1]}KKKKA. . .4{c[0]} """, 29 | """{c[0]} {c[3]}KKA. 'KKKKK{c[0]} {c[2]}LJ{c[0]} {c[1]}KKKKK' .4KK{c[0]} """, 30 | """{c[0]} {c[3]}KKSSA. VKKK{c[0]} {c[2]}LJ{c[0]} {c[1]}KKKV .4SSKK{c[0]} """, 31 | """{c[0]} {c[2]}<><><><>{c[0]} """, 32 | """{c[0]} {c[2]}'MKKM'{c[0]} """, 33 | """{c[0]} {c[2]}''{c[0]} """, 34 | ] 35 | -------------------------------------------------------------------------------- /archey/logos/crunchbang.py: -------------------------------------------------------------------------------- 1 | """Crunchbang logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} ___ ___ _ """, 9 | """{c[0]} / / / / | |""", 10 | """{c[0]} / / / / | |""", 11 | """{c[0]} / / / / | |""", 12 | """{c[0]} _______/ /______/ /______ | |""", 13 | """{c[0]} /______ _______ _______/ | |""", 14 | """{c[0]} / / / / | |""", 15 | """{c[0]} / / / / | |""", 16 | """{c[0]} / / / / | |""", 17 | """{c[0]} ______/ /______/ /______ | |""", 18 | """{c[0]} /_____ _______ _______/ | |""", 19 | """{c[0]} / / / / | |""", 20 | """{c[0]} / / / / |_|""", 21 | """{c[0]} / / / / _ """, 22 | """{c[0]} / / / / | |""", 23 | """{c[0]} /__/ /__/ |_|""", 24 | ] 25 | -------------------------------------------------------------------------------- /archey/logos/debian.py: -------------------------------------------------------------------------------- 1 | """Debian logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.RED_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} _sudZUZ#Z#XZo=_ """, 9 | """{c[0]} _jmZZ2!!~---~!!X##wx """, 10 | """{c[0]} .{c[0]} {c[1]}-]Xb/{c[0]} {c[1]}~{c[0]} {c[1]}__#2({c[0]} """, 17 | """{c[0]} {c[1]}-Zo;{c[0]} {c[1]}+!4ZwerfgnZZXY'{c[0]} """, 18 | """{c[0]} {c[1]}*#[,{c[0]} {c[1]}~-?!!!!!!-~{c[0]} """, 19 | """{c[0]} {c[1]}XUb;.{c[0]} """, 20 | """{c[0]} {c[1]})YXL,,{c[0]} """, 21 | """{c[0]} {c[1]}+3#bc,{c[0]} """, 22 | """{c[0]} {c[1]}-)SSL,,{c[0]} """, 23 | """{c[0]} {c[1]}~~~~~{c[0]} """, 24 | ] 25 | -------------------------------------------------------------------------------- /archey/logos/devuan.py: -------------------------------------------------------------------------------- 1 | """Devuan logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.MAGENTA_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} ..,,. """, 9 | """{c[0]} .',;:cc;. """, 10 | """{c[0]} .,lxkkl;. """, 11 | """{c[0]} 'ckNKd:. """, 12 | """{c[0]} .lKMNx, """, 13 | """{c[0]} .OMMWx. """, 14 | """{c[0]} xMMMMk """, 15 | """{c[0]} lMMMMMK""", 16 | """{c[0]} oMMMMMMK""", 17 | """{c[0]} ,xWMMMMMMk """, 18 | """{c[0]} 'lOWMMMMMMWk. """, 19 | """{c[0]} .,lkXMMMMMMMMWO: """, 20 | """{c[0]} .,:okKWMMMMMMMMMMKd, """, 21 | """{c[0]} ckKNMMMMMMMMMMMMMW0o, """, 22 | """{c[0]} kMMMMMMMMMMMMW0d:. """, 23 | """{c[0]} cMMMMMMWKkl,. """, 24 | """{c[0]} '0MNx, """, 25 | ] 26 | -------------------------------------------------------------------------------- /archey/logos/elementary.py: -------------------------------------------------------------------------------- 1 | """Elementary OS logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} eeeeeeeeeeeeeeeee """, 9 | """{c[0]} eeeeeeeeeeeeeeeeeeeeeee """, 10 | """{c[0]} eeeee eeeeeeeeeeee eeeee """, 11 | """{c[0]} eeee eeeee eee eeee """, 12 | """{c[0]} eeee eeee eee eeee """, 13 | """{c[0]} eee eee eee eee""", 14 | """{c[0]} eee eee eee eee""", 15 | """{c[0]} ee eee eeee eeee""", 16 | """{c[0]} ee eee eeeee eeeeee""", 17 | """{c[0]} ee eee eeeee eeeee ee""", 18 | """{c[0]} eee eeee eeeeee eeeee eee""", 19 | """{c[0]} eee eeeeeeeeee eeeeee eee""", 20 | """{c[0]} eeeeeeeeeeeeeeeeeeeeeeee eeeee """, 21 | """{c[0]} eeeeeeee eeeeeeeeeeee eeee """, 22 | """{c[0]} eeeee eeeee """, 23 | """{c[0]} eeeeeee eeeeeee """, 24 | """{c[0]} eeeeeeeeeeeeeeeee """, 25 | ] 26 | -------------------------------------------------------------------------------- /archey/logos/endeavouros.py: -------------------------------------------------------------------------------- 1 | """Endeavour OS Logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.MAGENTA_NORMAL, Colors.BLUE_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} ./{c[1]}o{c[2]}.{c[0]} """, 9 | """{c[0]} ./{c[1]}sssso{c[2]}-{c[0]} """, 10 | """{c[0]} `:{c[1]}osssssss+{c[2]}-{c[0]} """, 11 | """{c[0]} `:+{c[1]}sssssssssso{c[2]}/.{c[0]} """, 12 | """{c[0]} `-/{c[1]}ssssssssssssso{c[2]}/.{c[0]} """, 13 | """{c[0]} `-/+{c[1]}sssssssssssssssso{c[2]}+:`{c[0]} """, 14 | """{c[0]} `-:/+{c[1]}sssssssssssssssssso{c[2]}/.{c[0]} """, 15 | """{c[0]} `.://o{c[1]}sssssssssssssssssssso{c[2]}++-{c[0]} """, 16 | """{c[0]} .://+{c[1]}ssssssssssssssssssssssso{c[2]}++:{c[0]} """, 17 | """{c[0]} .:///o{c[1]}ssssssssssssssssssssssssso{c[2]}++:{c[0]} """, 18 | """{c[0]} `:////{c[1]}ssssssssssssssssssssssssssso{c[2]}+++.{c[0]}""", 19 | """{c[0]}`-////+{c[1]}ssssssssssssssssssssssssssso{c[2]}++++-{c[0]}""", 20 | """{c[0]} `..-+{c[1]}oosssssssssssssssssssssssso{c[2]}+++++/`{c[0]}""", 21 | """{c[0]} {c[2]}./++++++++++++++++++++++++++++++/:.{c[0]} """, 22 | """{c[0]} {c[2]}`:::::::::::::::::::::::::------``{c[0]} """, 23 | ] 24 | -------------------------------------------------------------------------------- /archey/logos/enso.py: -------------------------------------------------------------------------------- 1 | """Enso logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.WHITE_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} .:--==--:. """, 9 | """{c[0]} :=*#############*+-. """, 10 | """{c[0]} .+##################*##*: """, 11 | """{c[0]} .*##########+==-==++*####*##- """, 12 | """{c[0]} =########=: .-+**#***. """, 13 | """{c[0]} *#######- ++*#**. """, 14 | """{c[0]} +######+ -*+#** """, 15 | """{c[0]} :######* .*+**= """, 16 | """{c[0]} :######* .*+**= """, 17 | """{c[0]} ####### +++#. """, 18 | """{c[0]} #######. ++=*. """, 19 | """{c[0]} *######+ .-+*+ """, 20 | """{c[0]} :#######- -:*+: """, 21 | """{c[0]} =#######*. :.*+- """, 22 | """{c[0]} +########*- :*=- """, 23 | """{c[0]} =###########+=: =+=: """, 24 | """{c[0]} .+#############. .-==: """, 25 | """{c[0]} .=###########= ..:--:. """, 26 | """{c[0]} .-+######+ """, 27 | ] 28 | -------------------------------------------------------------------------------- /archey/logos/fedora.py: -------------------------------------------------------------------------------- 1 | """Fedora logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.BLUE_BRIGHT, Colors.BLUE_NORMAL, Colors.WHITE_BRIGHT] 6 | 7 | # pylint: disable=line-too-long 8 | LOGO = [ 9 | """{c[0]} {c[1]}........{c[0]} """, 10 | """{c[0]} {c[1]}.::::::::::::::.{c[0]} """, 11 | """{c[0]} {c[1]}.::::::::::::::::::::.{c[0]} """, 12 | """{c[0]} {c[1]}.::::::::::::{c[2]}.shhdhyo.{c[1]}:::.{c[0]} """, 13 | """{c[0]} {c[1]}.::::::::::::{c[2]}omMMMNNNMMMP){c[1]}:::.{c[0]} """, 14 | """{c[0]} {c[1]}.::::::::::::{c[2]}sMMMdo:'{c[0]}+++++++{c[1]}:::.{c[0]} """, 15 | """{c[0]} {c[1]}:::::::::::::{c[2]}MMMd{c[1]}:::::::{c[0]}+++++{c[1]}:::{c[0]} """, 16 | """{c[0]} {c[1]}::::::::::::::{c[2]}MMMy{c[1]}:::::::{c[0]}+++++{c[1]}::::{c[0]}""", 17 | """{c[0]} {c[1]}:::::::{c[0]}++++++{c[1]}{c[2]}/+MMMh\\{c[1]}::{c[0]}+++++++{c[1]}:::::{c[0]}""", 18 | """{c[0]} {c[1]}::::{c[0]}+++++{c[2]}oNMMMMMMMMMNho{c[0]}+++++{c[1]}::::::{c[0]}""", 19 | """{c[0]} {c[1]}:::{c[0]}+++++{c[1]}::{c[2]}shhhMMMmhhy{c[0]}+++++{c[1]}::::::::{c[0]}""", 20 | """{c[0]} {c[1]}::{c[0]}++++++{c[1]}::::::{c[2]}MMMy{c[1]}:::::::::::::::{c[0]} """, 21 | """{c[0]} {c[1]}::{c[0]}+++++{c[1]}::::::{c[2]}.MMMy{c[1]}::::::::::::::'{c[0]} """, 22 | """{c[0]} {c[1]}::{c[0]}++++++{c[1]}::::{c[2]}.hMMM+{c[1]}:::::::::::::'{c[0]} """, 23 | """{c[0]} {c[1]}::::{c[2]}dMMNdyydNMMNo{c[1]}::::::::::::'{c[0]} """, 24 | """{c[0]} {c[1]}:::::{c[2]}sdNMMMMNds{c[1]}::::::::::::'{c[0]} """, 25 | """{c[0]} {c[1]}::::::::{c[2]}'YY'{c[1]}::::::::::::'{c[0]} """, 26 | """{c[0]} {c[1]}\\:::::::::::::::::'''{c[0]} """, 27 | ] 28 | -------------------------------------------------------------------------------- /archey/logos/freebsd.py: -------------------------------------------------------------------------------- 1 | """FreeBSD logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.RED_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} {c[1]}```{c[0]} {c[0]}` """, 9 | """{c[0]} {c[1]}s`{c[0]} {c[1]}`.....---...{c[0]}....--.``` -/""", 10 | """{c[0]} {c[1]}+o{c[0]} {c[1]}.--`{c[0]} /y:` +.""", 11 | """{c[0]} {c[1]}yo`:.{c[0]} :o `+- """, 12 | """{c[0]} {c[1]}y/{c[0]} -/` -o/ """, 13 | """{c[0]} {c[1]}.-{c[0]} ::/sy+:. """, 14 | """{c[0]} {c[1]}/{c[0]} `-- / """, 15 | """{c[0]} {c[1]}`:{c[0]} :`""", 16 | """{c[0]} {c[1]}`:{c[0]} :`""", 17 | """{c[0]} {c[1]}/{c[0]} / """, 18 | """{c[0]} {c[1]}.-{c[0]} -. """, 19 | """{c[0]} {c[1]}--{c[0]} -. """, 20 | """{c[0]} {c[1]}`:`{c[0]} `:` """, 21 | """{c[0]} .-- `--. """, 22 | """{c[0]} .---.....----. """, 23 | ] 24 | -------------------------------------------------------------------------------- /archey/logos/gentoo.py: -------------------------------------------------------------------------------- 1 | """Gentoo logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.MAGENTA_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} -/oyddmdhs+:. """, 9 | """{c[0]} -o{c[1]}dNMMMMMMMMNNmhy+{c[0]}-` """, 10 | """{c[0]} -y{c[1]}NMMMMMMMMMMMNNNmmdhy{c[0]}+- """, 11 | """{c[0]} `o{c[1]}mMMMMMMMMMMMMNmdmmmmddhhy{c[0]}/` """, 12 | """{c[0]} om{c[1]}MMMMMMMMMMMN{c[0]}hhyyyo{c[1]}hmdddhhhd{c[0]}o` """, 13 | """{c[0]} .y{c[1]}dMMMMMMMMMMd{c[0]}hs++so/s{c[1]}mdddhhhhdm{c[0]}+` """, 14 | """{c[0]} oy{c[1]}hdmNMMMMMMMN{c[0]}dyooy{c[1]}dmddddhhhhyhN{c[0]}d.""", 15 | """{c[0]} :o{c[1]}yhhdNNMMMMMMMNNNmmdddhhhhhyym{c[0]}Mh""", 16 | """{c[0]} .:{c[1]}+sydNMMMMMNNNmmmdddhhhhhhmM{c[0]}my""", 17 | """{c[0]} /m{c[1]}MMMMMMNNNmmmdddhhhhhmMNh{c[0]}s:""", 18 | """{c[0]} `o{c[1]}NMMMMMMMNNNmmmddddhhdmMNhs{c[0]}+` """, 19 | """{c[0]} `s{c[1]}NMMMMMMMMNNNmmmdddddmNMmhs{c[0]}/. """, 20 | """{c[0]} /N{c[1]}MMMMMMMMNNNNmmmdddmNMNdso{c[0]}:` """, 21 | """{c[0]} +M{c[1]}MMMMMMNNNNNmmmmdmNMNdso{c[0]}/- """, 22 | """{c[0]} yM{c[1]}MNNNNNNNmmmmmNNMmhs+/{c[0]}-` """, 23 | """{c[0]} /h{c[1]}MMNNNNNNNNMNdhs++/{c[0]}-` """, 24 | """{c[0]} `/{c[1]}ohdmmddhys+++/:{c[0]}.` """, 25 | """{c[0]} `-//////:--. """, 26 | ] 27 | -------------------------------------------------------------------------------- /archey/logos/guix.py: -------------------------------------------------------------------------------- 1 | """Guix System logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.YELLOW_BRIGHT, Colors.RED_NORMAL, Colors.YELLOW_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} + ? """, 9 | """{c[0]} ?? ?{c[2]}I{c[0]} """, 10 | """{c[0]} {c[2]}??{c[1]}I{c[0]}? I??N $??? $?{c[1]}?{c[2]}??{c[0]}""", 11 | """{c[0]} {c[2]}?{c[1]}III7{c[0]}$??????? ??????${c[1]}7III?Z{c[0]} """, 12 | """{c[0]} {c[1]}OI77{c[0]}$$????? ?????$${c[1]}77IIII{c[0]} """, 13 | """{c[0]} ????? $???? """, 14 | """{c[0]} ???{c[1]}ID{c[0]} $???? """, 15 | """{c[0]} {c[1]}IIII{c[0]} $+???? """, 16 | """{c[0]} {c[1]}IIIII{c[0]} $???? """, 17 | """{c[0]} {c[1]}IIII{c[0]} $????? """, 18 | """{c[0]} {c[1]}IIIII{c[0]} $???? """, 19 | """{c[0]} {c[1]}II77{c[0]} $????$ """, 20 | """{c[0]} {c[1]}7777{c[2]}+${c[0]}???? """, 21 | """{c[0]} {c[1]}77{c[2]}++?${c[0]}??$ """, 22 | """{c[0]} {c[1]}N{c[2]}?+???${c[0]}? """, 23 | ] 24 | -------------------------------------------------------------------------------- /archey/logos/kali.py: -------------------------------------------------------------------------------- 1 | """Kali Linux logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.BLUE_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} ,..... """, 9 | """{c[0]} ----` `..,;:ccc,. """, 10 | """{c[0]} ......''';lxO. """, 11 | """{c[0]} .....''''..........,:ld; """, 12 | """{c[0]} .';;;:::;,,.x, """, 13 | """{c[0]} ..'''. 0Xxoc:,. ... """, 14 | """{c[0]} .... ,ONkc;,;cokOdc',. """, 15 | """{c[0]} . OMo ':{c[1]}d{c[0]}o. """, 16 | """{c[0]} dMc :OO; """, 17 | """{c[0]} 0M. .:o. """, 18 | """{c[0]} ;Wd """, 19 | """{c[0]} ;XO, """, 20 | """{c[0]} ,d0Odlc;,.. """, 21 | """{c[0]} ..',;:cdOOd::,. """, 22 | """{c[0]} .:d;.':;. """, 23 | """{c[0]} 'd, .' """, 24 | """{c[0]} ;l ..""", 25 | """{c[0]} .o """, 26 | """{c[0]} c """, 27 | """{c[0]} .' """, 28 | """{c[0]} . """, 29 | ] 30 | -------------------------------------------------------------------------------- /archey/logos/linux.py: -------------------------------------------------------------------------------- 1 | """Linux logo (tux)""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.WHITE_BRIGHT, Colors.YELLOW_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} a8888b. """, 9 | """{c[0]} d888888b. """, 10 | """{c[0]} 8P"YP"Y88 """, 11 | """{c[0]} 8|o||o|88 """, 12 | """{c[0]} 8{c[1]}\\vvvv/{c[0]}88 """, 13 | """{c[0]} 8{c[1]} \\vv/ {c[0]}Y8. """, 14 | """{c[0]} d/ {c[1]}`'{c[0]} \\8b. """, 15 | """{c[0]} .dP . Y8b. """, 16 | """{c[0]} d8:' " `::88b. """, 17 | """{c[0]} d8" `Y88b """, 18 | """{c[0]} :8P ' :888 """, 19 | """{c[0]} 8a. : _a88P """, 20 | """{c[0]} {c[1]}._/"{c[0]}Yaa_ : .{c[1]}| {c[0]}88P{c[1]}|{c[0]} """, 21 | """{c[0]} {c[1]}\\++++{c[0]}YP" `{c[1]}| {c[0]}8P{c[1]}++\\.{c[0]}""", 22 | """{c[0]} {c[1]}/+++++\\.{c[0]}_____.d{c[1]}|+++++/{c[0]} """, 23 | """{c[0]} {c[1]}\\++++++){c[0]}888888P{c[1]}\\+++/{c[0]} """, 24 | ] 25 | -------------------------------------------------------------------------------- /archey/logos/linuxmint.py: -------------------------------------------------------------------------------- 1 | """Linux Mint logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.GREEN_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | # pylint: disable=line-too-long 8 | LOGO = [ 9 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMMMMMmds+. """, 10 | """{c[0]} MMm----::-://////////////oymNMd+` """, 11 | """{c[0]} MMd {c[1]}/++{c[0]} -sNMd: """, 12 | """{c[0]} MMNso/` {c[1]}dMM{c[0]} {c[1]}`.::-. .-::.`{c[0]} .hMN:""", 13 | """{c[0]} ddddMMh {c[1]}dMM{c[0]} {c[1]}:hNMNMNhNMNMNh:`{c[0]} NMm""", 14 | """{c[0]} NMm {c[1]}dMM{c[0]} {c[1]}.NMN/-+MMM+-/NMN`{c[0]} dMM""", 15 | """{c[0]} NMm {c[1]}dMM{c[0]} {c[1]}-MMm{c[0]} {c[1]}`MMM{c[0]} {c[1]}dMM.{c[0]} dMM""", 16 | """{c[0]} NMm {c[1]}dMM{c[0]} {c[1]}-MMm{c[0]} {c[1]}`MMM{c[0]} {c[1]}dMM.{c[0]} dMM""", 17 | """{c[0]} NMm {c[1]}dMM{c[0]} {c[1]}.mmd{c[0]} {c[1]}`mmm{c[0]} {c[1]}yMM.{c[0]} dMM""", 18 | """{c[0]} NMm {c[1]}dMM`{c[0]} {c[1]}..`{c[0]} {c[1]}`...{c[0]} {c[1]}ydm.{c[0]} dMM""", 19 | """{c[0]} hMM- {c[1]}+MMd/-------...-:sdds{c[0]} MMM""", 20 | """{c[0]} -NMm- {c[1]}:hNMNNNmdddddddddy/`{c[0]} dMM""", 21 | """{c[0]} -dMNs-``{c[1]}-::::-------.``{c[0]} dMM""", 22 | """{c[0]} `/dMNmy+/:-------------:/yMMM""", 23 | """{c[0]} ./ydNMMMMMMMMMMMMMMMMMMMMM""", 24 | ] 25 | -------------------------------------------------------------------------------- /archey/logos/manjaro.py: -------------------------------------------------------------------------------- 1 | """Manjaro logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.GREEN_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} $$$$$$$$$$$$$$$$ $$$$$$$""", 9 | """{c[0]} M77777777777777M M77777M""", 10 | """{c[0]} M77777777777777M M77777M""", 11 | """{c[0]} M77777MMMMMMMMMM M77777M""", 12 | """{c[0]} M77777M M77777M""", 13 | """{c[0]} M77777M $$$$$$$ M77777M""", 14 | """{c[0]} MMMMMMM M77777M M77777M""", 15 | """{c[0]} M77777M M77777M""", 16 | """{c[0]} $$$$$$$ M77777M M77777M""", 17 | """{c[0]} M77777M M77777M M77777M""", 18 | """{c[0]} M77777M M77777M M77777M""", 19 | """{c[0]} M77777M M77777M M77777M""", 20 | """{c[0]} M77777M M77777M M77777M""", 21 | """{c[0]} M77777M M77777M M77777M""", 22 | """{c[0]} M77777M M77777M M77777M""", 23 | """{c[0]} M77777M M77777M M77777M""", 24 | """{c[0]} M77777M M77777M M77777M""", 25 | """{c[0]} MMMMMMM MMMMMMM MMMMMMM""", 26 | ] 27 | -------------------------------------------------------------------------------- /archey/logos/moevalent.py: -------------------------------------------------------------------------------- 1 | """Moevalent logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.MAGENTA_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]}UUUUU{c[1]}VVVVVVVVVVVVVVVVVVVVVVV{c[0]}UUUUU""", 9 | """{c[0]}UUUUUU{c[1]}VVVVVVVV{c[2]}MM{c[1]}V{c[2]}MMVVVVVVVV{c[0]}UUUUUU""", 10 | """{c[0]}UUUUUUU{c[1]}VVVVVV{c[2]}MMMMMMM{c[1]}VVVVVV{c[0]}UUUUUUU""", 11 | """{c[0]}UUUUUUU {c[1]}VVVVVV{c[2]}MMMMM{c[1]}VVVVVV {c[0]}UUUUUUU""", 12 | """{c[0]}UUUUUUUEE{c[1]}VVVVVV{c[2]}MMM{c[1]}VVVVVV {c[0]}UUUUUUU""", 13 | """{c[0]}UUUUUUUEEE{c[1]}VVVVVV{c[2]}M{c[1]}VVVVVV {c[0]}UUUUUUU""", 14 | """{c[0]}UUUUUUU {c[1]}VVVVVVVVVVV {c[0]}UUUUUUU""", 15 | """{c[0]}UUUUUUUEEEEEEEEEEEEEE UUUUUUU""", 16 | """{c[0]}UUUUUUUEEEEEEEEEEEEEE UUUUUUU""", 17 | """{c[0]}UUUUUUU {c[1]}VVVVV {c[0]}UUUUUUU""", 18 | """{c[0]}UUUUUUU {c[1]}VVV {c[0]}UUUUUUU""", 19 | """{c[0]} UUUUUUU {c[1]}V {c[0]}UUUUUUU """, 20 | """{c[0]} UUUUUUUUUUUUUUUUUUU """, 21 | """{c[0]} UUUUUUUUUUUUU """, 22 | ] 23 | -------------------------------------------------------------------------------- /archey/logos/netbsd.py: -------------------------------------------------------------------------------- 1 | """NetBSD logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.WHITE_NORMAL, Colors.RED_BRIGHT] 6 | 7 | # pylint: disable=line-too-long 8 | LOGO = [ 9 | """{c[0]} {c[1]}__,gnnnOCCCCCOObaau,_{c[0]} """, 10 | """{c[0]} _.{c[1]}_{c[0]} {c[1]}__,gnnCCCCCCCCOPF"''{c[0]} {c[1]}~{c[0]}""", 11 | """{c[0]} (N\\{c[1]}XCbngg,._____.,gnnndCCCCCCCCCCCCF"___,,,,___{c[0]} """, 12 | """{c[0]} \\N\\{c[1]}XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCOOOOPYvv.{c[0]} """, 13 | """{c[0]} \\N\\{c[1]}XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCPF"''{c[0]} """, 14 | """{c[0]} \\N\\{c[1]}XCCCCCCCCCCCCCCCCCCCCCCCCCOF"'{c[0]} """, 15 | """{c[0]} \\N\\{c[1]}XCCCCCCCCCCCCCCCCCCCCOF"'{c[0]} """, 16 | """{c[0]} \\N\\{c[1]}XCCCCCCCCCCCCCCCPF"'{c[0]} """, 17 | """{c[0]} \\N\\{c[1]}"PCOCCCOCCFP""{c[0]} """, 18 | """{c[0]} \\N\\ """, 19 | """{c[0]} \\N\\ """, 20 | """{c[0]} \\N\\ """, 21 | """{c[0]} \\N\\ """, 22 | """{c[0]} \\NN\\ """, 23 | """{c[0]} \\NN\\ """, 24 | """{c[0]} \\NNA. """, 25 | """{c[0]} \\NNA, """, 26 | """{c[0]} \\NNN, """, 27 | """{c[0]} \\NNN\\ """, 28 | """{c[0]} \\NNN\\ """, 29 | """{c[0]} \\NNNA """, 30 | ] 31 | -------------------------------------------------------------------------------- /archey/logos/nixos.py: -------------------------------------------------------------------------------- 1 | """NixOS logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.BLUE_NORMAL, Colors.CYAN_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} ::::. {c[1]}':::::{c[0]} {c[1]}::::'{c[0]} """, 9 | """{c[0]} '::::: {c[1]}':::::.{c[0]} {c[1]}::::'{c[0]} """, 10 | """{c[0]} ::::: {c[1]}'::::.:::::{c[0]} """, 11 | """{c[0]} .......:::::..... {c[1]}::::::::{c[0]} """, 12 | """{c[0]} ::::::::::::::::::. {c[1]}::::::{c[0]} ::::. """, 13 | """{c[0]} ::::::::::::::::::::: {c[1]}:::::.{c[0]} .::::' """, 14 | """{c[0]} {c[1]}.....{c[0]} {c[1]}::::'{c[0]} :::::' """, 15 | """{c[0]} {c[1]}:::::{c[0]} {c[1]}'::'{c[0]} :::::' """, 16 | """{c[0]} {c[1]}........:::::{c[0]} {c[1]}'{c[0]} :::::::::::.""", 17 | """{c[0]} {c[1]}:::::::::::::{c[0]} :::::::::::::""", 18 | """{c[0]} {c[1]}:::::::::::{c[0]} .. ::::: """, 19 | """{c[0]} {c[1]}.:::::{c[0]} .::: ::::: """, 20 | """{c[0]} {c[1]}.:::::{c[0]} ::::: ''''' {c[1]}.....{c[0]} """, 21 | """{c[0]} {c[1]}:::::{c[0]} ':::::. {c[1]}......:::::::::::::'{c[0]} """, 22 | """{c[0]} {c[1]}:::{c[0]} ::::::. {c[1]}':::::::::::::::::'{c[0]} """, 23 | """{c[0]} .:::::::: {c[1]}'::::::::::{c[0]} """, 24 | """{c[0]} .::::''::::. {c[1]}'::::.{c[0]} """, 25 | """{c[0]} .::::' ::::. {c[1]}'::::.{c[0]} """, 26 | """{c[0]} .:::: :::: {c[1]}'::::.{c[0]} """, 27 | ] 28 | -------------------------------------------------------------------------------- /archey/logos/nobara.py: -------------------------------------------------------------------------------- 1 | """Nobara logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} .cONWMWNOl;cdkKXWWMMMMWWXKkdc'. """, 9 | """{c[0]} kWMMMMMMMWWMMMMMMMMMMMMMMMMMMN0o,. """, 10 | """{c[0]} WMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNO:. """, 11 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWO; """, 12 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXl. """, 13 | """{c[0]} MMMMMMMMMMMMMMMWX0kkkk0XWMMMMMMMMMMMMNo.""", 14 | """{c[0]} MMMMMMMMMMMMMXd;. .;dXMMMMMMMMMMMXc""", 15 | """{c[0]} MMMMMMMMMMMWk' 'kWMMMMMMMMMM0""", 16 | """{c[0]} MMMMMMMMMMMO. ;dO00ko. .dXNWMMMMMMMW""", 17 | """{c[0]} MMMMMMMMMMWo cNMMMMMWO' .,cdKWMMMMM""", 18 | """{c[0]} MMMMMMMMMMWl lNMMMMMM0' .lxOKNWMMMMM""", 19 | """{c[0]} MMMMMMMMMMWl .cOXXKKx, lWMMMMMMMMMM""", 20 | """{c[0]} MMMMMMMMMMMk. .... lWMMMMMMMMMM""", 21 | """{c[0]} MMMMMMMMMMMW0o:,,. lWMMMMMMMMMM""", 22 | """{c[0]} MMMMMMMMMMMMMMMWWXOc,. lWMMMMMMMMMM""", 23 | """{c[0]} MMMMMMMMMMMMMMMMMMMWNk. lWMMMMMMMMMM""", 24 | """{c[0]} MMMMMMMMMMMMMWNXNWMMMNc lWMMMMMMMMMM""", 25 | """{c[0]} XMMMMMMMMMWOl;'..;lOXXl ;XMMMMMMMMMX""", 26 | """{c[0]} :0WMMMMMW0c. .'c; :0WMMMMMW0:""", 27 | """{c[0]} 'dKWMWXx' . 'dXWMWKd' """, 28 | ] 29 | -------------------------------------------------------------------------------- /archey/logos/openbsd.py: -------------------------------------------------------------------------------- 1 | """OpenBSD logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.YELLOW_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} /\\ __ /\\ """, 9 | """{c[0]} ___.;;'```` ``';; ___ """, 10 | """{c[0]} `\\.;` /\\ /\\ `;./ """, 11 | """{c[0]} __ ./ .., \\. """, 12 | """{c[0]} _ \\ / .. /\\ / .. \\ """, 13 | """{c[0]} \\""\\ .| \\ , / _\\ /`. """, 14 | """{c[0]} \\= \"""| | {c[1]}O{c[0]} ||{c[1]}D{c[0]}; """, 15 | """{c[0]} \\= | / - ' ` ``-,/ \\.; """, 16 | """{c[0]} | __|; \\ . \\ ,-.-,""", 17 | """{c[0]} |=| | , |< -|-)""", 18 | """{c[0]} _/_/ /_\\ / / / ,/ `-'-`""", 19 | """{c[0]} `\\ `` `../ / / """, 20 | """{c[0]} `; \\/ \\/ ;' """, 21 | """{c[0]} /`;;.. ..;;``_\\ """, 22 | """{c[0]} ``` ``\\/``--``\\/` """, 23 | ] 24 | -------------------------------------------------------------------------------- /archey/logos/opensuse.py: -------------------------------------------------------------------------------- 1 | """openSUSE logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.GREEN_NORMAL, Colors.WHITE_BRIGHT] 6 | 7 | # pylint: disable=line-too-long 8 | LOGO = [ 9 | """{c[0]} {c[1]}.;ldkO0000Okdl;.{c[0]} """, 10 | """{c[0]} {c[1]}.;d00xl:^''''''^:ok00d;.{c[0]} """, 11 | """{c[0]} {c[1]}.d00l'{c[0]} {c[1]}'o00d.{c[0]} """, 12 | """{c[0]} {c[1]}.d0Kd'{c[0]} Okxol:;,.{c[0]} {c[1]}:O0d.{c[0]} """, 13 | """{c[0]} {c[1]}.OK{c[0]}KKK0kOKKKKKKKKKKOxo:, {c[1]}lKO.{c[0]} """, 14 | """{c[0]} {c[1]},0K{c[0]}KKKKKKKKKKKKKKK0P^{c[1]},,,{c[0]}^dx:{c[0]} {c[1]};00,{c[0]} """, 15 | """{c[0]} {c[1]}.OK{c[0]}KKKKKKKKKKKKKKKk'{c[1]}.oOPPb.{c[0]}'0k.{c[0]} {c[1]}cKO.{c[0]}""", 16 | """{c[0]} {c[1]}:KK{c[0]}KKKKKKKKKKKKKKK: {c[1]}kKx..dd{c[0]} lKd{c[0]} {c[1]}'OK:{c[0]}""", 17 | """{c[0]} {c[1]}dKK{c[0]}KKKKKKKKKOx0KKKd {c[1]}^0KKKO'{c[0]} kKKc{c[0]} {c[1]}dKd{c[0]}""", 18 | """{c[0]} {c[1]}dKK{c[0]}KKKKKKKKKK;.;oOKx,..{c[1]}^{c[0]}..;kKKK0.{c[0]} {c[1]}dKd{c[0]}""", 19 | """{c[0]} {c[1]}:KK{c[0]}KKKKKKKKKK0o;...^cdxxOK0O/^^' {c[1]}.0K:{c[0]}""", 20 | """{c[0]} {c[1]}kKK{c[0]}KKKKKKKKKKKKK0x;,,......,;od {c[1]}lKk{c[0]} """, 21 | """{c[0]} {c[1]}'0K{c[0]}KKKKKKKKKKKKKKKKKKKK00KKOo^ {c[1]}c00'{c[0]} """, 22 | """{c[0]} {c[1]}'kK{c[0]}KKOxddxkOO00000Okxoc;'' {c[1]}.dKk'{c[0]} """, 23 | """{c[0]} {c[1]}l0Ko.{c[0]} {c[1]}.c00l'{c[0]} """, 24 | """{c[0]} {c[1]}'l0Kk:.{c[0]} {c[1]}.;xK0l'{c[0]} """, 25 | """{c[0]} {c[1]}'lkK0xl:;,,,,;:ldO0kl'{c[0]} """, 26 | """{c[0]} {c[1]}'^:ldxkkkkxdl:^'{c[0]} """, 27 | ] 28 | -------------------------------------------------------------------------------- /archey/logos/parabola.py: -------------------------------------------------------------------------------- 1 | """Parabola logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.MAGENTA_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} _,, _ """, 9 | """{c[0]} _, ,##' ,##; """, 10 | """{c[0]} ,. ,##' ,##' ,#####;""", 11 | """{c[0]} _,-#' ,#' ,##' ,#######'""", 12 | """{c[0]} _,-##^' ` ,######### """, 13 | """{c[0]} .-^` `######### """, 14 | """{c[0]} ######## """, 15 | """{c[0]} ;###### """, 16 | """{c[0]} ;####* """, 17 | """{c[0]} ####' """, 18 | """{c[0]} ;### """, 19 | """{c[0]} ,##' """, 20 | """{c[0]} ## """, 21 | """{c[0]} #' """, 22 | """{c[0]} / """, 23 | """{c[0]} ' """, 24 | ] 25 | -------------------------------------------------------------------------------- /archey/logos/pop.py: -------------------------------------------------------------------------------- 1 | """Pop!_OS logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.CYAN_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | # pylint: disable=line-too-long 8 | LOGO = [ 9 | """{c[0]} ///////////// """, 10 | """{c[0]} ///////////////////// """, 11 | """{c[0]} ///////{c[1]}*767{c[0]}//////////////// """, 12 | """{c[0]} //////{c[1]}7676767676*{c[0]}////////////// """, 13 | """{c[0]} /////{c[1]}76767{c[0]}//{c[1]}7676767{c[0]}////////////// """, 14 | """{c[0]} /////{c[1]}767676{c[0]}///{c[1]}*76767{c[0]}/////////////// """, 15 | """{c[0]} ///////{c[1]}767676{c[0]}///{c[1]}76767{c[0]}.///{c[1]}7676*{c[0]}/////// """, 16 | """{c[0]} /////////{c[1]}767676{c[0]}//{c[1]}76767{c[0]}///{c[1]}767676{c[0]}////////""", 17 | """{c[0]} //////////{c[1]}76767676767{c[0]}////{c[1]}76767{c[0]}/////////""", 18 | """{c[0]} ///////////{c[1]}76767676{c[0]}//////{c[1]}7676{c[0]}//////////""", 19 | """{c[0]} ////////////,{c[1]}7676{c[0]},///////{c[1]}767{c[0]}///////////""", 20 | """{c[0]} /////////////*{c[1]}7676{c[0]}///////{c[1]}76{c[0]}////////////""", 21 | """{c[0]} ///////////////{c[1]}7676{c[0]}////////////////////""", 22 | """{c[0]} ///////////////{c[1]}7676{c[0]}///{c[1]}767{c[0]}//////////// """, 23 | """{c[0]} //////////////////////{c[1]}'{c[0]}//////////// """, 24 | """{c[0]} //////{c[1]}.7676767676767676767,{c[0]}////// """, 25 | """{c[0]} /////{c[1]}767676767676767676767{c[0]}///// """, 26 | """{c[0]} /////////////////////////// """, 27 | """{c[0]} ///////////////////// """, 28 | """{c[0]} ///////////// """, 29 | ] 30 | -------------------------------------------------------------------------------- /archey/logos/quirinux.py: -------------------------------------------------------------------------------- 1 | """Quirinux logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.MAGENTA_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} @=++++++++++=@ """, 9 | """{c[0]} =++++++++++++++++++= """, 10 | """{c[0]} *++++++++++++++++++++++* """, 11 | """{c[0]} =++++++++++++++++++++++++++= """, 12 | """{c[0]} *++++++++{c[1]}-..........-{c[0]}++++++++* """, 13 | """{c[0]} =++++++++{c[1]}..............{c[0]}++++++++= """, 14 | """{c[0]} @++++++++{c[1]}:.....:{c[0]}++{c[1]}:.....:{c[0]}++++++++@""", 15 | """{c[0]} =++++++++{c[1]}:.....{c[0]}++++{c[1]}.....:{c[0]}++++++++=""", 16 | """{c[0]} =++++++++{c[1]}:.....{c[0]}++++{c[1]}.....:{c[0]}++++++++=""", 17 | """{c[0]} #++++++++{c[1]}:.....{c[0]}++++{c[1]}.....:{c[0]}++++++++#""", 18 | """{c[0]} +++++++++{c[1]}......{c[0]}--{c[1]}......{c[0]}+++++++++ """, 19 | """{c[0]} @++++++++{c[1]}:............:{c[0]}++++++++@ """, 20 | """{c[0]} @+++++++++++{c[1]}-....-{c[0]}+++++++++++@ """, 21 | """{c[0]} *++++++++++{c[1]}::::{c[0]}++++++++++* """, 22 | """{c[0]} *++++++++++++++++++++* """, 23 | """{c[0]} @*++++++++++++++*@ """, 24 | """{c[0]} @#====#@ """, 25 | ] 26 | -------------------------------------------------------------------------------- /archey/logos/raspbian.py: -------------------------------------------------------------------------------- 1 | """Raspbian logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.GREEN_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} {c[1]}.',;:cc;,'.{c[0]} {c[1]}.,;::c:,,.{c[0]} """, 9 | """{c[0]} {c[1]},ooolcloooo:{c[0]} {c[1]}'oooooccloo:{c[0]} """, 10 | """{c[0]} {c[1]}.looooc;;:ol{c[0]} {c[1]}:oc;;:ooooo'{c[0]} """, 11 | """{c[0]} {c[1]};oooooo:{c[0]} {c[1]},ooooooc.{c[0]} """, 12 | """{c[0]} {c[1]}.,:;'.{c[0]} {c[1]}.;:;'.{c[0]} """, 13 | """{c[0]} .dQ. .d0Q0Q0. '0Q. """, 14 | """{c[0]} .0Q0' 'Q0Q0Q' 'Q0Q. """, 15 | """{c[0]} '' .odo. .odo. '' """, 16 | """{c[0]} . .0Q0Q0Q' .0Q0Q0Q. . """, 17 | """{c[0]} ,0Q .0Q0Q0Q0Q 'Q0Q0Q0b. 0Q.""", 18 | """{c[0]} :Q0 Q0Q0Q0Q 'Q0Q0Q0 Q0'""", 19 | """{c[0]} '0 '0Q0' .0Q0. '0' 'Q'""", 20 | """{c[0]} .oo. .0Q0Q0. .oo. """, 21 | """{c[0]} 'Q0Q0. '0Q0Q0Q0. .Q0Q0b """, 22 | """{c[0]} 'Q0Q0. '0Q0Q0' .d0Q0Q' """, 23 | """{c[0]} 'Q0Q' .. '0Q.' """, 24 | """{c[0]} .0Q0Q0Q. """, 25 | """{c[0]} '0Q0Q' """, 26 | ] 27 | -------------------------------------------------------------------------------- /archey/logos/rhel.py: -------------------------------------------------------------------------------- 1 | """Red Hat logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} .MM:..:MMMMMMM. """, 9 | """{c[0]} MMMMMMMMMMMMMMMMMM """, 10 | """{c[0]} MMMMMMMMMMMMMMMMMMMM. """, 11 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMM """, 12 | """{c[0]} ,MMMMMMMMMMMMMMMMMMMMMM: """, 13 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMMMM """, 14 | """{c[0]} .MMMM' MMMMMMMMMMMMMMMMMMMMMM """, 15 | """{c[0]} MMMMMM `MMMMMMMMMMMMMMMMMMMM. """, 16 | """{c[0]} MMMMMMMM MMMMMMMMMMMMMMMMMM . """, 17 | """{c[0]} MMMMMMMMM. `MMMMMMMMMMMMM' MM. """, 18 | """{c[0]} `MMMMMMMMMMMMM. `""` ,MMMMM. """, 19 | """{c[0]} `MMMMMMMMMMMMMMMMM:. .:MMMMMMMM. """, 20 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM """, 21 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM: """, 22 | """{c[0]} MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM """, 23 | """{c[0]} `MMMMMMMMMMMMMMMMMMMMMMMM: """, 24 | """{c[0]} ``MMMMMMMMMMMMMMMMM' """, 25 | """{c[0]} `""` """, 26 | """{c[1]} R e d H a t """, 27 | ] 28 | 29 | # Alias to the current logo "the hat" 30 | COLORS_HAT = COLORS 31 | LOGO_HAT = LOGO 32 | 33 | 34 | # Alternative logo : "Shadowman" (tho old logo). 35 | COLORS_SHADOWMAN = [Colors.RED_BRIGHT, Colors.WHITE_BRIGHT, Colors.RED_NORMAL] 36 | 37 | LOGO_SHADOWMAN = [ 38 | """{c[0]} {c[2]}\\`.-..........\\`{c[0]} """, 39 | """{c[0]} {c[2]}\\`////////::.\\`-/.{c[0]} """, 40 | """{c[0]} {c[2]}-: ....-////////.{c[0]} """, 41 | """{c[0]} {c[2]}//:-::///////////\\`{c[0]} """, 42 | """{c[0]} {c[2]}\\`--::: \\`-://////////////:{c[0]} """, 43 | """{c[0]} {c[2]}//////- \\`\\`.-://///////{c[0]} .\\` """, 44 | """{c[0]} {c[2]}\\`://////:-.\\` :///////::///:\\`{c[0]} """, 45 | """{c[0]} {c[2]}.-/////////:---/////////////:{c[0]} """, 46 | """{c[0]} {c[2]}.-://////////////////////.{c[0]} """, 47 | """{c[0]} {c[1]}yMN+\\`.-${c[2]}::///////////////-\\`{c[0]} """, 48 | """{c[0]} {c[1]}.-\\`:NMMNMs\\` \\`..-------..\\`{c[0]} """, 49 | """{c[0]} {c[1]}MN+/mMMMMMhoooyysshsss{c[0]} """, 50 | """{c[0]} {c[1]}MMM MMMMMMMMMMMMMMyyddMMM+{c[0]} """, 51 | """{c[0]} {c[1]}MMMM MMMMMMMMMMMMMNdyNMMh\\` hyhMMM{c[0]}""", 52 | """{c[0]} {c[1]}MMMMMMMMMMMMMMMMyoNNNMMM+. MMMMMMMM{c[0]} """, 53 | """{c[0]} {c[1]}MMNMMMNNMMMMMNM+ mhsMNyyyyMNMMMMsMM{c[0]} """, 54 | ] 55 | -------------------------------------------------------------------------------- /archey/logos/rocky.py: -------------------------------------------------------------------------------- 1 | """Rocky Linux logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.GREEN_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]} __wgliliiligw_, """, 9 | """{c[0]} _williiiiiiliilililw, """, 10 | """{c[0]} _%%iiiiiilililiiiiiiiiiii_ """, 11 | """{c[0]} .Qliiiililiiiiiiililililiilm. """, 12 | """{c[0]} _iiiiiliiiiiililiiiiiiiiiiliil, """, 13 | """{c[0]} .lililiiilililiiiilililililiiiii, """, 14 | """{c[0]} _liiiiiiliiiiiiiliiiiiF{{iiiiiilili,""", 15 | """{c[0]} jliililiiilililiiili@` ~ililiiiiiL""", 16 | """{c[0]} iiiliiiiliiiiiiili>` ~liililii""", 17 | """{c[0]} liliiiliiilililii` -9liiiil""", 18 | """{c[0]} iiiiiliiliiiiii~ "4lili""", 19 | """{c[0]} 4ililiiiiilil~ -w, )4lf""", 20 | """{c[0]} -liiiiililiF' _liig, )'""", 21 | """{c[0]} )iiiliii@` _QIililig, """, 22 | """{c[0]} )iiii>` .Qliliiiililw """, 23 | """{c[0]} )<>~ .mliiiiiliiiiiil, """, 24 | """{c[0]} _gllilililiililii~ """, 25 | """{c[0]} giliiiiiiiiiiiiT` """, 26 | """{c[0]} -^~$ililili@~~' """, 27 | ] 28 | -------------------------------------------------------------------------------- /archey/logos/siduction.py: -------------------------------------------------------------------------------- 1 | """Siduction logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.BLUE_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} ,__~~:. """, 9 | """{c[0]} >|]>I<]1 """, 10 | """{c[0]} ,r/(1(/_ """, 11 | """{c[0]} . ';+_l` .^::,^. """, 12 | """{c[0]} `_-i<<` "(]<;l+[` """, 13 | """{c[0]} t(?>~[\\ 1t1-~+](_ """, 14 | """{c[0]} ^\\ftt\\^ '\\jt//f(' .''. """, 15 | """{c[0]} .^^`. .'. `:I;,' `","'.;}}-<>+_" """, 16 | """{c[0]} ,(+I>[^ ])_l;<}}" -\\}}+l;i-1:""", 17 | """{c[0]} "j|1)/^ \\t)[]{{\\l ]j\\{{]][)/;""", 18 | """{c[0]} .",". .. .!|rf{{, .'`' .]<>~" '?[~!>-; '``' """, 20 | """{c[0]} <\\]<-): """, 21 | """{c[0]} '{{jtttl +r/())\\f` """, 22 | """{c[0]} .'' ."::,` `>}})?;. """, 23 | """{c[0]} ^|]>;i?+ """, 24 | """{c[0]} ;j([?[)/ """, 25 | """{c[0]} :(rjt+' """, 26 | ] 27 | -------------------------------------------------------------------------------- /archey/logos/slackware.py: -------------------------------------------------------------------------------- 1 | """Slackware logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.BLUE_NORMAL, Colors.BLUE_BRIGHT, Colors.CLEAR] 6 | 7 | # pylint: disable=line-too-long 8 | LOGO = [ 9 | """{c[0]} {c[1]}:::::::{c[0]} """, 10 | """{c[0]} {c[1]}:::::::::::::::::::{c[0]} """, 11 | """{c[0]} {c[1]}:::::::::::::::::::::::::{c[0]} """, 12 | """{c[0]} {c[1]}::::::::{c[2]}cllcccccllllllll{c[1]}::::::{c[0]} """, 13 | """{c[0]} {c[1]}:::::::::{c[2]}lc{c[0]} {c[2]}dc{c[1]}:::::::{c[0]} """, 14 | """{c[0]} {c[1]}::::::::{c[2]}cl{c[0]} {c[2]}clllccllll{c[0]} {c[2]}oc{c[1]}:::::::::{c[0]} """, 15 | """{c[0]} {c[1]}:::::::::{c[2]}o{c[0]} {c[2]}lc{c[1]}::::::::{c[2]}co{c[0]} {c[2]}oc{c[1]}::::::::::{c[0]} """, 16 | """{c[0]} {c[1]}::::::::::{c[2]}o{c[0]} {c[2]}cccclc{c[1]}:::::{c[2]}clcc{c[1]}::::::::::::{c[0]} """, 17 | """{c[0]} {c[1]}:::::::::::{c[2]}lc{c[0]} {c[2]}cclccclc{c[1]}:::::::::::::{c[0]} """, 18 | """{c[0]} {c[1]}::::::::::::::{c[2]}lcclcc{c[0]} {c[2]}lc{c[1]}::::::::::::{c[0]}""", 19 | """{c[0]} {c[1]}::::::::::{c[2]}cclcc{c[1]}:::::{c[2]}lccclc{c[0]} {c[2]}oc{c[1]}:::::::::::{c[0]}""", 20 | """{c[0]} {c[1]}::::::::::{c[2]}o{c[0]} {c[2]}l{c[1]}::::::::::{c[2]}l{c[0]} {c[2]}lc{c[1]}:::::::::::{c[0]}""", 21 | """{c[0]} {c[1]}:::::{c[2]}c{c[0]} {c[0]}{c[1]}::{c[2]}o{c[0]} {c[2]}clcllcccll{c[0]} {c[2]}o{c[1]}:::::::::::{c[0]} """, 22 | """{c[0]} {c[1]}:::::{c[2]}o{c[0]} {c[0]}{c[1]}:{c[2]}o{c[0]} {c[2]}clc{c[1]}:::::::::::{c[0]} """, 23 | """{c[0]} {c[1]}::::{c[2]}o{c[0]} {c[0]}{c[1]}:{c[2]}ccslclccclclccclclc{c[1]}:::::::::::::{c[0]} """, 24 | """{c[0]} {c[1]}:::{c[2]}o{c[0]} {c[1]}:::::{c[0]} """, 25 | """{c[0]} {c[1]}::{c[2]}lcccccccccccccccccccccccccccco{c[1]}::::{c[0]} """, 26 | """{c[0]} {c[1]}::::::::::::::::::::::::::::::::{c[0]} """, 27 | """{c[0]} {c[1]}::::::::::::::::::::::::::::{c[0]} """, 28 | """{c[0]} {c[1]}::::::::::::::::::::::{c[0]} """, 29 | """{c[0]} {c[1]}::::::::::::{c[0]} """, 30 | ] 31 | -------------------------------------------------------------------------------- /archey/logos/ubuntu.py: -------------------------------------------------------------------------------- 1 | """Ubuntu logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.RED_BRIGHT, Colors.WHITE_BRIGHT] 6 | 7 | # pylint: disable=line-too-long 8 | LOGO = [ 9 | """{c[0]} ..vvAAAvv.. """, 10 | """{c[0]} .:s/OOOOOOOOOOOOO\\s:. """, 11 | """{c[0]} .sOOOOOOOOOOOOOO{c[1]}.vv.{c[0]}OOOs. """, 12 | """{c[0]} .sOOOOOOOOO{c[1]},ssssn{c[0]}:{c[1]}lMMl:{c[0]}OOOOs. """, 13 | """{c[0]} :OOOOOOO{c[1]}.n{c[0]}@{c[1]}'MMMMMMy{c[0]}\\{c[1]}^^{c[0]}/{c[1]}.{c[0]}OOOOOO: """, 14 | """{c[0]} :OOOOOOO{c[1]}iMMj{c[0]}@{c[1]}"{c[0]}OOOO{c[1]}"YIOOIl{c[0]}OOOOOOO: """, 15 | """{c[0]} .OOOOOO{c[1]}.MMMi{c[0]}OOOOOOOOOO{c[1]}`WMMM:{c[0]}OOOOOO.""", 16 | """{c[0]} iOO{c[1]}.vv.{c[0]}O{c[1]}JMW{c[0]}OOOOOOOOOOOOO{c[1]}:MMM:{c[0]}OOOOOi""", 17 | """{c[0]} OO{c[1]}:MMMM:{c[0]}l{c[1]}I:{c[0]}OOOOOOOOOOOOOii+iiOOOOOO""", 18 | """{c[0]} iOO{c[1]}'YY'{c[0]}O{c[1]}JMM{c[0]}OOOOOOOOOOOOO{c[1]}:MMM:{c[0]}OOOOOi""", 19 | """{c[0]} 'OOOOOO{c[1]}'MMMi{c[0]}OOOOOOOOOOO{c[1]},MMMi{c[0]}OOOOOO'""", 20 | """{c[0]} :OOOOOO{c[1]}'iMMY{c[0]}@{c[1]},{c[0]}OOOOOO{c[1]},;MMM/{c[0]}OOOOOO: """, 21 | """{c[0]} :OOOOOOO{c[1]}"Y{c[0]}@{c[1]}AMivviiY'__`'{c[0]}OOOOOO: """, 22 | """{c[0]} 'QOOOOOOOO{c[1]}'"YYYYK{c[0]}O{c[1]}aMMM:{c[0]}OOOOP' """, 23 | """{c[0]} 'QOOOOOOOOOOOOOO{c[1]}`YY'{c[0]}OOOP' """, 24 | """{c[0]} ':QOOOOOOOOOOOOOOOQ:' """, 25 | """{c[0]} '"":YOOOY:""' """, 26 | ] 27 | -------------------------------------------------------------------------------- /archey/logos/univalent.py: -------------------------------------------------------------------------------- 1 | """Univalent logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.CYAN_BRIGHT, Colors.GREEN_BRIGHT] 6 | 7 | LOGO = [ 8 | """{c[0]}UUUUU{c[1]}VVVVVVVVVVVVVVVVVVVVVVV{c[0]}UUUUU""", 9 | """{c[0]}UUUUUU{c[1]}VVVVVVVVVVVVVVVVVVVVV{c[0]}UUUUUU""", 10 | """{c[0]}UUUUUUU{c[1]}VVVVVVVVVVVVVVVVVVV{c[0]}UUUUUUU""", 11 | """{c[0]}UUUUUUU {c[1]}VVVVVVVVVVVVVVVVV {c[0]}UUUUUUU""", 12 | """{c[0]}UUUUUUUEE{c[1]}VVVVVVVVVVVVVVV {c[0]}UUUUUUU""", 13 | """{c[0]}UUUUUUUEEE{c[1]}VVVVVVVVVVVVV {c[0]}UUUUUUU""", 14 | """{c[0]}UUUUUUU {c[1]}VVVVVVVVVVV {c[0]}UUUUUUU""", 15 | """{c[0]}UUUUUUUEEEEEEEEEEEEEE UUUUUUU""", 16 | """{c[0]}UUUUUUUEEEEEEEEEEEEEE UUUUUUU""", 17 | """{c[0]}UUUUUUU {c[1]}VVVVV {c[0]}UUUUUUU""", 18 | """{c[0]}UUUUUUU {c[1]}VVV {c[0]}UUUUUUU""", 19 | """{c[0]} UUUUUUU {c[1]}V {c[0]}UUUUUUU """, 20 | """{c[0]} UUUUUUUUUUUUUUUUUUU """, 21 | """{c[0]} UUUUUUUUUUUUU """, 22 | ] 23 | -------------------------------------------------------------------------------- /archey/logos/windows.py: -------------------------------------------------------------------------------- 1 | """Windows logo""" 2 | 3 | from archey.colors import Colors 4 | 5 | COLORS = [Colors.BLUE_BRIGHT, Colors.RED_BRIGHT, Colors.GREEN_BRIGHT, Colors.YELLOW_NORMAL] 6 | 7 | LOGO = [ 8 | """{c[0]} {c[1]},.=:!!t3Z3z.,{c[0]} """, 9 | """{c[0]} {c[1]}.tt:::tt333EE3{c[0]} {c[2]},{c[0]} """, 10 | """{c[0]} {c[1]}Et:::ztt33EEE;{c[0]} {c[2]}@Ee.,{c[0]} {c[2]}..,{c[0]}""", 11 | """{c[0]} {c[1]};tt:::tt333EE7{c[0]} {c[2]};EEEEEEttttt33#{c[0]}""", 12 | """{c[0]} {c[1]}.Et:::zt333EEQ'{c[0]}{c[2]}.SEEEEEttttt33Q;{c[0]}""", 13 | """{c[0]} {c[1]}it::::tt333EEF{c[0]} {c[2]}@EEEEEEttttt33F{c[0]} """, 14 | """{c[0]} {c[1]};3=*^```'*4EEV{c[0]} {c[2]}:EEEEEEttttt33@'{c[0]} """, 15 | """{c[0]} ,.=::::it=.,{c[1]} `{c[0]} {c[2]}@EEEEEEtttz33QF{c[0]} """, 16 | """{c[0]} ;::::::::zt33){c[0]} {c[3]}, {c[2]}'4EEEtttji3P*{c[0]} """, 17 | """{c[0]} ,l::::::::tt33'{c[3]} Z3z..{c[2]} `` {c[3]},..g:{c[0]} """, 18 | """{c[0]} y::::::::zt33;{c[0]} {c[3]}AEEEtttt::::ztF{c[0]} """, 19 | """{c[0]} ;:::::::::t33J{c[0]} {c[3]};EEEttttt::::t3'{c[0]} """, 20 | """{c[0]} ,E;:::::::zt33:{c[0]} {c[3]}@EEEtttt::::z3;{c[0]} """, 21 | """{c[0]} {{3=*^```'*4E3P{c[0]} {c[3]};EEEtttt:::::tZ{c[0]} """, 22 | """{c[0]} `{c[0]} {c[3]}`:EEEEttt:::::z`{c[0]} """, 23 | """{c[0]} {c[3]}'VEzjt:;;z>*`{c[0]} """, 24 | ] 25 | -------------------------------------------------------------------------------- /archey/processes.py: -------------------------------------------------------------------------------- 1 | """Simple class (acting as a singleton) to handle processes listing""" 2 | 3 | import logging 4 | import typing 5 | from subprocess import PIPE, CalledProcessError, check_output 6 | 7 | from archey.singleton import Singleton 8 | 9 | 10 | class Processes(metaclass=Singleton): 11 | """At startup, instantiate this class to populate a list of running processes""" 12 | 13 | def __init__(self): 14 | self._processes: typing.List[str] 15 | 16 | try: 17 | ps_output = check_output(["ps", "-eo", "comm"], stderr=PIPE, universal_newlines=True) 18 | except OSError as os_error: 19 | self._processes = [] 20 | logging.warning("`ps` failed or `procps`/`procps-ng` isn't installed : %s", os_error) 21 | except CalledProcessError as process_error: 22 | self._processes = [] 23 | logging.warning( 24 | "This implementation of `ps` might not be supported : %s", process_error.stderr 25 | ) 26 | else: 27 | # Discard first heading line here. 28 | self._processes = ps_output.splitlines()[1:] 29 | 30 | @property 31 | def list(self) -> tuple: 32 | """Simple getter to retrieve (am immutable copy of) the processes list""" 33 | return tuple(self._processes) 34 | 35 | @property 36 | def number(self) -> int: 37 | """Simple getter to retrieve the number of stored processes""" 38 | return len(self._processes) 39 | -------------------------------------------------------------------------------- /archey/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HorlogeSkynet/archey4/f9e98a281bd6832fa7f3abf353dae16853d01f02/archey/py.typed -------------------------------------------------------------------------------- /archey/screenshot.py: -------------------------------------------------------------------------------- 1 | """Simple module doing its best at taking a screenshot of the current screen""" 2 | 3 | import logging 4 | import os 5 | import platform 6 | import time 7 | import typing 8 | from contextlib import ExitStack 9 | from datetime import datetime 10 | from functools import partial 11 | from subprocess import DEVNULL, CalledProcessError, check_call 12 | 13 | 14 | def take_screenshot(output_file: typing.Optional[str] = None) -> bool: 15 | """ 16 | Simple function trying to take a screenshot using various famous back-end programs. 17 | When supported by a found and available back-end, **try to** honor `output_file`. 18 | Returns a `bool` representing whether or not a screenshot could be taken. 19 | """ 20 | if output_file is None or os.path.isdir(output_file): 21 | # When a directory is provided, we've to force `output_file` to represent a **file** path. 22 | output_file = os.path.join( 23 | (output_file or os.getcwd()), 24 | datetime.now().strftime("archey4_screenshot_%Y-%m-%d_%H.%M.%S.png"), 25 | ) 26 | 27 | # Some programs don't accept specific filename as parameters. 28 | # In such cases, we may provide them a target directory instead. 29 | output_dir = os.path.dirname(output_file) 30 | 31 | # Back-end programs that _may_ (?) be available across different platforms. 32 | screenshot_tools = { 33 | "Flameshot": ["flameshot", "full", "-p", output_dir], 34 | "ImageMagick": ["import", "-window", "root", output_file], 35 | "maim": ["maim", output_file], 36 | "scrot": ["scrot", "-z", output_file], 37 | "Shutter": ["shutter", "-f", "-o", output_file, "-e"], 38 | } 39 | 40 | # Extends the original screenshot tools dictionary according to current platform. 41 | if platform.system() == "Windows": 42 | screenshot_tools["SnippingTool"] = ["SnippingTool.exe", "/clip"] 43 | elif platform.system() == "Darwin": 44 | screenshot_tools["ScreenCapture"] = [ 45 | "screencapture", 46 | "-x", 47 | "-t", 48 | output_file.rpartition(".")[2], 49 | output_file, 50 | ] 51 | else: # *NIX systems (and others)... 52 | screenshot_tools["Escrotum"] = ["escrotum", output_file] 53 | screenshot_tools["GNOME-Screenshot"] = ["gnome-screenshot", "-f", output_file] 54 | screenshot_tools["grim"] = ["grim", output_file] 55 | screenshot_tools["KDE-Spectacle"] = ["spectacle", "-b", "-o", output_file] 56 | screenshot_tools["Xfce4-Screenshooter"] = ["xfce4-screenshooter", "-f", "-s", output_dir] 57 | screenshot_tools["Screencap (Android)"] = [ 58 | "screencap", # Binary available on Android. 59 | "-p", # It only accepts PNG as image output format. 60 | (output_file.rpartition(".")[0] + ".png"), 61 | ] 62 | 63 | # This part purposefully blocks so we wait a little bit before taking the screenshot. 64 | # It prevents taking a screenshot before Archey's output has appeared. 65 | for time_remaining in range(3, 0, -1): 66 | taking_sc_str = f"Taking screenshot in {time_remaining:1d}..." 67 | print(taking_sc_str, end="", flush=True) 68 | time.sleep(1) 69 | print("\r" + " " * len(taking_sc_str), end="\r", flush=True) 70 | time.sleep(0.5) 71 | 72 | with ExitStack() as defer_stack: 73 | for screenshot_tool, screenshot_cmd in screenshot_tools.items(): 74 | try: 75 | check_call(screenshot_cmd, stderr=DEVNULL) 76 | except OSError: 77 | continue 78 | except CalledProcessError as process_error: 79 | defer_stack.callback( 80 | partial( 81 | logging.warning, 82 | 'Couldn\'t take a screenshot with %s: "%s".', 83 | screenshot_tool, 84 | process_error, 85 | ) 86 | ) 87 | continue 88 | 89 | return True 90 | 91 | logging.error( 92 | """\ 93 | Sorry, we couldn\'t find any supported program to take a screenshot on your system. 94 | Please install one of the following and try again: %s.""", 95 | ", ".join(screenshot_tools.keys()), 96 | ) 97 | return False 98 | -------------------------------------------------------------------------------- /archey/singleton.py: -------------------------------------------------------------------------------- 1 | """Simple singleton meta-class definition""" 2 | 3 | from abc import ABCMeta as AbstractBaseMetaClass 4 | from typing import Dict 5 | 6 | 7 | class Singleton(AbstractBaseMetaClass): 8 | """ 9 | Taken from : 10 | This meta-class allows us to declare `Configuration` as a singleton. 11 | This way, we are able to import `Configuration` in multiple modules, ... 12 | ... whereas it is effectively loaded only once. 13 | You cannot instantiate this meta-class directly. 14 | """ 15 | 16 | _instances: Dict["Singleton", object] = {} 17 | 18 | def __call__(cls, *args, **kwargs): 19 | if cls not in cls._instances: 20 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 21 | return cls._instances[cls] 22 | -------------------------------------------------------------------------------- /archey/test/__init__.py: -------------------------------------------------------------------------------- 1 | """`archey.test` module initialization file""" 2 | 3 | import logging 4 | import unittest 5 | 6 | # Disable `WARNING` log level (internally used). 7 | logging.disable(logging.WARNING) 8 | 9 | # This global stops `unittest` from printing tracebacks _beyond_ our custom assertion. 10 | # See . 11 | __unittest = True # pylint: disable=invalid-name 12 | 13 | 14 | # From . 15 | class CustomAssertions: 16 | """This class defines our custom assertion methods being used in Archey unit testing""" 17 | 18 | @staticmethod 19 | def assertListEmpty(obj): # pylint: disable=invalid-name 20 | """Simple method to check that passed `obj` is an **empty** `list`""" 21 | if not isinstance(obj, list): 22 | raise AssertionError("First sequence is not a list: " + str(obj)) 23 | 24 | if obj: 25 | raise AssertionError("First sequence is not empty: " + str(obj)) 26 | 27 | @staticmethod 28 | def assertTupleEmpty(obj): # pylint: disable=invalid-name 29 | """Simple method to check that passed `obj` is an **empty** `tuple`""" 30 | if not isinstance(obj, tuple): 31 | raise AssertionError("First sequence is not a tuple: " + str(obj)) 32 | 33 | if obj: 34 | raise AssertionError("First sequence is not empty: " + str(obj)) 35 | 36 | 37 | class TestCustomAssertions(unittest.TestCase, CustomAssertions): 38 | """This class implements test cases for the custom Archey unit testing framework (#inception)""" 39 | 40 | def test_assert_list_empty(self): 41 | """Test cases for our `self.assertListEmpty` custom assertion""" 42 | self.assertListEmpty([]) 43 | self.assertRaises(AssertionError, self.assertListEmpty, {}) 44 | self.assertRaises(AssertionError, self.assertListEmpty, "test") 45 | self.assertRaises(AssertionError, self.assertListEmpty, ["test"]) 46 | 47 | def test_assert_tuple_empty(self): 48 | """Test cases for our `self.assertTupleEmpty` custom assertion""" 49 | self.assertTupleEmpty(()) 50 | self.assertRaises(AssertionError, self.assertTupleEmpty, {}) 51 | self.assertRaises(AssertionError, self.assertTupleEmpty, "test") 52 | self.assertRaises(AssertionError, self.assertTupleEmpty, ("test",)) 53 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_distro.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's distribution detection module""" 2 | 3 | import unittest 4 | from unittest.mock import MagicMock, patch 5 | 6 | from archey.configuration import DEFAULT_CONFIG 7 | from archey.entries.distro import Distro 8 | from archey.test.entries import HelperMethods 9 | 10 | 11 | class TestDistroEntry(unittest.TestCase): 12 | """`Distro` entry simple test cases""" 13 | 14 | @patch( 15 | "archey.entries.distro.check_output", 16 | return_value="10\n", # Imitate `getprop` output on Android 10. 17 | ) 18 | def test_fetch_android_release(self, _): 19 | """Test `_fetch_android_release` static method""" 20 | self.assertEqual( 21 | Distro._fetch_android_release(), # pylint: disable=protected-access 22 | "Android 10", 23 | ) 24 | 25 | @patch( 26 | "archey.entries.distro.platform.mac_ver", 27 | side_effect=[ 28 | ("", ("", "", ""), ""), # Darwin case. 29 | ("11.1", ("foo", "bar", "baz"), "x86_64"), # macOS case. 30 | ], 31 | ) 32 | @patch( 33 | "archey.entries.distro.platform.release", 34 | return_value="20.2.0", # Darwin release. 35 | ) 36 | def test_fetch_darwin_release(self, _, __): 37 | """Test `_fetch_darwin_release` static method""" 38 | self.assertEqual( 39 | Distro._fetch_darwin_release(), # pylint: disable=protected-access 40 | "Darwin 20.2.0", 41 | ) 42 | self.assertEqual( 43 | Distro._fetch_darwin_release(), # pylint: disable=protected-access 44 | "macOS 11.1", 45 | ) 46 | 47 | @HelperMethods.patch_clean_configuration 48 | def test_unknown_distro_output(self): 49 | """Test for `output` method when distribution name couldn't be found""" 50 | distro_intance_mock = HelperMethods.entry_mock(Distro) 51 | output_mock = MagicMock() 52 | 53 | distro_intance_mock.value = { 54 | "name": None, 55 | "arch": "ARCHITECTURE", 56 | } 57 | 58 | Distro.output(distro_intance_mock, output_mock) 59 | self.assertEqual( 60 | output_mock.append.call_args[0][1], 61 | f"{DEFAULT_CONFIG['default_strings']['not_detected']} ARCHITECTURE", 62 | ) 63 | 64 | 65 | if __name__ == "__main__": 66 | unittest.main() 67 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_hostname.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's device host-name detection module""" 2 | 3 | import unittest 4 | from unittest.mock import mock_open, patch 5 | 6 | from archey.entries.hostname import Hostname 7 | 8 | 9 | class TestHostnameEntry(unittest.TestCase): 10 | """Test cases mocking for `/etc/hostname` file and `platform.node` call""" 11 | 12 | @patch( 13 | "archey.entries.hostname.open", 14 | mock_open( 15 | read_data="""\ 16 | MY-COOL-LAPTOP 17 | """ 18 | ), 19 | ) 20 | def test_etc_hostname(self): 21 | """Mock reading from `/etc/hostname`""" 22 | self.assertEqual(Hostname().value, "MY-COOL-LAPTOP") 23 | 24 | @patch( 25 | "archey.entries.hostname.open", 26 | side_effect=FileNotFoundError(), 27 | ) 28 | @patch( 29 | "archey.entries.hostname.platform.node", 30 | return_value="MY-COOL-LAPTOP", 31 | ) 32 | def test_hostname(self, _, __): 33 | """Mock call to `hostname`""" 34 | self.assertEqual(Hostname().value, "MY-COOL-LAPTOP") 35 | 36 | 37 | if __name__ == "__main__": 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_kernel.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's kernel information detection module""" 2 | 3 | import unittest 4 | from unittest.mock import MagicMock, Mock, patch 5 | 6 | from archey.configuration import DEFAULT_CONFIG 7 | from archey.entries.kernel import Kernel 8 | from archey.test.entries import HelperMethods 9 | 10 | 11 | class TestKernelEntry(unittest.TestCase): 12 | """ 13 | Here, we mock the `platform` module calls and check afterwards 14 | that the output is correct. 15 | """ 16 | 17 | @patch( 18 | "archey.entries.kernel.platform.system", 19 | return_value="Linux", 20 | ) 21 | @patch( 22 | "archey.entries.kernel.platform.release", 23 | return_value="X.Y.Z-R-arch", 24 | ) 25 | def test_fetch_kernel_release(self, _, __): 26 | """Verify `platform` module mocking""" 27 | self.assertEqual(Kernel().value["name"], "Linux") 28 | self.assertEqual(Kernel().value["release"], "X.Y.Z-R-arch") 29 | 30 | @patch("archey.entries.kernel.urlopen") 31 | def test_fetch_latest_linux_release(self, urlopen_mock): 32 | """Check proper JSON decoding and value gathering""" 33 | urlopen_mock.return_value.__enter__.return_value.read.return_value = b"""\ 34 | { 35 | "latest_stable": { 36 | "version": "5.10.1" 37 | }, 38 | "releases": [ 39 | { 40 | "iseol": false, 41 | "version": "5.10", 42 | "moniker": "mainline", 43 | "source": "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.tar.xz", 44 | "pgp": "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.tar.sign", 45 | "released": { 46 | "timestamp": 1607899290, 47 | "isodate": "2020-12-13" 48 | }, 49 | "gitweb": "https://git.kernel.org/torvalds/h/v5.10", 50 | "changelog": null, 51 | "diffview": "https://git.kernel.org/torvalds/ds/v5.10/v5.9", 52 | "patch": { 53 | "full": "https://cdn.kernel.org/pub/linux/kernel/v5.x/patch-5.10.xz", 54 | "incremental": null 55 | } 56 | }, 57 | { 58 | "iseol": false, 59 | "version": "5.10.1", 60 | "moniker": "stable", 61 | "source": "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.1.tar.xz", 62 | "pgp": "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.1.tar.sign", 63 | "released": { 64 | "timestamp": 1607970794, 65 | "isodate": "2020-12-14" 66 | }, 67 | "gitweb": "https://git.kernel.org/stable/h/v5.10.1", 68 | "changelog": "https://cdn.kernel.org/pub/linux/kernel/v5.x/ChangeLog-5.10.1", 69 | "diffview": "https://git.kernel.org/stable/ds/v5.10.1/v5.10", 70 | "patch": { 71 | "full": "https://cdn.kernel.org/pub/linux/kernel/v5.x/patch-5.10.1.xz", 72 | "incremental": null 73 | } 74 | } 75 | ] 76 | }""" 77 | self.assertEqual( 78 | Kernel._fetch_latest_linux_release(), "5.10.1" # pylint: disable=protected-access 79 | ) 80 | 81 | @patch( 82 | "archey.entries.kernel.platform.system", 83 | return_value="Java", 84 | ) 85 | @patch( 86 | "archey.entries.kernel.platform.release", 87 | return_value="X.Y.Z-R-arch", 88 | ) 89 | @patch( 90 | "archey.entries.kernel.Environment", 91 | Mock(DO_NOT_TRACK=False), 92 | ) 93 | def test_non_linux_platform(self, _, __): 94 | """Check behavior on non-Linux platforms""" 95 | kernel = Kernel(options={"check_version": True}) 96 | 97 | self.assertIsNone(kernel.value["latest"]) 98 | self.assertIsNone(kernel.value["is_outdated"]) 99 | 100 | @patch( 101 | "archey.entries.kernel.platform.release", 102 | return_value="X.Y.Z-R-arch", 103 | ) 104 | @patch( 105 | "archey.entries.kernel.Environment", 106 | Mock(DO_NOT_TRACK=True), 107 | ) 108 | def test_do_not_track(self, _): 109 | """Check `DO_NOT_TRACK` is correctly honored""" 110 | kernel = Kernel(options={"check_version": True}) 111 | 112 | self.assertIsNone(kernel.value["latest"]) 113 | self.assertIsNone(kernel.value["is_outdated"]) 114 | 115 | @patch( 116 | "archey.entries.kernel.platform.release", 117 | return_value="1.2.3-4-arch", 118 | ) 119 | @patch( 120 | "archey.entries.kernel.Kernel._fetch_latest_linux_release", 121 | side_effect=["1.2.3", "1.3.2"], 122 | ) 123 | @patch( 124 | "archey.entries.kernel.platform.system", 125 | return_value="Linux", 126 | ) 127 | @patch( 128 | "archey.entries.kernel.Environment", 129 | Mock(DO_NOT_TRACK=False), 130 | ) 131 | @HelperMethods.patch_clean_configuration 132 | def test_kernel_comparison(self, _, __, ___): 133 | """Check kernel releases comparison and output templates""" 134 | output_mock = MagicMock() 135 | 136 | # Only current release (`check_version` disabled by default). 137 | kernel = Kernel() 138 | kernel.output(output_mock) 139 | self.assertEqual(output_mock.append.call_args[0][1], "Linux 1.2.3-4-arch") 140 | 141 | # Current = latest (up to date !). 142 | kernel = Kernel(options={"check_version": True}) 143 | kernel.output(output_mock) 144 | 145 | self.assertTrue(kernel.value["latest"]) 146 | self.assertIs(kernel.value["is_outdated"], False) 147 | self.assertEqual( 148 | output_mock.append.call_args[0][1], 149 | f"Linux 1.2.3-4-arch ({DEFAULT_CONFIG['default_strings']['latest']})", 150 | ) 151 | 152 | # Current < latest (outdated). 153 | kernel = Kernel(options={"check_version": True}) 154 | kernel.output(output_mock) 155 | 156 | self.assertTrue(kernel.value["latest"]) 157 | self.assertIs(kernel.value["is_outdated"], True) 158 | self.assertEqual( 159 | output_mock.append.call_args[0][1], 160 | f"Linux 1.2.3-4-arch (1.3.2 {DEFAULT_CONFIG['default_strings']['available']})", 161 | ) 162 | 163 | 164 | if __name__ == "__main__": 165 | unittest.main() 166 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_load_average.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey LoadAverage detection module""" 2 | 3 | import unittest 4 | from unittest.mock import MagicMock 5 | 6 | from archey.colors import Colors 7 | from archey.entries.load_average import LoadAverage 8 | from archey.test.entries import HelperMethods 9 | 10 | 11 | class TestLoadAverageEntry(unittest.TestCase): 12 | """LoadAverage `output` configuration-based coloration test class""" 13 | 14 | def setUp(self): 15 | """Define mocked entry before each test""" 16 | self.load_average_mock = HelperMethods.entry_mock(LoadAverage) 17 | self.output_mock = MagicMock() 18 | 19 | @HelperMethods.patch_clean_configuration 20 | def test_output_coloration(self): 21 | """Test `output` coloration based on user preferences""" 22 | self.load_average_mock.value = (0.5, 1.25, 2.5) 23 | self.load_average_mock.options = { 24 | "warning_threshold": 0.75, 25 | "danger_threshold": 2.25, 26 | } 27 | 28 | LoadAverage.output(self.load_average_mock, self.output_mock) 29 | self.assertEqual( 30 | self.output_mock.append.call_args[0][1], 31 | f"{Colors.GREEN_NORMAL}0.5{Colors.CLEAR} " 32 | f"{Colors.YELLOW_NORMAL}1.25{Colors.CLEAR} " 33 | f"{Colors.RED_NORMAL}2.5{Colors.CLEAR}", 34 | ) 35 | 36 | @HelperMethods.patch_clean_configuration 37 | def test_output_rounding(self): 38 | """Test `output` rounding based on user preferences""" 39 | self.load_average_mock.value = (0.33333, 1.25, 2.0) 40 | 41 | with self.subTest("No decimal places"): 42 | self.load_average_mock.options = { 43 | "decimal_places": 0, 44 | "warning_threshold": 5, 45 | "danger_threshold": 5, 46 | } 47 | LoadAverage.output(self.load_average_mock, self.output_mock) 48 | self.assertEqual( 49 | self.output_mock.append.call_args[0][1], 50 | f"{Colors.GREEN_NORMAL}0.0{Colors.CLEAR} " 51 | f"{Colors.GREEN_NORMAL}1.0{Colors.CLEAR} " 52 | f"{Colors.GREEN_NORMAL}2.0{Colors.CLEAR}", 53 | ) 54 | 55 | with self.subTest("1 decimal place"): 56 | self.load_average_mock.options = { 57 | "decimal_places": 1, 58 | "warning_threshold": 5, 59 | "danger_threshold": 5, 60 | } 61 | LoadAverage.output(self.load_average_mock, self.output_mock) 62 | self.assertEqual( 63 | self.output_mock.append.call_args[0][1], 64 | f"{Colors.GREEN_NORMAL}0.3{Colors.CLEAR} " 65 | f"{Colors.GREEN_NORMAL}1.2{Colors.CLEAR} " 66 | f"{Colors.GREEN_NORMAL}2.0{Colors.CLEAR}", 67 | ) 68 | 69 | with self.subTest("2 decimal places"): 70 | self.load_average_mock.options = { 71 | "decimal_places": 2, 72 | "warning_threshold": 5, 73 | "danger_threshold": 5, 74 | } 75 | LoadAverage.output(self.load_average_mock, self.output_mock) 76 | self.assertEqual( 77 | self.output_mock.append.call_args[0][1], 78 | f"{Colors.GREEN_NORMAL}0.33{Colors.CLEAR} " 79 | f"{Colors.GREEN_NORMAL}1.25{Colors.CLEAR} " 80 | f"{Colors.GREEN_NORMAL}2.0{Colors.CLEAR}", 81 | ) 82 | 83 | 84 | if __name__ == "__main__": 85 | unittest.main() 86 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_ram.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's RAM usage detection module""" 2 | 3 | import unittest 4 | from unittest.mock import MagicMock, mock_open, patch 5 | 6 | from archey.colors import Colors 7 | from archey.configuration import DEFAULT_CONFIG 8 | from archey.entries.ram import RAM 9 | from archey.test.entries import HelperMethods 10 | 11 | 12 | class TestRAMEntry(unittest.TestCase): 13 | """ 14 | Here, we mock the `check_output` call to `free` using all three levels of available ram. 15 | In the last test, mock with `/proc/meminfo` file opening during the manual way. 16 | """ 17 | 18 | @patch( 19 | "archey.entries.ram.check_output", 20 | return_value="""\ 21 | total used free shared buff/cache available 22 | Mem: 15658 2043 10232 12 3382 13268 23 | Swap: 4095 39 4056 24 | """, 25 | ) 26 | def test_run_free_dash_m(self, _): 27 | """Test `_run_free_dash_m` output parsing""" 28 | self.assertTupleEqual( 29 | RAM._run_free_dash_m(), # pylint: disable=protected-access 30 | (2043.0, 15658.0), 31 | ) 32 | 33 | @patch( 34 | "archey.entries.ram.open", 35 | mock_open( 36 | read_data="""\ 37 | MemTotal: 7581000 kB 38 | MemFree: 716668 kB 39 | MemAvailable: 3632244 kB 40 | Buffers: 478524 kB 41 | Cached: 2807032 kB 42 | SwapCached: 67092 kB 43 | Active: 3947284 kB 44 | Inactive: 2447708 kB 45 | Active(anon): 2268724 kB 46 | Inactive(anon): 1106220 kB 47 | Active(file): 1678560 kB 48 | Inactive(file): 1341488 kB 49 | Unevictable: 128 kB 50 | Mlocked: 128 kB 51 | SwapTotal: 7811068 kB 52 | SwapFree: 7277708 kB 53 | Dirty: 144 kB 54 | Writeback: 0 kB 55 | AnonPages: 3067204 kB 56 | Mapped: 852272 kB 57 | Shmem: 451056 kB 58 | Slab: 314100 kB 59 | SReclaimable: 200792 kB 60 | SUnreclaim: 113308 kB 61 | """ 62 | ), 63 | ) # Some lines have been ignored as they are useless for computations. 64 | def test_read_proc_meminfo(self): 65 | """Test `_read_proc_meminfo` content parsing""" 66 | self.assertTupleEqual( 67 | RAM._read_proc_meminfo(), # pylint: disable=protected-access 68 | (3739.296875, 7403.3203125), 69 | ) 70 | 71 | @patch( 72 | "archey.entries.ram.check_output", 73 | side_effect=[ 74 | "8589934592\n", 75 | """\ 76 | Mach Virtual Memory Statistics: (page size of 4096 bytes) 77 | Pages free: 55114. 78 | Pages active: 511198. 79 | Pages inactive: 488363. 80 | Pages speculative: 22646. 81 | Pages throttled: 0. 82 | Pages wired down: 666080. 83 | Pages purgeable: 56530. 84 | "Translation faults": 170435998. 85 | Pages copy-on-write: 3496023. 86 | Pages zero filled: 96454484. 87 | Pages reactivated: 12101726. 88 | Pages purged: 6728288. 89 | File-backed pages: 445114. 90 | Anonymous pages: 577093. 91 | Pages stored in compressor: 2019211. 92 | Pages occupied by compressor: 353431. 93 | Decompressions: 10535599. 94 | Compressions: 19723567. 95 | Pageins: 7586286. 96 | Pageouts: 388644. 97 | Swapins: 2879182. 98 | Swapouts: 3456015. 99 | """, 100 | ], 101 | ) 102 | def test_run_sysctl_and_vmstat(self, _): 103 | """Check `sysctl` and `vm_stat` parsing logic""" 104 | self.assertTupleEqual( 105 | RAM._run_sysctl_and_vmstat(), # pylint: disable=protected-access 106 | (1685.58984375, 8192.0), 107 | ) 108 | 109 | @patch( 110 | "archey.entries.ram.check_output", 111 | side_effect=[ 112 | """\ 113 | 3992309 114 | 3050620 115 | 297854 116 | """ 117 | ], 118 | ) 119 | @patch( 120 | "archey.entries.ram.os.sysconf", 121 | return_value=4096, 122 | ) 123 | def test_run_sysctl_mem(self, _, __): 124 | """Test _run_sysctl_mem()""" 125 | self.assertTupleEqual( 126 | RAM._run_sysctl_mem(), # pylint: disable=protected-access 127 | (2514.98046875, 15594.95703125), 128 | ) 129 | 130 | @HelperMethods.patch_clean_configuration 131 | def test_various_output_configuration(self): 132 | """Test `output` overloading based on user preferences""" 133 | ram_instance_mock = HelperMethods.entry_mock(RAM) 134 | output_mock = MagicMock() 135 | 136 | with self.subTest("Output in case of non-detection."): 137 | RAM.output(ram_instance_mock, output_mock) 138 | self.assertEqual( 139 | output_mock.append.call_args[0][1], 140 | DEFAULT_CONFIG["default_strings"]["not_detected"], 141 | ) 142 | 143 | output_mock.reset_mock() 144 | 145 | with self.subTest('"Normal" output (green).'): 146 | ram_instance_mock.value = { 147 | "used": 2043.0, 148 | "total": 15658.0, 149 | "unit": "MiB", 150 | } 151 | ram_instance_mock.options = { 152 | "warning_use_percent": 33.3, 153 | "danger_use_percent": 66.7, 154 | } 155 | 156 | RAM.output(ram_instance_mock, output_mock) 157 | self.assertEqual( 158 | output_mock.append.call_args[0][1], 159 | f"{Colors.GREEN_NORMAL}2043 MiB{Colors.CLEAR} / 15658 MiB", 160 | ) 161 | 162 | output_mock.reset_mock() 163 | 164 | with self.subTest('"Danger" output (red).'): 165 | ram_instance_mock.value = { 166 | "used": 7830.0, 167 | "total": 15658.0, 168 | "unit": "MiB", 169 | } 170 | ram_instance_mock.options = { 171 | "warning_use_percent": 25, 172 | "danger_use_percent": 50, 173 | } 174 | 175 | RAM.output(ram_instance_mock, output_mock) 176 | self.assertEqual( 177 | output_mock.append.call_args[0][1], 178 | f"{Colors.RED_NORMAL}7830 MiB{Colors.CLEAR} / 15658 MiB", 179 | ) 180 | 181 | 182 | if __name__ == "__main__": 183 | unittest.main() 184 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_shell.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's shell detection module""" 2 | 3 | import unittest 4 | from subprocess import CalledProcessError 5 | from unittest.mock import MagicMock, patch 6 | 7 | from archey.configuration import DEFAULT_CONFIG 8 | from archey.entries.shell import Shell 9 | from archey.test.entries import HelperMethods 10 | 11 | 12 | class TestShellEntry(unittest.TestCase): 13 | """ 14 | For this entry, we'll just verify that the output is non-null. 15 | """ 16 | 17 | @patch( 18 | "archey.entries.shell.os.getenv", 19 | return_value="SHELL", 20 | ) 21 | def test_getenv(self, _): 22 | """Simple mock, simple test""" 23 | self.assertEqual(Shell().value, "SHELL") 24 | 25 | @patch( 26 | "archey.entries.shell.os.getenv", 27 | return_value=None, 28 | ) 29 | @patch( 30 | "archey.entries.shell.os.getuid", 31 | return_value=1000, 32 | create=True, # Only available on UNIX platforms... 33 | ) 34 | @patch( 35 | "archey.entries.shell.check_output", 36 | return_value="USERNAME:x:1000:1000:User Name,,,:/home/user:/bin/bash\n", 37 | ) 38 | def test_getent_call(self, _, __, ___): 39 | """Mock `getent` returned value and check the correct assignment""" 40 | self.assertEqual(Shell().value, "/bin/bash") 41 | 42 | @patch( 43 | "archey.entries.shell.os.getuid", 44 | side_effect=AttributeError(), 45 | ) 46 | def test_os_getuid_missing(self, _): 47 | """Check behavior when `os.getuid` is not available""" 48 | self.assertIsNone(Shell._query_name_service_switch()) # pylint: disable=protected-access 49 | 50 | @patch( 51 | "archey.entries.shell.os.getenv", 52 | return_value=None, 53 | ) 54 | @patch( 55 | "archey.entries.shell.check_output", 56 | side_effect=CalledProcessError(2, "getent"), 57 | ) 58 | @HelperMethods.patch_clean_configuration 59 | def test_config_fall_back(self, _, __): 60 | """`id` fails, but Archey must not !""" 61 | shell = Shell() 62 | 63 | output_mock = MagicMock() 64 | shell.output(output_mock) 65 | 66 | self.assertIsNone(shell.value) 67 | self.assertEqual( 68 | output_mock.append.call_args[0][1], DEFAULT_CONFIG["default_strings"]["not_detected"] 69 | ) 70 | 71 | 72 | if __name__ == "__main__": 73 | unittest.main() 74 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_terminal.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's terminal detection module""" 2 | 3 | import unittest 4 | from unittest.mock import MagicMock, patch 5 | 6 | from archey.configuration import DEFAULT_CONFIG 7 | from archey.entries.terminal import Terminal 8 | from archey.test.entries import HelperMethods 9 | 10 | 11 | class TestTerminalEntry(unittest.TestCase): 12 | """ 13 | For this entry, we'll verify that the output contains what the environment 14 | is supposed to give, plus the right number of "colorized" characters. 15 | """ 16 | 17 | def setUp(self): 18 | # By default, colors won't be disabled. 19 | self._should_color_output_patch = patch( 20 | "archey.colors.Style.should_color_output", 21 | return_value=True, 22 | ) 23 | self._should_color_output_patch.start() 24 | 25 | def tearDown(self): 26 | self._should_color_output_patch.stop() 27 | 28 | @patch.dict( 29 | "archey.entries.terminal.os.environ", 30 | { 31 | "TERM": "xterm-256color", 32 | "COLORTERM": "truecolor", 33 | "TERM_PROGRAM": "A-COOL-TERMINAL-EMULATOR", 34 | }, 35 | clear=True, 36 | ) 37 | def test_terminal_emulator_term_program(self): 38 | """Check that `TERM_PROGRAM` is honored even if `TERM` or `COLORTERM` are defined""" 39 | self.assertEqual(Terminal().value, "A-COOL-TERMINAL-EMULATOR") 40 | 41 | @patch.dict( 42 | "archey.entries.terminal.os.environ", 43 | {"TERM_PROGRAM": "A-COOL-TERMINAL-EMULATOR", "TERM_PROGRAM_VERSION": "vX.Y.Z-beta42"}, 44 | clear=True, 45 | ) 46 | def test_terminal_emulator_term_program_version(self): 47 | """Check that `TERM_PROGRAM` and `TERM_PROGRAM_VERSION` are correctly honored""" 48 | self.assertEqual(Terminal().value, "A-COOL-TERMINAL-EMULATOR vX.Y.Z-beta42") 49 | 50 | @patch.dict( 51 | "archey.entries.terminal.os.environ", 52 | { 53 | "TERM": "OH-A-SPECIAL-CASE", 54 | "TERMINATOR_UUID": "urn:uuid:xxxxxxxx-yyyy-zzzz-tttt-uuuuuuuuuuuu", # Ignored. 55 | }, 56 | clear=True, 57 | ) 58 | def test_terminal_emulator_special_term(self): 59 | """Check that `TERM` is honored even if a "known identifier" could be found""" 60 | self.assertEqual(Terminal().value, "OH-A-SPECIAL-CASE") 61 | 62 | @patch.dict( 63 | "archey.entries.terminal.os.environ", 64 | {"TERM": "xterm-256color", "KONSOLE_VERSION": "X.Y.Z"}, 65 | clear=True, 66 | ) 67 | def test_terminal_emulator_name_normalization(self): 68 | """Check that our manual terminal detection as long as name normalization are working""" 69 | self.assertEqual(Terminal().value, "Konsole") 70 | 71 | @patch.dict( 72 | "archey.entries.terminal.os.environ", 73 | {"TERM": "xterm-256color"}, 74 | clear=True, 75 | ) 76 | def test_terminal_emulator_term_fallback_and_unicode(self): 77 | """Check that `TERM` is honored if present, and Unicode support for the colors palette""" 78 | terminal = Terminal(options={"use_unicode": True}) 79 | 80 | output_mock = MagicMock() 81 | terminal.output(output_mock) 82 | 83 | self.assertEqual(terminal.value, "xterm-256color") 84 | self.assertTrue(output_mock.append.call_args[0][1].startswith("xterm-256color")) 85 | self.assertEqual(output_mock.append.call_args[0][1].count("\u2588"), 7 * 2) 86 | 87 | @patch.dict( 88 | "archey.entries.terminal.os.environ", 89 | {"COLORTERM": "kmscon"}, 90 | clear=True, 91 | ) 92 | def test_terminal_emulator_colorterm(self): 93 | """Check we can detect terminals using the `COLORTERM` environment variable.""" 94 | self.assertEqual(Terminal().value, "KMSCON") 95 | 96 | @patch.dict( 97 | "archey.entries.terminal.os.environ", 98 | {"TERM": "xterm-256color", "KONSOLE_VERSION": "200401", "COLORTERM": "kmscon"}, 99 | clear=True, 100 | ) 101 | def test_terminal_emulator_colorterm_override(self): 102 | """ 103 | Check we observe terminal using `COLORTERM` even if `TERM` or a "known identifier" is found. 104 | """ 105 | self.assertEqual(Terminal().value, "KMSCON") 106 | 107 | @patch.dict( 108 | "archey.entries.terminal.os.environ", 109 | {"TERM_PROGRAM": "X-TERMINAL-EMULATOR"}, 110 | clear=True, 111 | ) 112 | def test_color_disabling(self): 113 | """Test `Terminal` output behavior when coloration has been disabled""" 114 | with patch("archey.colors.Style.should_color_output", return_value=False): 115 | terminal = Terminal() 116 | 117 | output_mock = MagicMock() 118 | terminal.output(output_mock) 119 | 120 | self.assertEqual(terminal.value, "X-TERMINAL-EMULATOR") 121 | self.assertEqual(output_mock.append.call_args[0][1], "X-TERMINAL-EMULATOR") 122 | 123 | @patch.dict( 124 | "archey.entries.terminal.os.environ", 125 | {}, 126 | clear=True, 127 | ) 128 | @HelperMethods.patch_clean_configuration 129 | def test_not_detected(self): 130 | """Test terminal emulator (non-)detection, without Unicode support""" 131 | terminal = Terminal(options={"use_unicode": False}) 132 | 133 | output_mock = MagicMock() 134 | terminal.output(output_mock) 135 | output = output_mock.append.call_args[0][1] 136 | 137 | self.assertIsNone(terminal.value) 138 | self.assertTrue(output.startswith(DEFAULT_CONFIG["default_strings"]["not_detected"])) 139 | self.assertFalse(output.count("\u2588")) 140 | 141 | 142 | if __name__ == "__main__": 143 | unittest.main() 144 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_user.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's session user name detection module""" 2 | 3 | import unittest 4 | from unittest.mock import MagicMock, patch 5 | 6 | from archey.configuration import DEFAULT_CONFIG 7 | from archey.entries.user import User 8 | from archey.test.entries import HelperMethods 9 | 10 | 11 | class TestUserEntry(unittest.TestCase): 12 | """ 13 | For this entry, we'll simply mock `getpass.getuser` call. 14 | If internals happen to fail, `ImportError` might be raised. 15 | """ 16 | 17 | @patch( 18 | "archey.entries.user.getpass.getuser", 19 | side_effect=[ 20 | "USERNAME", 21 | ImportError("pwd", "Sure, you got a good reason..."), 22 | ], 23 | ) 24 | def test_getenv(self, _): 25 | """Simple mock, simple test""" 26 | self.assertEqual(User().value, "USERNAME") 27 | self.assertIsNone(User().value) 28 | 29 | @HelperMethods.patch_clean_configuration 30 | def test_output(self): 31 | """Simple test for `output` base method""" 32 | user_instance_mock = HelperMethods.entry_mock(User) 33 | 34 | output_mock = MagicMock() 35 | User.output(user_instance_mock, output_mock) 36 | 37 | self.assertEqual( 38 | output_mock.append.call_args[0][1], DEFAULT_CONFIG["default_strings"]["not_detected"] 39 | ) 40 | 41 | 42 | if __name__ == "__main__": 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /archey/test/entries/test_archey_window_manager.py: -------------------------------------------------------------------------------- 1 | """Test module for Archey's window manager detection module""" 2 | 3 | import unittest 4 | from unittest.mock import MagicMock, patch 5 | 6 | from archey.configuration import DEFAULT_CONFIG 7 | from archey.entries.window_manager import WindowManager 8 | from archey.test.entries import HelperMethods 9 | 10 | 11 | @patch( 12 | "archey.entries.desktop_environment.platform.system", 13 | return_value="Linux", 14 | ) 15 | class TestWindowManagerEntry(unittest.TestCase): 16 | """ 17 | Here, we mock the `check_output` call and check afterwards 18 | that the output is correct. 19 | We've to test the case where `wmctrl` is not installed too. 20 | """ 21 | 22 | @patch( 23 | "archey.entries.window_manager.check_output", 24 | return_value="""\ 25 | Name: WINDOW MANAGER 26 | Class: N/A 27 | PID: N/A 28 | Window manager's "showing the desktop" mode: OFF 29 | """, 30 | ) 31 | def test_wmctrl(self, _, __): 32 | """Test `wmctrl` output parsing""" 33 | self.assertEqual(WindowManager().value["name"], "WINDOW MANAGER") 34 | 35 | @patch( 36 | "archey.entries.window_manager.check_output", 37 | side_effect=FileNotFoundError(), # `wmctrl` call will fail 38 | ) 39 | @patch( 40 | "archey.entries.window_manager.Processes.list", 41 | ( # Fake running processes list 42 | "some", 43 | "awesome", # Match ! 44 | "programs", 45 | "running", 46 | "here", 47 | ), 48 | ) 49 | @patch( 50 | "archey.entries.desktop_environment.os.getenv", 51 | return_value="wayland", 52 | ) 53 | def test_no_wmctrl_match(self, _, __, ___): 54 | """Test basic detection based on a (fake) processes list""" 55 | window_manager = WindowManager() 56 | self.assertEqual(window_manager.value["name"], "Awesome") 57 | self.assertEqual(window_manager.value["display_server_protocol"], "Wayland") 58 | 59 | @patch( 60 | "archey.entries.window_manager.check_output", 61 | side_effect=FileNotFoundError(), # `wmctrl` call will fail 62 | ) 63 | @patch( 64 | "archey.entries.window_manager.Processes.list", 65 | ( # Fake running processes list 66 | "some", 67 | "weird", # Mismatch ! 68 | "programs", 69 | "running", 70 | "here", 71 | ), 72 | ) 73 | @HelperMethods.patch_clean_configuration 74 | def test_no_wmctrl_mismatch(self, _, __): 75 | """Test (non-detection) when processes list do not contain any known value""" 76 | window_manager = WindowManager() 77 | 78 | output_mock = MagicMock() 79 | window_manager.output(output_mock) 80 | 81 | self.assertIsNone(window_manager.value["name"]) 82 | self.assertEqual( 83 | output_mock.append.call_args[0][1], DEFAULT_CONFIG["default_strings"]["not_detected"] 84 | ) 85 | 86 | 87 | if __name__ == "__main__": 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /archey/test/test_archey_api.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.api`""" 2 | 3 | import json 4 | import unittest 5 | from datetime import datetime 6 | from unittest.mock import Mock 7 | 8 | from archey.api import API 9 | 10 | 11 | class TestApi(unittest.TestCase): 12 | """ 13 | Simple test cases to check `API` formatting behaviors. 14 | """ 15 | 16 | def test_json_serialization(self): 17 | """ 18 | Check that our JSON serialization is working as expected. 19 | """ 20 | mocked_entries = [ 21 | Mock(value="test"), 22 | Mock(value="more"), 23 | Mock(value=42), 24 | Mock( 25 | value={ 26 | "complex": { 27 | "dictionary": True, 28 | }, 29 | } 30 | ), 31 | ] 32 | 33 | # Since we can't assign a Mock's `name` attribute on creation, we'll do it here. 34 | # Note: Since each entry is only present once, all `name` attributes are always unique. 35 | for idx, name in enumerate(("simple1", "some", "simple2", "simple3")): 36 | mocked_entries[idx].name = name 37 | 38 | api_instance = API(mocked_entries) 39 | json_serialization = api_instance.json_serialization( 40 | # Imitates an execution with `-jjj`. 41 | indent=2 42 | ) 43 | output_json_document = json.loads(json_serialization) 44 | 45 | # Indentation verification (all bar first and last lines must begin with two tabs). 46 | self.assertTrue( 47 | all(line.startswith(" ") for line in json_serialization.splitlines()[1:-2]) 48 | ) 49 | 50 | # Output data verifications. 51 | self.assertIn("data", output_json_document) 52 | self.assertDictEqual( 53 | output_json_document["data"], 54 | { 55 | "simple1": "test", 56 | "some": "more", 57 | "simple2": 42, 58 | "simple3": { 59 | "complex": { 60 | "dictionary": True, 61 | }, 62 | }, 63 | }, 64 | ) 65 | 66 | # Meta-data verifications. 67 | self.assertIn("meta", output_json_document) 68 | # Check the SemVer segments types (should be integers). 69 | for semver_segment in output_json_document["meta"]["version"]: 70 | self.assertTrue(isinstance(semver_segment, int)) 71 | # Check that generated `date` meta-data is correct and not in the future. 72 | self.assertGreaterEqual( 73 | datetime.now(), 74 | # `datetime.fromisoformat` is not available for Python < 3.7, so we parse it manually. 75 | # datetime.fromisoformat(output_json_document['meta']['date']) 76 | datetime.strptime(output_json_document["meta"]["date"], "%Y-%m-%dT%H:%M:%S.%f"), 77 | ) 78 | # Check the `count` meta-data attribute. 79 | self.assertEqual(output_json_document["meta"]["count"], 4) 80 | -------------------------------------------------------------------------------- /archey/test/test_archey_colors.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.colors`""" 2 | 3 | import unittest 4 | from unittest.mock import patch 5 | 6 | from archey.colors import ANSI_ECMA_REGEXP, Colors, Colors8Bit, Style 7 | 8 | 9 | class TestColors(unittest.TestCase): 10 | """Test cases for the `Style` and `Colors` (enumeration / utility) classes""" 11 | 12 | def setUp(self): 13 | # Skip `Style.should_color_output` patching for its own testing method. 14 | if self.id().endswith("should_color_output"): 15 | return 16 | 17 | # By default, colors won't be disabled. 18 | self._should_color_output_patch = patch( 19 | "archey.colors.Style.should_color_output", 20 | return_value=True, 21 | ) 22 | self._should_color_output_patch.start() 23 | 24 | def tearDown(self): 25 | # Skip `Style.should_color_output` patching for its own testing method. 26 | if self.id().endswith("should_color_output"): 27 | return 28 | 29 | self._should_color_output_patch.stop() 30 | 31 | def test_constant_values(self): 32 | """Test `Colors` enumeration member instantiation from value""" 33 | self.assertEqual(Colors((1, 31)), Colors.RED_BRIGHT) 34 | self.assertRaises(ValueError, Colors, (-1,)) 35 | 36 | def test_string_representation(self): 37 | """Test cases for `__str__` implementations""" 38 | with self.subTest("`Colors` class"): 39 | self.assertEqual(str(Colors.CLEAR), "\x1b[0m") 40 | self.assertEqual(str(Colors.CYAN_BRIGHT), "\x1b[1;36m") 41 | 42 | with self.subTest("`Colors8Bit` class"): 43 | self.assertEqual(str(Colors8Bit(0, 0)), "\x1b[0;38;5;0m") 44 | self.assertEqual(str(Colors8Bit(0, 127)), "\x1b[0;38;5;127m") 45 | self.assertEqual(str(Colors8Bit(1, 202)), "\x1b[1;38;5;202m") 46 | 47 | def test_8_bit_out_of_range(self): 48 | """Test for exception on colors outside the defined range in `Colors8Bit`""" 49 | for test_values in [(0, 256), (2, 0), (0, -1), (-1, 0)]: 50 | with self.assertRaises(ValueError): 51 | Colors8Bit(*test_values) 52 | 53 | def test_should_color_output(self): 54 | """Test for `Style.should_color_output`""" 55 | # Clear cache filled by `functools.lru_cache` decorator. 56 | Style.should_color_output.cache_clear() 57 | 58 | with patch("archey.colors.Environment.CLICOLOR_FORCE", True): 59 | self.assertTrue(Style.should_color_output()) 60 | 61 | Style.should_color_output.cache_clear() 62 | 63 | with patch("archey.colors.Environment.CLICOLOR_FORCE", False), patch( 64 | "archey.colors.Environment.NO_COLOR", True 65 | ): 66 | self.assertFalse(Style.should_color_output()) 67 | 68 | Style.should_color_output.cache_clear() 69 | 70 | with patch("archey.colors.Environment.CLICOLOR_FORCE", False), patch( 71 | "archey.colors.Environment.NO_COLOR", False 72 | ): 73 | with patch("archey.colors.sys.stdout.isatty", return_value=False): 74 | with patch("archey.colors.Environment.CLICOLOR", True): 75 | self.assertFalse(Style.should_color_output()) 76 | 77 | Style.should_color_output.cache_clear() 78 | 79 | with patch("archey.colors.Environment.CLICOLOR", False): 80 | self.assertFalse(Style.should_color_output()) 81 | 82 | Style.should_color_output.cache_clear() 83 | 84 | with patch("archey.colors.sys.stdout.isatty", return_value=True): 85 | # Default case : STDOUT is a TTY and `CLICOLOR` is (by default) set. 86 | with patch("archey.colors.Environment.CLICOLOR", True): 87 | self.assertTrue(Style.should_color_output()) 88 | 89 | Style.should_color_output.cache_clear() 90 | 91 | with patch("archey.colors.Environment.CLICOLOR", False): 92 | self.assertFalse(Style.should_color_output()) 93 | 94 | Style.should_color_output.cache_clear() 95 | 96 | def test_escape_code_from_attrs(self): 97 | """Test for `Style.escape_code_from_attrs`""" 98 | self.assertEqual(Style.escape_code_from_attrs("0;31"), "\x1b[0;31m") 99 | self.assertEqual(Style.escape_code_from_attrs("0;31;45"), "\x1b[0;31;45m") 100 | 101 | def test_get_level_color(self): 102 | """Test for `Colors.get_level_color`""" 103 | # [25] (GREEN) < 50 < 75 104 | self.assertEqual(Colors.get_level_color(25, 50, 75), Colors.GREEN_NORMAL) 105 | # 33 < [34] (YELLOW) < 66 106 | self.assertEqual(Colors.get_level_color(34, 33, 66), Colors.YELLOW_NORMAL) 107 | # 33 < 66 < [90] (RED) 108 | self.assertEqual(Colors.get_level_color(90, 33, 66), Colors.RED_NORMAL) 109 | 110 | def test_ansi_ecma_regexp(self): 111 | """Test our ANSI/ECMA REGEXP compiled pattern""" 112 | self.assertTrue(ANSI_ECMA_REGEXP.match(str(Colors.CLEAR))) 113 | self.assertTrue(ANSI_ECMA_REGEXP.match(str(Colors.RED_NORMAL))) 114 | self.assertTrue(ANSI_ECMA_REGEXP.match(str(Colors8Bit(0, 127)))) 115 | self.assertTrue(ANSI_ECMA_REGEXP.match(Colors.escape_code_from_attrs("0;31;45"))) 116 | self.assertFalse(ANSI_ECMA_REGEXP.match("")) 117 | self.assertFalse(ANSI_ECMA_REGEXP.match("\x1b[m")) 118 | self.assertFalse(ANSI_ECMA_REGEXP.match("\x1b[0M")) 119 | # Check that matched groups contain the whole code (no capturing groups). 120 | self.assertEqual( 121 | len( 122 | "".join( 123 | ANSI_ECMA_REGEXP.findall( 124 | str(Colors.GREEN_NORMAL) + "NOT_A_COLOR" + str(Colors.CLEAR) 125 | ) 126 | ) 127 | ), 128 | len(str(Colors.GREEN_NORMAL) + str(Colors.CLEAR)), 129 | ) 130 | 131 | def test_remove_colors(self): 132 | """Test our ANSI/ECMA REGEXP colors removal method""" 133 | self.assertFalse(Style.remove_colors(str(Colors.CLEAR))) 134 | self.assertEqual(Style.remove_colors("\x1b[0;31mTEST\x1b[0;0m"), "TEST") # 4-bit 135 | self.assertEqual(Style.remove_colors("\x1b[0;38;5;127mTEST\x1b[0;0m"), "TEST") # 8-bit 136 | self.assertEqual( 137 | Style.remove_colors("\x1b[0nTEST\xde\xad\xbe\xaf"), "\x1b[0nTEST\xde\xad\xbe\xaf" 138 | ) 139 | 140 | def test_color_disabling(self): 141 | """Test `Colors` internal behavior when coloration is disabled""" 142 | with patch("archey.colors.Style.should_color_output", return_value=False): 143 | self.assertFalse(str(Colors.CYAN_NORMAL)) 144 | self.assertFalse(str(Colors8Bit(0, 127))) 145 | 146 | 147 | if __name__ == "__main__": 148 | unittest.main() 149 | -------------------------------------------------------------------------------- /archey/test/test_archey_distributions.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.distributions`""" 2 | 3 | import unittest 4 | from unittest.mock import patch 5 | 6 | from archey.distributions import Distributions 7 | 8 | 9 | class TestDistributions(unittest.TestCase): 10 | """ 11 | Test cases for the `Distributions` (enumeration / utility) class. 12 | """ 13 | 14 | def setUp(self): 15 | # Clear cache filled by `functools.lru_cache` decorator. 16 | Distributions.get_local.cache_clear() 17 | 18 | def test_constant_values(self): 19 | """Test enumeration member instantiation from value""" 20 | self.assertEqual(Distributions("debian"), Distributions.DEBIAN) 21 | self.assertRaises(ValueError, Distributions, "unknown") 22 | 23 | # Check `get_identifiers` consistency. 24 | distributions_identifiers = Distributions.get_identifiers() 25 | self.assertTrue(isinstance(distributions_identifiers, list)) 26 | self.assertTrue(all(isinstance(i, str) for i in distributions_identifiers)) 27 | 28 | @patch("archey.distributions.platform.system", return_value="Windows") 29 | def test_get_local_windows(self, _): 30 | """Test output for Windows""" 31 | self.assertEqual(Distributions.get_local(), Distributions.WINDOWS) 32 | 33 | @patch("archey.distributions.platform.system", return_value="Linux") 34 | @patch("archey.distributions.distro.id", return_value="debian") 35 | @patch( 36 | "archey.distributions.os.path.isfile", # Emulate a "regular" Debian file-system. 37 | return_value=False, # Any additional check will fail. 38 | ) 39 | def test_get_local_known_distro_id(self, _, __, ___): 40 | """Test known distribution output""" 41 | self.assertEqual(Distributions.get_local(), Distributions.DEBIAN) 42 | 43 | @patch("archey.distributions.platform.system", return_value="Linux") 44 | @patch("archey.distributions.distro.id", return_value="an-unknown-distro-id") 45 | @patch("archey.distributions.distro.like", return_value="") # No `ID_LIKE` specified. 46 | @patch( 47 | "archey.distributions.os.path.isdir", return_value=False # Make Android detection fails. 48 | ) 49 | def test_get_local_unknown_distro_id(self, _, __, ___, ____): 50 | """Test unknown distribution output""" 51 | self.assertEqual(Distributions.get_local(), Distributions.LINUX) 52 | 53 | @patch("archey.distributions.platform.system", return_value="Linux") 54 | @patch("archey.distributions.distro.id", return_value="") # Unknown distribution. 55 | @patch( 56 | "archey.distributions.distro.like", 57 | return_value="ubuntu", # Oh, it's actually an Ubuntu-based one ! 58 | ) 59 | def test_get_local_known_distro_like(self, _, __, ___): 60 | """Test distribution matching from the `os-release`'s `ID_LIKE` option""" 61 | self.assertEqual(Distributions.get_local(), Distributions.UBUNTU) 62 | 63 | @patch("archey.distributions.platform.system", return_value="Linux") 64 | @patch("archey.distributions.distro.id", return_value="") # Unknown distribution. 65 | @patch( 66 | "archey.distributions.distro.like", 67 | return_value="an-unknown-distro-id arch", # Hmmm, an unknown Arch-based... 68 | ) 69 | def test_get_local_distro_like_second(self, _, __, ___): 70 | """Test distribution matching from the `os-release`'s `ID_LIKE` option (second candidate)""" 71 | self.assertEqual(Distributions.get_local(), Distributions.ARCH) 72 | 73 | @patch( 74 | "archey.distributions.platform.system", 75 | return_value="Darwin", # Mostly used by our second run. 76 | ) 77 | @patch( 78 | "archey.distributions.distro.id", 79 | side_effect=[ 80 | "darwin", # First detection will succeed. 81 | "", # Second detection will fail. 82 | ], 83 | ) 84 | @patch("archey.distributions.distro.like", return_value="") # No `ID_LIKE` here. 85 | def test_darwin_detection(self, _, __, ___): 86 | """Test OS detection for Darwin""" 87 | # Detection based on `distro`. 88 | self.assertEqual(Distributions.get_local(), Distributions.DARWIN) 89 | 90 | # Detection based on `platform`. 91 | self.assertEqual(Distributions.get_local(), Distributions.DARWIN) 92 | 93 | @patch( 94 | "archey.distributions.distro.name", 95 | side_effect=[ 96 | "Debian GNU/Linux 10 (buster)", 97 | "", # Second call will (soft-)fail. 98 | ], 99 | ) 100 | def test_get_distro_name(self, _): 101 | """Very basic test cases for `get_distro_name` static method""" 102 | self.assertEqual(Distributions.get_distro_name(), "Debian GNU/Linux 10 (buster)") 103 | self.assertIsNone(Distributions.get_distro_name()) 104 | 105 | @patch( 106 | "archey.distributions.distro.os_release_attr", 107 | side_effect=[ 108 | "33;1", 109 | "", 110 | ], # Second call will (soft-)fail. 111 | ) 112 | def test_get_ansi_color(self, _): 113 | """Very basic test cases for `get_ansi_color` static method""" 114 | self.assertEqual(Distributions.get_ansi_color(), "33;1") 115 | self.assertIsNone(Distributions.get_ansi_color()) 116 | -------------------------------------------------------------------------------- /archey/test/test_archey_entry.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.entry`""" 2 | 3 | import typing 4 | import unittest 5 | from abc import ABC 6 | 7 | from archey.entry import Entry 8 | 9 | 10 | class _SimpleEntry(Entry): 11 | _PRETTY_NAME = "Simple Entry" 12 | 13 | def __init__(self, *args, **kwargs): 14 | super().__init__(*args, **kwargs) 15 | 16 | def output(self, output) -> None: 17 | """Reverse order!""" 18 | output.append((self.value, self.name)) 19 | 20 | 21 | class TestEntry(unittest.TestCase): 22 | """Simple test cases for our `Entry` abstract class""" 23 | 24 | def test_entry_itself(self): 25 | """Check `Entry`'s type and direct-instantiation failure""" 26 | self.assertTrue(issubclass(_SimpleEntry, ABC)) 27 | self.assertTrue(issubclass(_SimpleEntry, Entry)) 28 | self.assertRaises(TypeError, Entry) 29 | 30 | def test_entry_disabling(self): 31 | """Test `Entry` _disabling_""" 32 | simple_entry = _SimpleEntry() 33 | self.assertIsNotNone(simple_entry) 34 | 35 | simple_entry = _SimpleEntry(options={"disabled": True}) 36 | self.assertIsNone(simple_entry) 37 | 38 | simple_entry = _SimpleEntry(options={"disabled": False}) 39 | self.assertNotIn("disabled", simple_entry.options) 40 | 41 | def test_entry_usage(self): 42 | """Test `Entry` instantiation and parameters passing""" 43 | # No name passed as parameter, let's use internal defined "pretty name". 44 | simple_entry = _SimpleEntry() 45 | self.assertEqual(simple_entry.name, "Simple Entry") 46 | self.assertIsNone(simple_entry.value) 47 | self.assertFalse(simple_entry) 48 | 49 | # No `_PRETTY_NAME` is defined : proper fall-back on entry internal name. 50 | delattr(_SimpleEntry, "_PRETTY_NAME") 51 | self.assertEqual(_SimpleEntry().name, "_SimpleEntry") 52 | 53 | # A name is passed as parameter, it has to be chosen. 54 | simple_entry = _SimpleEntry("T", "est") 55 | self.assertEqual(simple_entry.name, "T") 56 | self.assertEqual(simple_entry.value, "est") 57 | self.assertTrue(simple_entry) 58 | 59 | def test_entry_output_overriding(self): 60 | """Check `Entry.output` public method overriding""" 61 | simple_entry = _SimpleEntry("is this", "ordered") 62 | output: typing.List[typing.Tuple[str, ...]] = [] 63 | simple_entry.output(output) 64 | self.assertListEqual(output, [("ordered", "is this")]) 65 | -------------------------------------------------------------------------------- /archey/test/test_archey_logos.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.logos`""" 2 | 3 | import pkgutil 4 | import unittest 5 | 6 | from archey import logos 7 | from archey.colors import Style 8 | from archey.distributions import Distributions 9 | from archey.logos import get_logo_width, lazy_load_logo_module 10 | 11 | 12 | class TestLogos(unittest.TestCase): 13 | """Simple tests checking logos consistency and utility function logic""" 14 | 15 | def test_distribution_logos_consistency(self): 16 | """ 17 | Verify each distribution identifier got a logo module. 18 | Verify each distribution logo module contains `LOGO` & `COLORS` ("truthy") attributes. 19 | Also check they got _consistent_ widths across their respective lines. 20 | Additionally verify they don't contain any (useless) empty line. 21 | 22 | Logo alternative "styles" are also checked. 23 | 24 | This test also indirectly checks `lazy_load_logo_module` behavior! 25 | """ 26 | distributions_identifiers = Distributions.get_identifiers() 27 | 28 | for i, logo_module_info in enumerate(pkgutil.iter_modules(logos.__path__), start=1): 29 | # Check each logo module name corresponds to a distribution identifier. 30 | self.assertIn( 31 | logo_module_info.name, 32 | distributions_identifiers, 33 | msg=f"No distribution identifier for [{logo_module_info.name}]", 34 | ) 35 | 36 | logo_module = lazy_load_logo_module(logo_module_info.name) 37 | 38 | # Check at least a default logo has been defined. 39 | self.assertTrue( 40 | getattr(logo_module, "LOGO", []), 41 | msg=f"[{logo_module_info.name}] logo module misses `LOGO` attribute", 42 | ) 43 | 44 | for logo_style in filter(lambda s: s.startswith("LOGO"), dir(logo_module)): 45 | parts = logo_style.partition("_") 46 | style_suffix = parts[1] + parts[2] 47 | 48 | self.assertTrue( 49 | getattr(logo_module, "COLORS" + style_suffix, []), 50 | msg=( 51 | f"[{logo_module_info.name} {parts[2] or 'default'} logo] module misses " 52 | f"`COLORS{style_suffix}` attribute" 53 | ), 54 | ) 55 | 56 | # Compute once and for all the number of defined colors for this logo. 57 | nb_colors = len(getattr(logo_module, "COLORS" + style_suffix)) 58 | 59 | # Make Archey compute the logo (effective) width. 60 | logo_width = get_logo_width(getattr(logo_module, "LOGO" + style_suffix), nb_colors) 61 | 62 | # Then, check that each logo line got the same effective width. 63 | for j, line in enumerate(getattr(logo_module, "LOGO" + style_suffix)[1:], start=1): 64 | # Here we gotta trick the `get_logo_width` call. 65 | # We actually pass each logo line as if it was a "complete" logo. 66 | line_width = get_logo_width([line], nb_colors) 67 | 68 | # Width check. 69 | self.assertEqual( 70 | line_width, 71 | logo_width, 72 | msg=( 73 | f"[{logo_module_info.name} {parts[2] or 'default'} logo] line index " 74 | f"{j}, got an unexpected width {line_width} (expected {logo_width})" 75 | ), 76 | ) 77 | 78 | # Non-empty line check. 79 | self.assertTrue( 80 | Style.remove_colors(line.format(c=[""] * nb_colors)).strip(), 81 | msg=f"[{logo_module_info.name}] line index {j}, got an useless empty line", 82 | ) 83 | 84 | # Finally, check each distributions identifier got a logo! 85 | # pylint: disable=undefined-loop-variable 86 | self.assertEqual( 87 | i, 88 | len(distributions_identifiers), 89 | msg=( 90 | f"[{logo_module_info.name}] Expected {len(distributions_identifiers)} " 91 | f"logo modules, got {i}" 92 | ), 93 | ) 94 | 95 | def test_get_logo_width(self): 96 | """Test `logos.get_logo_width` behavior""" 97 | self.assertEqual(get_logo_width([]), 0) 98 | self.assertEqual(get_logo_width(["{c[0]} {c[1]}"], 2), 3) 99 | self.assertEqual(get_logo_width(["{c[0]} {{ {c[1]}"]), 3) 100 | self.assertEqual( 101 | get_logo_width( 102 | [ 103 | "{c[0]} {c[1]}>>>>{c[2]}<<<<{c[3]}", 104 | "{c[0]} {c[1]}>>>>{c[2]}<<<<<{c[3]}", # Ignored from computation... 105 | ] 106 | ), 107 | 11, 108 | ) 109 | -------------------------------------------------------------------------------- /archey/test/test_archey_processes.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.processes`""" 2 | 3 | import unittest 4 | from unittest.mock import patch 5 | 6 | from archey.processes import Processes 7 | from archey.test import CustomAssertions 8 | 9 | 10 | # To avoid edge-case issues due to singleton, we automatically reset internal `_instances`. 11 | # This is done at the class-level. 12 | @patch.dict( 13 | "archey.singleton.Singleton._instances", 14 | clear=True, 15 | ) 16 | class TestProcesses(unittest.TestCase, CustomAssertions): 17 | """ 18 | Test cases for the `Processes` (singleton) class. 19 | To work around the singleton, we reset the internal `_instances` dictionary. 20 | This way, `check_output` can be mocked here. 21 | """ 22 | 23 | @patch( 24 | "archey.processes.check_output", 25 | return_value="""\ 26 | COMMAND 27 | what 28 | an 29 | awesome 30 | processes 31 | list 32 | you 33 | got 34 | there 35 | """, 36 | ) 37 | def test_ps_ok(self, check_output_mock): 38 | """Simple test with a plausible `ps` output""" 39 | # We'll create two `Processes` instances. 40 | processes_1 = Processes() 41 | _ = Processes() 42 | 43 | self.assertTupleEqual( 44 | processes_1.list, 45 | ( 46 | "what", 47 | "an", 48 | "awesome", 49 | "processes", 50 | "list", 51 | "you", 52 | "got", 53 | "there", 54 | ), 55 | ) 56 | self.assertEqual(processes_1.number, 8) 57 | 58 | # The class has been instantiated twice, but `check_output` has been called only once. 59 | self.assertTrue(check_output_mock.assert_called_once) 60 | 61 | @patch("archey.processes.check_output", side_effect=FileNotFoundError()) 62 | def test_ps_not_available(self, _): 63 | """Checks behavior when `ps` is not available""" 64 | self.assertTupleEmpty(Processes().list) 65 | 66 | 67 | if __name__ == "__main__": 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /archey/test/test_archey_singleton.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.singleton`""" 2 | 3 | import unittest 4 | from abc import ABCMeta 5 | 6 | from archey.singleton import Singleton 7 | 8 | 9 | class _SimpleCounter(metaclass=Singleton): 10 | def __init__(self): 11 | self._counter = 0 12 | self.an_object = {} 13 | 14 | def increment(self): 15 | """Adds `1` to the internal counter""" 16 | self._counter += 1 17 | 18 | def get(self): 19 | """Returns the internal counter value""" 20 | return self._counter 21 | 22 | 23 | class TestSingleton(unittest.TestCase): 24 | """Test cases for our `Singleton` meta-class""" 25 | 26 | def test_singleton_itself(self): 27 | """Verifies `Singleton` hierarchy""" 28 | self.assertTrue(issubclass(Singleton, type)) 29 | self.assertTrue(issubclass(Singleton, ABCMeta)) 30 | self.assertTrue(isinstance(_SimpleCounter, Singleton)) 31 | 32 | def test_singleton_subclass_instances(self): 33 | """Simple tests for `Singleton` sub-class instantiation results""" 34 | counter_1 = _SimpleCounter() 35 | counter_2 = _SimpleCounter() 36 | self.assertIs(counter_1, counter_2) 37 | self.assertEqual(counter_1, counter_2) 38 | 39 | # Modifies `counter_1`'s internal value. 40 | counter_1.increment() 41 | # These values should be equals. 42 | self.assertEqual(counter_1.get(), counter_2.get()) 43 | 44 | # Internal objects are also the same. 45 | self.assertIs(counter_1.an_object, counter_2.an_object) 46 | 47 | def test_singleton_instantiations(self): 48 | """Simple test for `Singleton` (direct) instantiation""" 49 | self.assertRaises(TypeError, Singleton) 50 | 51 | 52 | if __name__ == "__main__": 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /archey/test/test_archey_utility.py: -------------------------------------------------------------------------------- 1 | """Test module for `archey.configuration`""" 2 | 3 | import unittest 4 | 5 | from archey.utility import Utility 6 | 7 | 8 | class TestUtility(unittest.TestCase): 9 | """ 10 | Simple test cases to check the behavior of `Utility` singleton utility class. 11 | Values will be manually set in the tests below. 12 | """ 13 | 14 | def test_update_recursive(self): 15 | """Test for the `update_recursive` class method""" 16 | test_dict = { 17 | "allow_overriding": True, 18 | "suppress_warnings": False, 19 | "default_strings": { 20 | "no_address": "No Address", 21 | "not_detected": "Not detected", 22 | }, 23 | "colors_palette": { 24 | "use_unicode": False, 25 | }, 26 | "ip_settings": { 27 | "lan_ip_max_count": 2, 28 | }, 29 | "temperature": { 30 | "use_fahrenheit": False, 31 | }, 32 | } 33 | 34 | # We change existing values, add new ones, and omit some others. 35 | Utility.update_recursive( 36 | test_dict, 37 | { 38 | "suppress_warnings": True, 39 | "colors_palette": { 40 | "use_unicode": False, 41 | }, 42 | "default_strings": { 43 | "no_address": "\xde\xad \xbe\xef", 44 | "not_detected": "Not detected", 45 | "virtual_environment": "Virtual Environment", 46 | }, 47 | "temperature": { 48 | "a_weird_new_dict": [ 49 | None, 50 | "l33t", 51 | { 52 | "really": "one_more_?", 53 | }, 54 | ], 55 | }, 56 | }, 57 | ) 58 | 59 | self.assertDictEqual( 60 | test_dict, 61 | { 62 | "allow_overriding": True, 63 | "suppress_warnings": True, 64 | "colors_palette": { 65 | "use_unicode": False, 66 | }, 67 | "default_strings": { 68 | "no_address": "\xde\xad \xbe\xef", 69 | "not_detected": "Not detected", 70 | "virtual_environment": "Virtual Environment", 71 | }, 72 | "ip_settings": { 73 | "lan_ip_max_count": 2, 74 | }, 75 | "temperature": { 76 | "use_fahrenheit": False, 77 | "a_weird_new_dict": [ 78 | None, 79 | "l33t", 80 | { 81 | "really": "one_more_?", 82 | }, 83 | ], 84 | }, 85 | }, 86 | ) 87 | 88 | def test_version_to_semver_segments(self): 89 | """Check `version_to_semver_segments` implementation""" 90 | self.assertTupleEqual(Utility.version_to_semver_segments("1.2.3"), (1, 2, 3)) 91 | self.assertTupleEqual(Utility.version_to_semver_segments("1.2.3.4-beta5"), (1, 2, 3, 4)) 92 | self.assertTupleEqual(Utility.version_to_semver_segments("1"), (1,)) 93 | -------------------------------------------------------------------------------- /archey/utility.py: -------------------------------------------------------------------------------- 1 | """Archey Utility module""" 2 | 3 | from typing import Tuple 4 | 5 | from archey.singleton import Singleton 6 | 7 | 8 | class Utility(metaclass=Singleton): 9 | """Miscellaneous logic used in Archey internals""" 10 | 11 | @classmethod 12 | def update_recursive(cls, old_dict: dict, new_dict: dict) -> None: 13 | """ 14 | A method for recursively merging dictionaries as `dict.update()` is not able to do this. 15 | Original snippet taken from here : 16 | """ 17 | for key, value in new_dict.items(): 18 | if key in old_dict and isinstance(old_dict[key], dict) and isinstance(value, dict): 19 | cls.update_recursive(old_dict[key], value) 20 | else: 21 | old_dict[key] = value 22 | 23 | @staticmethod 24 | def version_to_semver_segments(version: str) -> Tuple[int, ...]: 25 | """Transforms string `version` to a tuple containing SemVer segments""" 26 | return tuple(map(int, version.partition("-")[0].split("."))) 27 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "allow_overriding": true, 3 | "parallel_loading": true, 4 | "suppress_warnings": false, 5 | "entries_color": "", 6 | "honor_ansi_color": true, 7 | "logo_style": "", 8 | "hide_undetected": false, 9 | "entries_icon": false, 10 | "entries": [ 11 | { "type": "User" }, 12 | { "type": "Hostname" }, 13 | { "type": "Model" }, 14 | { "type": "Distro" }, 15 | { 16 | "type": "Kernel", 17 | "check_version": false 18 | }, 19 | { "type": "Uptime" }, 20 | { 21 | "type": "LoadAverage", 22 | "decimal_places": 2, 23 | "warning_threshold": 1.0, 24 | "danger_threshold": 2.0 25 | }, 26 | { "type": "Processes" }, 27 | { "type": "WindowManager" }, 28 | { "type": "DesktopEnvironment" }, 29 | { "type": "Shell" }, 30 | { 31 | "type": "Terminal", 32 | "use_unicode": true 33 | }, 34 | { 35 | "type": "Packages", 36 | "combine_total": false, 37 | "one_line": true, 38 | "show_zeros": false 39 | }, 40 | { 41 | "type": "Temperature", 42 | "char_before_unit": " ", 43 | "sensors_chipsets": [], 44 | "sensors_excluded_subfeatures": [], 45 | "use_fahrenheit": false 46 | }, 47 | { 48 | "type": "CPU", 49 | "one_line": false, 50 | "show_cores": true 51 | }, 52 | { 53 | "type": "GPU", 54 | "one_line": false, 55 | "max_count": 2 56 | }, 57 | { 58 | "type": "RAM", 59 | "warning_use_percent": 33.3, 60 | "danger_use_percent": 66.7 61 | }, 62 | { 63 | "type": "Disk", 64 | "show_filesystems": ["local"], 65 | "combine_total": true, 66 | "disk_labels": null, 67 | "hide_entry_name": null, 68 | "warning_use_percent": 50, 69 | "danger_use_percent": 75 70 | }, 71 | { 72 | "type": "LAN_IP", 73 | "one_line": true, 74 | "max_count": 2, 75 | "show_global": false, 76 | "show_link_local": true, 77 | "ipv6_support": true 78 | }, 79 | { 80 | "type": "WAN_IP", 81 | "one_line": true, 82 | "ipv4": { 83 | "dns_query": "myip.opendns.com", 84 | "dns_resolver": "resolver1.opendns.com", 85 | "dns_timeout": 1, 86 | "http_url": "https://4.ident.me/", 87 | "http_timeout": 1 88 | }, 89 | "ipv6": { 90 | "dns_query": "myip.opendns.com", 91 | "dns_resolver": "resolver1.opendns.com", 92 | "dns_timeout": 1, 93 | "http_url": "https://6.ident.me/", 94 | "http_timeout": 1 95 | } 96 | } 97 | ], 98 | "default_strings": { 99 | "latest": "latest", 100 | "available": "available", 101 | "no_address": "No Address", 102 | "not_detected": "Not detected", 103 | "virtual_environment": "Virtual Environment" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HorlogeSkynet/archey4/f9e98a281bd6832fa7f3abf353dae16853d01f02/dist/.gitkeep -------------------------------------------------------------------------------- /packaging/after_install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | 6 | # Handles AppArmor profile (see dh_apparmor). 7 | APP_PROFILE="/etc/apparmor.d/usr.bin.archey4" 8 | if [ -f "$APP_PROFILE" ]; then 9 | # Add the local/ include 10 | LOCAL_APP_PROFILE="/etc/apparmor.d/local/usr.bin.archey4" 11 | 12 | test -e "$LOCAL_APP_PROFILE" || { 13 | mkdir -p "$(dirname "$LOCAL_APP_PROFILE")" 14 | install --mode 644 /dev/null "$LOCAL_APP_PROFILE" 15 | } 16 | 17 | # Reload the profile, including any abstraction updates 18 | if aa-enabled --quiet 2>/dev/null; then 19 | apparmor_parser -r -T -W "$APP_PROFILE" || true 20 | fi 21 | fi 22 | 23 | # Creates a symbolic link providing `archey4` command alias. 24 | ln -s -f /usr/bin/archey /usr/bin/archey4 25 | -------------------------------------------------------------------------------- /packaging/after_remove: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | 6 | # Handles AppArmor profile (see dh_apparmor). 7 | if ! [ -e /etc/apparmor.d/usr.bin.archey4 ] ; then 8 | rm -f /etc/apparmor.d/disable/usr.bin.archey4 || true 9 | rm -f /etc/apparmor.d/force-complain/usr.bin.archey4 || true 10 | rm -f /etc/apparmor.d/local/usr.bin.archey4 || true 11 | rm -f /var/cache/apparmor/*/usr.bin.archey4 || true 12 | rmdir /etc/apparmor.d/disable 2>/dev/null || true 13 | rmdir /etc/apparmor.d/local 2>/dev/null || true 14 | rmdir /etc/apparmor.d 2>/dev/null || true 15 | fi 16 | -------------------------------------------------------------------------------- /packaging/before_remove: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | 6 | # Removes symbolic link created by `after_install`. 7 | if [ -L /usr/bin/archey4 ]; then 8 | rm /usr/bin/archey4 9 | fi 10 | 11 | 12 | # Removes any byte-code file that may have been generated by Archey. 13 | # Wild-cards are being used to match all supported distribution layouts. 14 | find /usr/lib/python3*/*-packages/archey \ 15 | -type d \ 16 | -name __pycache__ \ 17 | -exec \ 18 | rm -r {} + 19 | 20 | # Removes the AppArmor definition from kernel. 21 | APP_PROFILE="/etc/apparmor.d/usr.bin.archey4" 22 | if aa-enabled --quiet 2>/dev/null; then 23 | apparmor_parser -R "$APP_PROFILE" || true 24 | fi 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Pylint 2 | [tool.pylint.MASTER] 3 | ## Required hook as we use absolute imports within the code. 4 | init-hook = 'import sys; sys.path.append("archey/")' 5 | 6 | ## C sources of the `netifaces` module won't be available. 7 | ## Let's ignore it during linting please. 8 | extension-pkg-whitelist = "netifaces" 9 | 10 | ## Automatically detects the number of CPU available to use. 11 | jobs = 0 12 | 13 | ## For the time being, disable `similarities` checker due to false positives across tests modules. 14 | ## See PyCQA/pylint#214. 15 | disable = "similarities" 16 | 17 | ## Additional plugins to check the code base against. 18 | load-plugins = [ 19 | "pylint.extensions.check_elif", 20 | "pylint.extensions.redefined_variable_type", 21 | "pylint.extensions.overlapping_exceptions", 22 | "pylint.extensions.empty_comment", 23 | "pylint.extensions.while_used", 24 | "pylint_secure_coding_standard", 25 | ] 26 | 27 | [tool.pylint.DESIGN] 28 | ## For entries classes, we (often) only use the `__init__` magic method. 29 | min-public-methods = 0 30 | 31 | # Mypy 32 | [tool.mypy] 33 | check_untyped_defs = true 34 | 35 | [[tool.mypy.overrides]] 36 | ## netifaces is not typed and does not provide any stub 37 | module = "netifaces" 38 | ignore_missing_imports = true 39 | 40 | # Ruff 41 | [tool.ruff] 42 | line-length = 100 43 | 44 | # Black 45 | [tool.black] 46 | line-length = 100 47 | target-version = ["py36"] 48 | 49 | # isort 50 | [tool.isort] 51 | profile = "black" 52 | line_length = 100 53 | py_version = 36 54 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | This is the Archey 4's `setup.py` file, allowing us to distribute it as a package... 5 | ... with cool meta-data. 6 | """ 7 | 8 | import os 9 | 10 | from setuptools import find_packages, setup 11 | 12 | from archey._version import __version__ 13 | 14 | 15 | setup( 16 | name='archey4', 17 | version=__version__, 18 | description='Archey is a simple system information tool written in Python', 19 | keywords='archey python3 linux system-information monitoring screenshot', 20 | url='https://github.com/HorlogeSkynet/archey4', 21 | author='Samuel Forestier', # Not alone 22 | author_email='samuel+archey@forestier.app', 23 | license='GPLv3', 24 | packages=find_packages(exclude=['archey.test*']), 25 | package_data={'archey': ['py.typed']}, 26 | python_requires='>=3.6', 27 | install_requires=[ 28 | 'distro~=1.3', 29 | 'netifaces~=0.10' 30 | ], 31 | entry_points={ 32 | 'console_scripts': [ 33 | 'archey = archey.__main__:main' 34 | ] 35 | }, 36 | long_description="""\ 37 | Archey4 is a **maintained** fork of the original Archey Linux system tool. 38 | The original Archey program had been written by Melik Manukyan in 2009, and quickly abandoned in 2011. 39 | At first, it only supported Arch Linux distribution, further support had been added afterwards. 40 | Many forks popped in the wild due to inactivity, but this one attends since 2017 to succeed where the others failed: 41 | Remain *maintained*, *community-driven* and *highly-compatible* with yesterday's and today's systems.\ 42 | """, 43 | long_description_content_type='text/x-rst', 44 | data_files=[ 45 | # By filtering on `os.path.exists`, install should succeed even when 46 | # the compressed manual page is not available (iterable would be empty). 47 | ('share/man/man1', filter(os.path.exists, ['dist/archey.1.gz'])), 48 | ('share/doc/archey4', ['CHANGELOG.md', 'COPYRIGHT.md', 'README.md']) 49 | ], 50 | zip_safe=False, 51 | classifiers=[ 52 | 'Development Status :: 5 - Production/Stable', 53 | 'Environment :: Console', 54 | 'Intended Audience :: Developers', 55 | 'Intended Audience :: Information Technology', 56 | 'Intended Audience :: System Administrators', 57 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 58 | 'Natural Language :: English', 59 | 'Operating System :: Android', 60 | 'Operating System :: MacOS', 61 | 'Operating System :: POSIX :: BSD', 62 | 'Operating System :: POSIX :: Linux', 63 | 'Programming Language :: Python :: 3', 64 | 'Programming Language :: Python :: 3.6', 65 | 'Programming Language :: Python :: 3.7', 66 | 'Programming Language :: Python :: 3.8', 67 | 'Programming Language :: Python :: 3.9', 68 | 'Programming Language :: Python :: 3.10', 69 | 'Programming Language :: Python :: 3.11', 70 | 'Programming Language :: Python :: 3.12', 71 | 'Programming Language :: Python :: 3.13', 72 | 'Programming Language :: Python :: 3.14', 73 | 'Programming Language :: Python :: 3 :: Only', 74 | 'Topic :: System' 75 | ] 76 | ) 77 | --------------------------------------------------------------------------------