Source code for textmate_grammar.utils.logger
from __future__ import annotations
import logging
from functools import wraps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..parser import GrammarParser
MAX_LENGTH = 79
[docs]
def track_depth(func):
"""Simple decorator to track recusion depth."""
@wraps(func)
def wrapper(*args, depth: int = -1, **kwargs):
return func(*args, depth=depth + 1, **kwargs)
return wrapper
[docs]
class LogFormatter(logging.Formatter):
"""
A custom log formatter that formats log records with color-coded messages.
"""
green = "\x1b[32;32m"
grey = "\x1b[38;20m"
yellow = "\x1b[33;20m"
red = "\x1b[31;20m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
format_string = "%(name)s:%(message)s"
FORMATS = {
logging.DEBUG: green + format_string + reset,
logging.INFO: grey + format_string + reset,
logging.WARNING: yellow + format_string + reset,
logging.ERROR: red + format_string + reset,
logging.CRITICAL: bold_red + format_string + reset,
}
[docs]
def format(self, record):
"""
Formats the log record with the color-coded format based on the log level.
:param record: The log record to be formatted.
:return: The formatted log message.
"""
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
[docs]
class Logger:
"""
The logger object for the grammar parsers.
"""
long_msg_div = "\x1b[1;32m ... \x1b[0m"
def __init__(self, **kwargs) -> None:
self.id = None
self.max_token_length = 50
self.line_decimals = 3
self.position_decimals = 3
self.scope = "UNKNOWN"
self.logger = logging.getLogger("textmate_grammar")
channel = logging.StreamHandler()
channel.setFormatter(LogFormatter())
self.logger.addHandler(channel)
[docs]
def configure(self, parser: GrammarParser, height: int, width: int, **kwargs) -> None:
"""Configures the logger to a specific grammar and content length"""
self.line_decimals = len(str(height))
self.position_decimals = len(str(width))
id = parser.token if parser.token else parser.key
if self.id != id:
self.id = id
tokens = _gen_all_tokens(parser.grammar)
self.max_token_length = max(len(token) for token in tokens)
self.scope = parser.token
[docs]
def format_message(
self,
message: str,
parser: GrammarParser | None = None,
position: tuple[int, int] | None = None,
depth: int = 0,
) -> str:
"""
Formats a logging message to the defined format.
:param message: The logging message to be formatted.
:param parser: The GrammarParser object associated with the message. Defaults to None.
:param position: The position tuple (line, column) associated with the message. Defaults to None.
:param depth: The depth of the message in the logging hierarchy. Defaults to 0.
:return: The formatted logging message.
"""
if position:
msg_pos = "{:{ll}d}-{:{lp}d}".format(
*position, ll=self.line_decimals, lp=self.position_decimals
).replace(" ", "0")
else:
msg_pos = "." * (self.line_decimals + self.position_decimals + 1)
if parser:
parser_id = parser.token if parser.token else parser.key
msg_id = (
"." * (self.max_token_length - len(parser_id)) + parser_id[: self.max_token_length]
)
else:
msg_id = "." * self.max_token_length
vb_message = f"{'|'*(depth-1)}{'-'*bool(depth)}{message}"
if len(vb_message) > MAX_LENGTH:
half_length = min([(MAX_LENGTH - 6) // 2, (len(vb_message) - 6) // 2])
vb_message = vb_message[:half_length] + self.long_msg_div + vb_message[-half_length:]
return f"{self.scope}:{msg_pos}:{msg_id}: {vb_message}"
[docs]
def debug(self, *args, **kwargs) -> None:
if self.logger.getEffectiveLevel() > logging.DEBUG:
return
message = self.format_message(*args, **kwargs)
self.logger.debug(message)
[docs]
def info(self, *args, **kwargs) -> None:
if self.logger.getEffectiveLevel() > logging.INFO:
return
message = self.format_message(*args, **kwargs)
self.logger.info(message)
[docs]
def warning(self, *args, **kwargs) -> None:
if self.logger.getEffectiveLevel() > logging.WARNING:
return
message = self.format_message(*args, **kwargs)
self.logger.warning(message)
[docs]
def error(self, *args, **kwargs) -> None:
if self.logger.getEffectiveLevel() > logging.ERROR:
return
message = self.format_message(*args, **kwargs)
self.logger.error(message)
[docs]
def critical(self, *args, **kwargs) -> None:
if self.logger.getEffectiveLevel() > logging.CRITICAL:
return
message = self.format_message(*args, **kwargs)
self.logger.critical(message)
def _gen_all_tokens(grammar: dict, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
for key, value in grammar.items():
if key in ["name", "contentName"]:
items.append(value)
elif isinstance(value, list):
for nested_grammar in (item for item in value if isinstance(item, dict)):
_gen_all_tokens(nested_grammar, items)
elif isinstance(value, dict):
_gen_all_tokens(value, items)
return items
LOGGER = Logger()