import os import sys DEFAULT_CAT = r""" ╱|、 (˚ˎ 。7 {{HBAR}}{{HBAR}} |、˜〵 じしˍ,)ノ""" BOXES = { "rounded": { "hbar": "─", "vbar": "│", "corner_tl": "╭", "corner_tr": "╮", "corner_bl": "╰", "corner_br": "╯", "joiner": "┤", } } def main(): parser = False args = False isatty = sys.stdin.isatty() message = "" if not isatty: message = sys.stdin.read().strip() if len(sys.argv) > 1: import argparse parser = argparse.ArgumentParser(description="Display a message in a speech bubble next to a kittycat.") parser.add_argument("--box", type=str, choices=BOXES.keys(), default="rounded", help="The style of the speech bubble.") parser.add_argument("-W", type=int, default=0, help="The maximum width of the message before wrapping. Defaults to terminal width minus kittycat width.") parser.add_argument("-f", "--file", type=str, default="./kittycats/default.cat", help="The path to the kittycat file to use.") if isatty: parser.add_argument("message", type=str, help="The message to display.") args = parser.parse_args() message = args.message else: args = parser.parse_args() if len(message) == 0: message = [ "Meow.", "We still don't support multi-line input.", "Try a pipe why don't you?", "Wi wi wi wi.", "Weh weh weh.", ][int((int.from_bytes(open("/dev/urandom", "rb").read(1), "big") / 256) * 5)] kittycat = DEFAULT_CAT box_style = "rounded" wrapping = 0 if args: if os.path.isfile(args.file): kittycat = open(args.file).read() box_style = args.box wrapping = args.W outcat = get_kittycat(kittycat, message, BOXES[box_style], wrap=wrapping) print(outcat) def get_kittycat(cat: str, msg: str, box: dict[str, str], wrap = 0): for kw in box.keys(): cat = cat.replace(r"{{" + kw.upper() + r"}}", box[kw]) cat_lines = cat.splitlines() cat_width = max(len(line) for line in cat_lines) max_msg_height = len(cat_lines) - 2 max_msg_width = os.get_terminal_size()[0] - cat_width - 2 if not wrap < max_msg_width: raise ValueError("Wrap width exceeds maximum message width") wrap = wrap if wrap else max_msg_width msg = msg.replace("\n", " ") msg_lines = [] msg_words = msg.split(" ") while msg_words: line = "" while msg_words and len(line) + len(msg_words[0]) + 1 <= wrap: line += (msg_words.pop(0) + " ") msg_lines.append(line.rstrip()) if len(msg_lines) > max_msg_height: raise ValueError(f"Message exceeds maximum height of {max_msg_height} given {wrap} character wrap") longest_msg_line = max(len(line) for line in msg_lines) msg_lines = [line.ljust(longest_msg_line) for line in msg_lines] msg_lines = [box["joiner"] + " " + msg_lines.pop(0) + " " + box["vbar"]] + [box["vbar"] + " " + line + " " + box["vbar"] for line in msg_lines] top_bar = box["corner_tl"] + box["hbar"] * (longest_msg_line + 2) + box["corner_tr"] bottom_bar = box["corner_bl"] + box["hbar"] * (longest_msg_line + 2) + box["corner_br"] msg_lines = [top_bar, *msg_lines, bottom_bar] outcat_lines = [] for i, line in enumerate(cat_lines): box_line = "" if i < len(msg_lines): box_line = msg_lines[i] outcat_lines.append(line + box_line) return "\n".join(outcat_lines) if __name__ == "__main__": main()