import textwrap import time from typing import Union from .common import LogLevel ## log message constant format template __LOG_TMPL = "[{DATE}] [{0}] {TYPE} " ## string time format (ISO: 8601, long-form) __TIMESTAMP_FMT = "%Y-%m-%dT%H:%M:%S%z" class Colours: NORMAL = '\033[0m' RED = '\033[91m' GREEN = '\033[92m' YELLOW = '\033[93m' BLUE = '\033[94m' def loglevel_as_str(level: Union[LogLevel, int]) -> str: """Convert an integer or enum value into its appropriate enum attribute name. `@Params`: level - `LogLevel | int` `Returns`: LogLevel enum literal attribute name - `str` """ ## perform a lookup of each attribute of LogLevel and its associate ## value, and search for the attribute that has the matching level ## value passed to the function. _attrs = [_dir for _dir in dir(LogLevel) \ if not _dir.startswith('__')] # list of all attributes of LogLevel for _ in _attrs: # iterate over all attributes defined if getattr(LogLevel, _) == level: # if the level matches return "{0: <5}".format(_) # return the attribute name return " " * 5 def log_as_col(level: Union[int, LogLevel]) -> Colours: """Convert an integer or enum value into an associated ANSI escape code terminal colour sequence. Depending on log level severity, an associated colour is returned to give STDOUT text a distinct separation of colour from regular STDOUT text. `@Params`: level - `LogLevel | int` `@Return`: Enum of `Colours` """ ## perform a colour lookup based on level number if level == LogLevel.DEBUG: return Colours.BLUE elif level == LogLevel.WARN: return Colours.YELLOW elif level == LogLevel.ERROR: return Colours.RED elif level == LogLevel.PASS: return Colours.GREEN else: return Colours.NORMAL def gen_log_header(_type: Union[LogLevel, int, str]) -> str: """Generate a header string for use of standardising log outputs. The header is defined using the following standard header: ```plaintext "[YYYY-MM-DDTHH:MM:SS+OFFSET] [{CALLER}] {0}" ``` The first column defines the Date-Time following the ISO 8601 (long) standard timestamp format, whereby `T` is the separator between Date and Time. The `+OFFSET` is the number of hours ahead/behind UTC, the currently timezone set by the PC. This is achieved using the following string to time format: `%Y-%m-%dT%H:%M:%S%z`. The second column is reversed during the return value of the string. This column is used to allow for a stacktrace to be attached to the log message. The third column defines the level of the log message. This will be five blank space characters if a level is passed outside of the defined scope of logging levels. The fourth column is reserved during the return value of the string. It represents the body of the message to be logged. `@Params`: _type - `LogLevel | int | str` `@Return`: Formattable string - `str` """ ## if the _type represents a LogLevel value, convert it to str if not isinstance(_type, str): _type = loglevel_as_str(_type) ## return newly formatted log message header return __LOG_TMPL.format('{CALLER}', DATE=\ time.strftime(__TIMESTAMP_FMT, time.localtime()), TYPE=_type) + "{0}" def wrap(value: str, *, width: int = 120, tb_size: int = 4) -> str: """Return a hard-wrapped string defined by a fixed width. Wrapped text is separated by a `\\n` character and a fixed width of 120 characters. Each wrap is appended with a tab size of 4 characters at the start of each line-wrap. `@Params`: value - `str`, width = 120 - `int`, tb_size = 4 - `int` `@Return`: `str` """ return "\n".join(textwrap.wrap(value, width=width, tabsize=tb_size))