aboutsummaryrefslogtreecommitdiff
path: root/rainbow_parenthesis
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2023-07-12 23:14:23 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2023-07-12 23:14:23 +0200
commitf333cae22e1d19119b1aa9ee9d30daa21243d1d8 (patch)
tree28cbef38210abb01284a54041250342f556bd8e9 /rainbow_parenthesis
parentRemove black from the pool of colors. (diff)
downloadrainbow-parenthesis-f333cae22e1d19119b1aa9ee9d30daa21243d1d8.tar.gz
rainbow-parenthesis-f333cae22e1d19119b1aa9ee9d30daa21243d1d8.tar.xz
Move project to a proper module layout.
Diffstat (limited to 'rainbow_parenthesis')
-rw-r--r--rainbow_parenthesis/__init__.py112
-rw-r--r--rainbow_parenthesis/__main__.py23
-rw-r--r--rainbow_parenthesis/term.py47
3 files changed, 182 insertions, 0 deletions
diff --git a/rainbow_parenthesis/__init__.py b/rainbow_parenthesis/__init__.py
new file mode 100644
index 0000000..ad8eb51
--- /dev/null
+++ b/rainbow_parenthesis/__init__.py
@@ -0,0 +1,112 @@
+"""Simple script which adds matching rainbow colors to data read on stdin."""
+
+import io
+from typing import Literal
+from dataclasses import dataclass
+
+
+@dataclass
+class Stackpointer():
+ """A "fancy" integer which implements pre-and post decrement."""
+
+ depth: int = 0
+
+ def __call__(self, c: int):
+ """
+ Update and return current state.
+
+ :param c:
+ An integer.
+ Positive integers increment the value after returning it,
+ negative integers decrement the value before returning it,
+ 0 returns the value as is.
+ """
+ ret: int
+ if c > 0:
+ ret = self.depth
+ self.depth += 1
+ elif c < 0:
+ self.depth -= 1
+ ret = self.depth
+ else:
+ ret = self.depth
+ return ret
+
+
+@dataclass
+class Colored:
+ """
+ Tag an item with a color "depth".
+
+ Depth is an arbitarary (positive) integer, from 0 and
+ incrementing by up. Each distinct value should correspond to a
+ color. The colors may repeat.
+ """
+
+ depth: int
+ item: str
+
+
+def color(depth: int, c: str) -> Colored:
+ """Write a highlighted string."""
+ return Colored(depth, c)
+
+
+def colorize(strm: io.TextIOBase) -> list[str | Colored]:
+ """
+ Colorize a given string.
+
+ :param strm:
+ Text stream to get contents from.
+
+ Use ``io.StringIO`` if you want to pass a string.
+ :returns:
+ A list where each item is either a plain string, or a
+ ``Colored`` object.
+ """
+ in_string: Literal[False] | Literal['"'] | Literal["'"] = False
+
+ paren = 0
+ brace = 0
+ brack = 0
+
+ depth = Stackpointer()
+
+ out: list[str | Colored] = []
+ while c := strm.read(1):
+ match c:
+ case '(':
+ out.append(color(depth(1), c))
+ paren += 1
+ case ')':
+ out.append(color(depth(-1), c))
+ paren -= 1
+ case '[':
+ out.append(color(depth(1), c))
+ brack += 1
+ case ']':
+ out.append(color(depth(-1), c))
+ brack -= 1
+ case '{':
+ out.append(color(depth(1), c))
+ brace += 1
+ case '}':
+ out.append(color(depth(-1), c))
+ brace -= 1
+ case "'":
+ if in_string == "'":
+ in_string = False
+ else:
+ in_string = "'"
+ out.append(c)
+ case '"':
+ if in_string == '"':
+ in_string = False
+ else:
+ in_string = '"'
+ out.append(c)
+ case '\\':
+ out.append(strm.read(1))
+ case c:
+ out.append(c)
+ return out
diff --git a/rainbow_parenthesis/__main__.py b/rainbow_parenthesis/__main__.py
new file mode 100644
index 0000000..251731a
--- /dev/null
+++ b/rainbow_parenthesis/__main__.py
@@ -0,0 +1,23 @@
+"""
+Entry point for rainbow parenthesis.
+
+Reads a string from stdin, and outputs it to stdout with all
+parenthesis prettily colored.
+"""
+
+from . import colorize, Colored
+from . import term
+import argparse
+
+parser = argparse.ArgumentParser(prog='rainbow')
+parser.add_argument('input', type=argparse.FileType('r'),
+ nargs='?', default='-')
+args = parser.parse_args()
+
+
+for item in colorize(args.input):
+ match item:
+ case Colored():
+ print(term.colorize(item), end='')
+ case s:
+ print(s, end='')
diff --git a/rainbow_parenthesis/term.py b/rainbow_parenthesis/term.py
new file mode 100644
index 0000000..c712e74
--- /dev/null
+++ b/rainbow_parenthesis/term.py
@@ -0,0 +1,47 @@
+"""
+Output engine for rainbow parenthesis.
+
+Attaches terminal escape codes for use on UNIX-like systems.
+"""
+
+from enum import auto, Enum
+# from dataclasses import dataclass, field
+
+from . import Colored
+
+
+CSI = '\033['
+
+
+class ANSIColor(Enum):
+ """Known CSI colors."""
+
+ # BLACK = 30
+ RED = 31
+ GREEN = auto()
+ YELLOW = auto()
+ BLUE = auto()
+ MAGENTA = auto()
+ CYAN = auto()
+
+ @classmethod
+ def get(cls, depth: int) -> int:
+ """Get color code for the given depth."""
+ return list(cls)[depth % len(list(cls))].value
+
+
+def SGR(*xs):
+ """
+ Build a CSI escape sequence.
+
+ https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
+ """
+ return CSI + ';'.join(str(x) for x in xs) + 'm'
+
+
+def colorize(c: Colored) -> str:
+ """Return contents of colored, surrounded by color escapes."""
+ out = SGR(1, ANSIColor.get(c.depth))
+ out += c.item
+ out += SGR()
+ return out