blib/blib/enums.py

333 lines
5.8 KiB
Python

from __future__ import annotations
import enum
import os
import re
from typing import Any
try:
from typing import Self
except ImportError:
from typing_extensions import Self
HTTP_REASON_REGEX = re.compile('[A-Z][^A-Z]*')
MULTIPLIER = {
"B": 1,
"KiB": 1024,
"MiB": 1024 ** 2,
"GiB": 1024 ** 3,
"TiB": 1024 ** 4,
"PiB": 1024 ** 5,
"EiB": 1024 ** 6,
"ZiB": 1024 ** 7,
"YiB": 1024 ** 8,
"KB": 1000,
"MB": 1000 ** 2,
"GB": 1000 ** 3,
"TB": 1000 ** 4,
"PB": 1000 ** 5,
"EB": 1000 ** 6,
"ZB": 1000 ** 7,
"YB": 1000 ** 8
}
XDG_DIR = {
"XDG_CACHE_HOME": "~/.cache",
"XDG_CONFIG_HOME": "~/.config",
"XDG_DATA_HOME": "~/.local/share",
"XDG_RUNTIME_DIR": f"/run/user/{os.getuid()}",
"XDG_DATA_STATE": "~/.local/state"
}
class Enum(enum.Enum):
":class:`enum.Enum` with a ``parse`` method"
@classmethod
def from_index(cls: type[Self], index: int) -> Self:
"""
Get the enum item at the specified index
:param index: Index of the enum item to get
:raises IndexError: If the index is out of range
"""
if index < 0:
index = len(cls) + index
for idx, value in enumerate(cls):
if idx == index:
return value
raise IndexError(index)
@classmethod
def parse(cls: type[Self], data: Any) -> Self:
"""
Get an enum item by name or value
:param data: Name or value
:raises AttributeError: If an item could not be found
"""
if isinstance(data, cls):
return data
try:
return cls[data]
except KeyError:
pass
try:
return cls(data)
except ValueError:
pass
if isinstance(data, str):
for item in cls:
if issubclass(cls, StrEnum) and data.lower() == item.value.lower():
return item
if data.lower() == item.name.lower():
return item
raise AttributeError(f'Invalid enum property for {cls.__name__}: {data}')
class StrEnum(str, Enum):
"Enum where items can be treated like a :class:`str`"
def __str__(self) -> str:
return self.value # type: ignore[no-any-return]
class IntEnum(enum.IntEnum, Enum):
"Enum where items can be treated like an :class:`int`"
class IntFlagEnum(enum.IntFlag, Enum): # type: ignore[misc]
":class:`IntEnum` with items that can be used like flags"
class FileSizeUnit(StrEnum):
"Unit identifier for various file sizes"
BYTE = 'B'
KIBIBYTE = 'KiB'
MEBIBYTE = 'MiB'
GIBIBYTE = 'GiB'
TEBIBYTE = 'TiB'
PEBIBYTE = 'PiB'
EXBIBYTE = 'EiB'
ZEBIBYTE = 'ZiB'
YOBIBYTE = 'YiB'
KILOBYTE = 'KB'
MEGABYTE = 'MB'
GIGABYTE = 'GB'
TERABYTE = 'TB'
PETABYTE = 'PB'
EXABYTE = 'EB'
ZETTABYTE = 'ZB'
YOTTABYTE = 'YB'
B = BYTE
K = KIBIBYTE
M = MEBIBYTE
G = GIBIBYTE
T = TEBIBYTE
P = PEBIBYTE
E = EXBIBYTE
Z = ZEBIBYTE
Y = YOBIBYTE
@property
def multiplier(self) -> int:
"Get the multiplier for the unit"
return MULTIPLIER[str(self)]
def multiply(self, size: int | float) -> int | float:
"""
Multiply a file size to get the size in bytes
:param size: File size to be multiplied
"""
return self.multiplier * size
class FileType(Enum):
"File type"
DIR = enum.auto()
FILE = enum.auto()
LINK = enum.auto()
UNKNOWN = enum.auto()
class HttpMethod(StrEnum):
"Valid HTTP methods"
CONNECT = "connect"
DELETE = "delete"
GET = "get"
HEAD = "head"
OPTIONS = "options"
PATCH = "patch"
POST = "post"
PUT = "put"
TRACE = "trace"
class HttpStatus(IntEnum):
"HTTP status codes"
Continue = 100
SwitchingProtocols = 101
Processing = 102
EarlyHints = 103
Ok = 200
Created = 201
Accepted = 202
NonAuthoritativeInformation = 203
NoContent = 204
Resetcontent = 205
PartialContent = 206
MultiStatus = 207
AlreadyReported = 208
ImUsed = 226
MultipleChoices = 300
MovedPermanently = 301
Found = 302
SeeOther = 303
NotModified = 304
UseProxy = 305
TemporaryRedirect = 307
PermanentRedirect = 308
BadRequest = 400
Unauthorized = 401
PaymentRequired = 402
Forbidden = 403
NotFound = 404
MethodNotAllowed = 405
NotAcceptable = 406
ProxyAuthenticationRequired = 407
RequestTimeout = 408
Conflict = 409
Gone = 410
LengthRequired = 411
PreconditionFailed = 412
RequestEntityTooLarge = 413
RequestUriTooLong = 414
UnsupportedMediaType = 415
RequestRangeNotSatisfiable = 416
ExpectationFailed = 417
IAmATeapot = 418
TooHighToHandleYourShit = 420
MisdirectedRequest = 421
UnprocessableEntity = 422
Locked = 423
FailedDependency = 424
TooEarly = 425
UpgradeRequired = 426
PreconditionRequired = 428
TooManyRequests = 429
RequestHeaderFieldsTooLarge = 431
UnavailableForLegalReasons = 451
InternalServerError = 500
NotImplemented = 501
BadGateway = 502
ServiceUnavailable = 503
GatewayTimeout = 504
HttpVersionNotSupported = 505
VariantAlsoNegotiates = 506
InsufficientStorage = 507
LoopDetected = 508
NotExtended = 510
NetworkAuthenticationRequired = 511
@property
def reason(self) -> str:
"The text associated with the code"
return " ".join(HTTP_REASON_REGEX.findall(self.name))
class ProtocolPort(IntEnum):
"Protocol names and their associated default port"
FILE = 0
FTP = 21
SSH = 22
TELNET = 23
SMTP = 25
WHOIS = 43
DNS = 53
TFTP = 69
GOPHER = 70
HTTP = 80
WS = 80
NTP = 123
XDMCP = 177
IRC = 194
IMAP = 220
HTTPS = 443
WSS = 443
SMB = 445
RTSP = 554
SMTPS = 587
IPP = 631
DOT = 853
RSYNC = 873
FTPS = 990
IMAPS = 993
NFS = 1023
GEMINI = 1965
TITAN = GEMINI
# various servers
MYSQL = 3306
PULSEAUDIO = 4713
RTP = 5004
RTCP = 5005
POSTGRES = 5432
MPD = 6600
IRCS = 6697
# game servers
TERRARIA = 7777
STARBOUND = 21025
MINECRAFT = 25565
MUMBLE = 64738
class XdgDir(StrEnum):
"XDG directories and their associated environmental variables"
CACHE = "XDG_CACHE_HOME"
CONFIG = "XDG_CONFIG_HOME"
DATA = "XDG_DATA_HOME"
RUNTIME = "XDG_RUNTIME_DIR"
STATE = "XDG_DATA_STATE"
@property
def path(self) -> str:
"Get the directory associated with the enum item"
return os.environ.get(self.value, os.path.expanduser(XDG_DIR[self.value]))