import re import datetime from dataclasses import dataclass from typing import List @dataclass class Ping: """ A ping is a single line in the log file consisting of a UNIX time stamp, followed by tags (where each individual string surrounded by spaces is a tag), followed by optional comments between square brackets or parentheses. The original Perl implementation puts a human readable representation of the UNIX time stamp into square brackets. Here are two example pings: 1601557948 morning_pages [2020.10.01 09:12:28 THU] 1601560369 work_call another_tag [2020.10.01 09:52:49 THU] """ time: int # UNIX time stamp tags: List[str] # each separate word is a tag comments: List[str] # each string between [] or () is a comment line: str = "" # the whole line as found in the log file r_time = re.compile("^\s*\d{9,11}") r_spaces = re.compile("\s+") r_comment_parens = re.compile("\(([^\)]*)\)") r_comment_square = re.compile("\[([^\]]*)\]") def line_to_ping(line: str): """ Parses a string into a Ping object. Raises Exception on failure. >>> line_to_ping(" 1601557948 t (c)") Ping(time=1601557948, tags=['t'], comments=['c'], line=' 1601557948 t (c)') """ time = int(Ping.r_time.match(line).group()) tags = Ping.get_tags(line) comments = Ping.r_comment_parens.findall(line) + \ Ping.r_comment_square.findall(line) return Ping(time, tags, comments, line) def get_tags(line): """Extracts the tags from a tag line.""" line = Ping.r_time.sub("", line) line = Ping.r_comment_parens.sub("", line) line = Ping.r_comment_square.sub("", line) line = Ping.r_spaces.sub(" ", line) return line.split() def ping_to_line(ping, annotate_time=False, line_length=79) -> str: tags = " ".join([t.strip() for t in ping.tags]) comments = " ".join(["[" + c.strip() + "]" for c in ping.comments]) line = "{} {} {}".format(ping.time, tags, comments) if annotate_time: line = Ping.add_time_annotation(ping.time, line, line_length) return line def add_time_annotation(time: int, line: str, line_length: int) -> str: """Appends human readable date/time in square brackets to line.""" remaining_length = line_length - len(line) if remaining_length > 24: strf = " [%Y.%m.%d %H:%M:%S %a]" elif remaining_length > 18: strf = " [%m.%d %H:%M:%S %a]" elif remaining_length > 15: strf = " [%d %H:%M:%S %a]" elif remaining_length > 12: strf = " [%H:%M:%S %a]" elif remaining_length > 9: strf = " [%H:%M %a]" elif remaining_length > 5: strf = " [%H:%M]" else: strf = " [%M]" time_comment = datetime.datetime.fromtimestamp(time).strftime(strf) remaining_length -= len(time_comment) line += " " * remaining_length + time_comment return line