diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f33237 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/result* +/.venv +__pycache__ diff --git a/jlog/__init__.py b/jlog/__init__.py index c90cbd9..f4f916e 100644 --- a/jlog/__init__.py +++ b/jlog/__init__.py @@ -1,6 +1,9 @@ """Configure logging.""" +import base64 +from enum import Enum from enum import IntEnum +from enum import auto import logging import pathlib import re @@ -23,7 +26,65 @@ class ANSIColor(IntEnum): WHITE = 8 +class Mode(Enum): + C0 = auto() + C1 = auto() + + class ANSI: + mode: Mode + + SOH = "\u0001" + STX = "\u0002" + BEL = "\u0007" + BS = "\u0008" + HT = "\u0009" + LF = "\u000a" + VT = "\u000b" + FF = "\u000c" + CR = "\u000d" + ESC = "\u001b" + DEL = "\u007f" + + def __init__(self, mode: Mode = Mode.C0): + self.mode = mode + + @property + def DCS(self: Self) -> str: + """Device Control String""" + match self.mode: + case Mode.C1: + return "\u008d" + case Mode.C0 | _: + return f"{self.ESC}P" + + @property + def CSI(self: Self) -> str: + """Control Sequence Introducer""" + match self.mode: + case Mode.C1: + return "\u009b" + case Mode.C0 | _: + return f"{self.ESC}[" + + @property + def ST(self: Self) -> str: + """String Terminator""" + match self.mode: + case Mode.C1: + return "\u009c" + case Mode.C0 | _: + return f"{self.ESC}\\" + + @property + def OSC(self: Self) -> str: + """Operating System Command""" + match self.mode: + case Mode.C1: + return "\u009d" + case Mode.C0 | _: + return f"{self.ESC}]" + def __getattr__(self: Self, item: str) -> str: return self.color(item) @@ -42,7 +103,7 @@ class ANSI: try: color = ANSIColor[item] - return f"\u001b[1;{color+offset:d}{extra}m" + return f"{self.CSI}1;{color+offset:d}{extra}m" except KeyError: return self.fg24bitrgb(*(getrgb(item)[:3])) @@ -54,6 +115,10 @@ class ANSI: def white(self: Self) -> str: return self.color("white") + @property + def green(self: Self) -> str: + return self.color("green") + @property def blue(self: Self) -> str: return self.color("blue") @@ -64,37 +129,76 @@ class ANSI: @property def reset(self: Self) -> str: - return "\u001b[0m" + return f"{self.CSI}0m" @property def bold(self: Self) -> str: - return "\u001b[1m" + return f"{self.CSI}1m" @property def faint(self: Self) -> str: - return "\u001b[2m" + return f"{self.CSI}2m" @property def italic(self: Self) -> str: - return "\u001b[3m" + return f"{self.CSI}3m" @property def underline(self: Self) -> str: - return "\u001b[4m" + return f"{self.CSI}4m" @property def reversed(self: Self) -> str: - return "\u001b[7m" + return f"{self.CSI}7m" + + def icon_name_and_window_title(self: Self, text: str) -> str: + return f"{self.OSC}0;{text}{self.ST}" + + def icon_name(self: Self, text: str) -> str: + return f"{self.OSC}1;{text}{self.ST}" + + def window_title(self: Self, text: str) -> str: + return f"{self.OSC}2;{text}{self.ST}" + + def hyperlink(self: Self, link: str, text: str) -> str: + return f"{self.OSC}8;;{link}{self.ST}{text}{self.OSC}8;;{self.ST}" + + def post_notification(self: Self, message: str) -> str: + return f"{self.OSC}9;{message}{self.ST}" + + def write_to_pastboard(self: Self, pasteboard: str, data: str) -> str: + return f"{self.OSC}52;{pasteboard};{base64.b64encode(data).decode()}{self.ST}" + + def query_pasteboard(self: Self, pasteboard: str) -> str: + return f"{self.OSC}52;{pasteboard};?{self.ST}" + + def copy_to_clipboard(self: Self, clipboard_name: str) -> str: + return f"{self.OSC}1337;CopyToClipboard={clipboard_name}{self.ST}" + + def end_copy(self: Self) -> str: + return f"{self.OSC}1337;EndCopy{self.ST}" + + def clear_scrollback_history(self: Self) -> str: + return f"{self.OSC}1337;ClearScrollback{self.ST}" + + def set_current_directory(self: Self, dir: str) -> str: + return f"{self.OSC}1337;CurrentDir={dir}{self.ST}" + + def change_profile(self: Self, new_profile_name: str) -> str: + return f"{self.OSC}1337;SetProfile={new_profile_name}{self.ST}" + + def steal_focus(self: Self) -> str: + return f"{self.OSC}1337;StealFocus{self.ST}" def fg8bit(self: Self, code: int) -> str: assert code >= 0 assert code < 256 - code = f"\u001b[38;5;{code}m" + code = f"{self.CSI}38;5;{code}m" def bg8bit(self: Self, code: int) -> str: assert code >= 0 assert code < 256 - return f"\u001b[48;5;{code}m" + return f"{self.CSI}48;5;{code}m" def fg24bitrgb(self: Self, red: int, green: int, blue: int) -> str: assert red >= 0 @@ -103,7 +207,7 @@ class ANSI: assert green < 256 assert blue >= 0 assert blue < 256 - return f"\u001b[38;2;{red};{green};{blue}m" + return f"{self.CSI}38;2;{red};{green};{blue}m" def bg24bitrgb(self: Self, red: int, green: int, blue: int) -> str: assert red >= 0 @@ -112,7 +216,7 @@ class ANSI: assert green < 256 assert blue >= 0 assert blue < 256 - return f"\u001b[48;2;{red};{green};{blue}m" + return f"{self.CSI}48;2;{red};{green};{blue}m" # def fg24bitcmy(self, cyan: int, magenta: int, yellow: int) -> str: # assert cyan >= 0 @@ -121,12 +225,41 @@ class ANSI: # assert magenta < 256 # assert yellow >= 0 # assert yellow < 256 - # return f"\u001b[38:2::{cyan};{magenta};{yellow}:::m" + # return f"{self.OSC}38:2::{cyan};{magenta};{yellow}:::m" ansi = ANSI() +class BashPromptBuilder: + ansi: ANSI + + ESC = "\\e" + BEL = "\\a" + prompt = "\\$" + current_working_directory = "\\w" + username = "\\u" + hostname_short = "\\h" + hostname_long = "\\H" + + @property + def RL_PROMPT_START_IGNORE(self: Self) -> str: + return self.ansi.SOH + + @property + def RL_PROMPT_END_IGNORE(self: Self) -> str: + return self.ansi.STX + + def __init__(self: Self, ansi: ANSI = ansi) -> None: + self.ansi = ansi + + def wrap_nonprinting(self: Self, text: str) -> str: + return f"{self.RL_PROMPT_START_IGNORE}{text}{self.RL_PROMPT_END_IGNORE}" + + def title_bar(self: Self, text: str) -> str: + return self.wrap_nonprinting(self.ansi.icon_name_and_window_title(text)) + + class JLogFormatter(logging.Formatter): """Format time the way it SHOULD be done.""" diff --git a/jlog/__pycache__/__init__.cpython-311.pyc b/jlog/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 9ae2f2f..0000000 Binary files a/jlog/__pycache__/__init__.cpython-311.pyc and /dev/null differ