archey4/archey/distributions.py
2021-07-11 15:30:15 +02:00

145 lines
5.3 KiB
Python

"""
Distributions enumeration module.
Operating Systems detection logic.
Interface to `os-release` (through `distro` module).
"""
import os
import platform
import sys
from enum import Enum
from functools import lru_cache
from typing import List, Optional
import distro
class Distributions(Enum):
"""
This enumeration lists supported operating systems (keys).
Values contain their respective `distro` identifier.
See <https://distro.readthedocs.io/en/latest/#distro.id>.
"""
ALPINE = 'alpine'
ANDROID = 'android'
ARCH = 'arch'
BUNSENLABS = 'bunsenlabs'
CENTOS = 'centos'
CRUNCHBANG = 'crunchbang'
DARWIN = 'darwin'
DEBIAN = 'debian'
DEVUAN = 'devuan'
ELEMENTARY = 'elementary'
FEDORA = 'fedora'
FREEBSD = 'freebsd'
GENTOO = 'gentoo'
KALI = 'kali'
MANJARO = 'manjaro'
NETBSD = 'netbsd'
NIXOS = 'nixos'
LINUX = 'linux'
LINUXMINT = 'linuxmint'
OPENBSD = 'openbsd'
OPENSUSE = 'opensuse'
POP = 'pop'
PARABOLA = 'parabola'
RASPBIAN = 'raspbian'
RHEL = 'rhel'
SLACKWARE = 'slackware'
UBUNTU = 'ubuntu'
WINDOWS = 'windows'
@staticmethod
def get_identifiers() -> List[str]:
"""Simple getter returning current supported distributions identifiers"""
return [d.value for d in Distributions.__members__.values()]
@staticmethod
@lru_cache(maxsize=None) # Python < 3.9, `functools.cache` is not yet available.
def get_local() -> 'Distributions':
"""Entry point of Archey distribution detection logic"""
distribution = Distributions._vendor_detection()
# In case nothing got detected the "regular" way...
if not distribution:
# Are we running on Darwin (somehow not previously detected by `distro`) ?
if platform.system() == 'Darwin':
return Distributions.DARWIN
# Android systems are currently not being handled by `distro`.
# At first, we imitate the Python standard library, by checking whether CPython
# has been built for Android.
# See <https://github.com/python/cpython/search?l=Python&q=getandroidapilevel>
# As a fallback, we mimic Neofetch behavior, by relying on the file-system.
# See <https://github.com/nir0s/distro/issues/253>
if hasattr(sys, 'getandroidapilevel') \
or (os.path.isdir('/system/app') and os.path.isdir('/system/priv-app')):
return Distributions.ANDROID
# If nothing of the above matched, fall-back on the Linux logo.
return Distributions.LINUX
# Below are brain-dead cases for distributions not properly handled by `distro`.
# One _may_ want to add its own logic to add support for such undetectable systems.
if distribution == Distributions.DEBIAN:
# CrunchBang is tagged as _regular_ Debian by `distro`.
# Below conditions are here to work-around this issue.
# First condition : CrunchBang-Linux and CrunchBang-Monara.
# Second condition : CrunchBang++ (CBPP).
if os.path.isfile('/etc/lsb-release-crunchbang') \
or os.path.isfile('/usr/bin/cbpp-exit'):
return Distributions.CRUNCHBANG
elif distribution == Distributions.UBUNTU:
# Older Pop!_OS releases (< 20.*) didn't ship their own `ID` (from `os-release`).
# Thus, they are detected as "regular" Ubuntu distributions.
# We may here rely on their `NAME` (from `os-release`), which is sufficient.
if Distributions.get_distro_name(pretty=False) == 'Pop!_OS':
return Distributions.POP
return distribution
@staticmethod
def _vendor_detection() -> Optional['Distributions']:
"""Main distribution detection logic, relying on `distro`, handling _common_ cases"""
# Are we running on Windows ?
if platform.system() == 'Windows':
return Distributions.WINDOWS
# Is it a Windows Sub-system Linux (WSL) distribution ?
# If so, kernel release identifier should keep a trace of it.
if 'microsoft' in platform.release().lower():
return Distributions.WINDOWS
# Is `ID` (from `os-release`) well-known and supported ?
try:
return Distributions(distro.id())
except ValueError:
pass
# Is any of `ID_LIKE` (from `os-release`) well-known and supported ?
# See <https://www.freedesktop.org/software/systemd/man/os-release.html#ID_LIKE=>.
for id_like in distro.like().split(' '):
try:
return Distributions(id_like)
except ValueError:
pass
# Nothing of the above matched, let's return `None` and let the caller handle it.
return None
@staticmethod
def get_distro_name(pretty: bool = True) -> Optional[str]:
"""Simple wrapper to `distro` to return the current distribution _pretty_ name"""
return distro.name(pretty=pretty) or None
@staticmethod
def get_ansi_color() -> Optional[str]:
"""
Simple wrapper to `distro` to return the distribution preferred ANSI color.
See <https://www.freedesktop.org/software/systemd/man/os-release.html#ANSI_COLOR=>.
"""
return distro.os_release_attr('ansi_color') or None