diff --git a/src/formatitko/formatitko.py b/src/formatitko/formatitko.py index e192c86..776d97e 100755 --- a/src/formatitko/formatitko.py +++ b/src/formatitko/formatitko.py @@ -29,12 +29,22 @@ def main(): parser.add_argument("-t", "--output-tex", help="The TEX file to write into.") parser.add_argument("-m", "--output-md", help="The Markdown file to write into. (Uses pandoc to generate markdown)") parser.add_argument("-j", "--output-json", help="The JSON file to dump the pandoc-compatible AST into.") - parser.add_argument("input_filename", help="The markdown file to process.") + parser.add_argument("--katex-server", action='store_true', help="Starts a KaTeX server and prints the socket filename onto stdout. Useful for running formatitko many times without starting the KaTeX server each time.") + parser.add_argument("-k", "--katex-socket", help="The KaTeX server socket filename obtained by running with `--katex-server`.") + parser.add_argument("input_filename", help="The markdown file to process.", nargs="?" if "--katex-server" in sys.argv else None) parser.add_argument("--debug", action='store_true') args = parser.parse_args() - # TODO: Accept path to unix socket for katexClient, then don't init our own, - # just connect to an existing one. For formátíking many files in a row. + if args.katex_server: + with KatexClient(connect=False) as katexClient: + print(katexClient.get_socket()) + try: + while True: + pass + except KeyboardInterrupt: + print("Exiting...") + return + # Use panflute to parse the input MD file doc = import_md(open(args.input_filename, "r").read()) @@ -48,7 +58,7 @@ def main(): if args.output_html is not None: # Initialize KaTeX client (this runs the node app and connects to a unix socket) - with KatexClient() as katexClient: + with KatexClient(socket=args.katex_socket) as katexClient: HTMLGenerator(open(args.output_html, "w"), katexClient, imageProcessor).generate(doc) if args.output_md is not None: diff --git a/src/formatitko/katex-server/index.mjs b/src/formatitko/katex-server/index.mjs index ecb4675..2c1c05c 100644 --- a/src/formatitko/katex-server/index.mjs +++ b/src/formatitko/katex-server/index.mjs @@ -83,7 +83,7 @@ async function handleClient(client) { * the one from the parent group and can add its own stuff without * affecting the parent. */ - const macroStack = [{}] + let macroStack = [{}] for await (const line of rl) { try { // The custom commands for pushing and popping the macro stack. @@ -94,6 +94,9 @@ async function handleClient(client) { } else if (line === "endgroup") { macroStack.pop() continue + } else if (line === "init") { + macroStack = [{}] + continue } const query = JSON.parse(line) const results = [] diff --git a/src/formatitko/katex.py b/src/formatitko/katex.py index b0a3570..ad431e6 100644 --- a/src/formatitko/katex.py +++ b/src/formatitko/katex.py @@ -19,8 +19,21 @@ class KatexClient: _server_process: subprocess.Popen[bytes] _socket_file: str _temp_dir: tempfile.TemporaryDirectory[str] + _connected: bool - def __init__(self): + def __init__(self, socket: str=None, connect: bool=True): + if socket is not None: + self._socket_file = socket + else: + self.open_socket() + if connect: + self.connect() + self._client.sendall("init\n".encode("utf-8")) # Reinitialize KaTeX Server in case it was reused. + self._connected = True + else: + self._connected = False + + def open_socket(self): # Create temporary directory for socket self._temp_dir = tempfile.TemporaryDirectory(prefix='formatitko') self._socket_file = self._temp_dir.name + "/katex-socket" @@ -40,15 +53,20 @@ class KatexClient: self._server_process = subprocess.Popen(["node", srcdir + "/katex-server/index.mjs", self._socket_file], stdout=subprocess.PIPE) - self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - ok = self._server_process.stdout.readline() if ok != b"OK\n": raise KatexServerError("Failed to connect to katex-server") + def connect(self): + self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._client.connect(self._socket_file) + def get_socket(self): + return self._socket_file + def render(self, tex: str, options: dict={}): + if not self._connected: + raise KatexServerError("KatexClient not connected to Katex server. It should be initialized with connect=True.") # Send formulas to translate self._client.sendall((json.dumps({"formulas":[{"tex":tex}], "options":options})+"\n").encode("utf-8")) @@ -67,13 +85,18 @@ class KatexClient: # Special commands implemented in the JS file for grouping defs together. def begingroup(self): + if not self._connected: + raise KatexServerError("KatexClient not connected to Katex server. It should be initialized with connect=True.") self._client.sendall("begingroup\n".encode("utf-8")) def endgroup(self): + if not self._connected: + raise KatexServerError("KatexClient not connected to Katex server. It should be initialized with connect=True.") self._client.sendall("endgroup\n".encode("utf-8")) def __enter__(self): return self def __exit__(self, type, value, tb): - self._server_process.terminate() + if hasattr(self, "_server_process") and self._server_process is not None: + self._server_process.terminate()