| | import datetime |
| | import functools |
| | import inspect |
| | import logging |
| | import os |
| | import shutil |
| | import subprocess |
| | from termcolor import colored |
| |
|
| |
|
| | def add_fillers(text, filler="=", fill_side="both"): |
| | terminal_width = shutil.get_terminal_size().columns |
| | text = text.strip() |
| | text_width = len(text) |
| | if text_width >= terminal_width: |
| | return text |
| |
|
| | if fill_side[0].lower() == "b": |
| | leading_fill_str = filler * ((terminal_width - text_width) // 2 - 1) + " " |
| | trailing_fill_str = " " + filler * ( |
| | terminal_width - text_width - len(leading_fill_str) - 1 |
| | ) |
| | elif fill_side[0].lower() == "l": |
| | leading_fill_str = filler * (terminal_width - text_width - 1) + " " |
| | trailing_fill_str = "" |
| | elif fill_side[0].lower() == "r": |
| | leading_fill_str = "" |
| | trailing_fill_str = " " + filler * (terminal_width - text_width - 1) |
| | else: |
| | raise ValueError("Invalid fill_side") |
| |
|
| | filled_str = f"{leading_fill_str}{text}{trailing_fill_str}" |
| | return filled_str |
| |
|
| |
|
| | class OSLogger(logging.Logger): |
| | LOG_METHODS = { |
| | "err": ("error", "red"), |
| | "warn": ("warning", "light_red"), |
| | "note": ("info", "light_magenta"), |
| | "mesg": ("info", "light_cyan"), |
| | "file": ("info", "light_blue"), |
| | "line": ("info", "white"), |
| | "success": ("info", "light_green"), |
| | "fail": ("info", "light_red"), |
| | "back": ("debug", "light_cyan"), |
| | } |
| | INDENT_METHODS = [ |
| | "indent", |
| | "set_indent", |
| | "reset_indent", |
| | "store_indent", |
| | "restore_indent", |
| | "log_indent", |
| | ] |
| | LEVEL_METHODS = [ |
| | "set_level", |
| | "store_level", |
| | "restore_level", |
| | "quiet", |
| | "enter_quiet", |
| | "exit_quiet", |
| | ] |
| | LEVEL_NAMES = { |
| | "critical": logging.CRITICAL, |
| | "error": logging.ERROR, |
| | "warning": logging.WARNING, |
| | "info": logging.INFO, |
| | "debug": logging.DEBUG, |
| | } |
| |
|
| | def __init__(self, name=None, prefix=False): |
| | if not name: |
| | frame = inspect.stack()[1] |
| | module = inspect.getmodule(frame[0]) |
| | name = module.__name__ |
| |
|
| | super().__init__(name) |
| | self.setLevel(logging.INFO) |
| |
|
| | if prefix: |
| | formatter_prefix = "[%(asctime)s] - [%(name)s] - [%(levelname)s]\n" |
| | else: |
| | formatter_prefix = "" |
| |
|
| | self.formatter = logging.Formatter(formatter_prefix + "%(message)s") |
| |
|
| | stream_handler = logging.StreamHandler() |
| | stream_handler.setLevel(logging.INFO) |
| | stream_handler.setFormatter(self.formatter) |
| | self.addHandler(stream_handler) |
| |
|
| | self.log_indent = 0 |
| | self.log_indents = [] |
| |
|
| | self.log_level = "info" |
| | self.log_levels = [] |
| |
|
| | def indent(self, indent=2): |
| | self.log_indent += indent |
| |
|
| | def set_indent(self, indent=2): |
| | self.log_indent = indent |
| |
|
| | def reset_indent(self): |
| | self.log_indent = 0 |
| |
|
| | def store_indent(self): |
| | self.log_indents.append(self.log_indent) |
| |
|
| | def restore_indent(self): |
| | self.log_indent = self.log_indents.pop(-1) |
| |
|
| | def set_level(self, level): |
| | self.log_level = level |
| | self.setLevel(self.LEVEL_NAMES[level]) |
| |
|
| | def store_level(self): |
| | self.log_levels.append(self.log_level) |
| |
|
| | def restore_level(self): |
| | self.log_level = self.log_levels.pop(-1) |
| | self.set_level(self.log_level) |
| |
|
| | def quiet(self): |
| | self.set_level("critical") |
| |
|
| | def enter_quiet(self, quiet=False): |
| | if quiet: |
| | self.store_level() |
| | self.quiet() |
| |
|
| | def exit_quiet(self, quiet=False): |
| | if quiet: |
| | self.restore_level() |
| |
|
| | def log( |
| | self, |
| | level, |
| | color, |
| | msg, |
| | indent=0, |
| | fill=False, |
| | fill_side="both", |
| | end="\n", |
| | *args, |
| | **kwargs, |
| | ): |
| | if type(msg) == str: |
| | msg_str = msg |
| | else: |
| | msg_str = repr(msg) |
| | quotes = ["'", '"'] |
| | if msg_str[0] in quotes and msg_str[-1] in quotes: |
| | msg_str = msg_str[1:-1] |
| |
|
| | indent_str = " " * (self.log_indent + indent) |
| | indented_msg = "\n".join([indent_str + line for line in msg_str.split("\n")]) |
| |
|
| | if fill: |
| | indented_msg = add_fillers(indented_msg, fill_side=fill_side) |
| |
|
| | handler = self.handlers[0] |
| | handler.terminator = end |
| |
|
| | getattr(self, level)(colored(indented_msg, color), *args, **kwargs) |
| |
|
| | def route_log(self, method, msg, *args, **kwargs): |
| | level, method = method |
| | functools.partial(self.log, level, method, msg)(*args, **kwargs) |
| |
|
| | def err(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("error", "red"), msg, *args, **kwargs) |
| |
|
| | def warn(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("warning", "light_red"), msg, *args, **kwargs) |
| |
|
| | def note(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("info", "light_magenta"), msg, *args, **kwargs) |
| |
|
| | def mesg(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("info", "light_cyan"), msg, *args, **kwargs) |
| |
|
| | def file(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("info", "light_blue"), msg, *args, **kwargs) |
| |
|
| | def line(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("info", "white"), msg, *args, **kwargs) |
| |
|
| | def success(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("info", "light_green"), msg, *args, **kwargs) |
| |
|
| | def fail(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("info", "light_red"), msg, *args, **kwargs) |
| |
|
| | def back(self, msg: str = "", *args, **kwargs): |
| | self.route_log(("debug", "light_cyan"), msg, *args, **kwargs) |
| |
|
| |
|
| | logger = OSLogger() |
| |
|
| |
|
| | def shell_cmd(cmd, getoutput=False, showcmd=True, env=None): |
| | if showcmd: |
| | logger.info(colored(f"\n$ [{os.getcwd()}]", "light_blue")) |
| | logger.info(colored(f" $ {cmd}\n", "light_cyan")) |
| | if getoutput: |
| | output = subprocess.getoutput(cmd, env=env) |
| | return output |
| | else: |
| | subprocess.run(cmd, shell=True, env=env) |
| |
|
| |
|
| | class Runtimer: |
| | def __enter__(self): |
| | self.t1, _ = self.start_time() |
| | return self |
| |
|
| | def __exit__(self, exc_type, exc_value, traceback): |
| | self.t2, _ = self.end_time() |
| | self.elapsed_time(self.t2 - self.t1) |
| |
|
| | def start_time(self): |
| | t1 = datetime.datetime.now() |
| | self.logger_time("start", t1) |
| | return t1, self.time2str(t1) |
| |
|
| | def end_time(self): |
| | t2 = datetime.datetime.now() |
| | self.logger_time("end", t2) |
| | return t2, self.time2str(t2) |
| |
|
| | def elapsed_time(self, dt=None): |
| | if dt is None: |
| | dt = self.t2 - self.t1 |
| | self.logger_time("elapsed", dt) |
| | return dt, self.time2str(dt) |
| |
|
| | def logger_time(self, time_type, t): |
| | time_types = { |
| | "start": "Start", |
| | "end": "End", |
| | "elapsed": "Elapsed", |
| | } |
| | time_str = add_fillers( |
| | colored( |
| | f"{time_types[time_type]} time: [ {self.time2str(t)} ]", |
| | "light_magenta", |
| | ), |
| | fill_side="both", |
| | ) |
| | logger.line(time_str) |
| |
|
| | |
| | def time2str(self, t): |
| | datetime_str_format = "%Y-%m-%d %H:%M:%S" |
| | if isinstance(t, datetime.datetime): |
| | return t.strftime(datetime_str_format) |
| | elif isinstance(t, datetime.timedelta): |
| | hours = t.seconds // 3600 |
| | hour_str = f"{hours} hr" if hours > 0 else "" |
| | minutes = (t.seconds // 60) % 60 |
| | minute_str = f"{minutes:>2} min" if minutes > 0 else "" |
| | seconds = t.seconds % 60 |
| | second_str = f"{seconds:>2} s" |
| | time_str = " ".join([hour_str, minute_str, second_str]).strip() |
| | return time_str |
| | else: |
| | return str(t) |
| |
|