Browse Source

KaTeX server is now in separate repo.

separate-katex-repo
Jan Černohorský 2 months ago
parent
commit
9c852f7be8
  1. 4
      .gitmodules
  2. 1
      katex-server
  3. 1
      src/formatitko/katex-server/.gitignore
  4. 1
      src/formatitko/katex-server/README.md
  5. 1
      src/formatitko/katex-server/index.js
  6. 131
      src/formatitko/katex-server/index.mjs
  7. 42
      src/formatitko/katex-server/package-lock.json
  8. 17
      src/formatitko/katex-server/package.json
  9. 15
      src/formatitko/katex-server/yarn.lock
  10. 28
      src/formatitko/katex.py

4
.gitmodules

@ -1,6 +1,6 @@
[submodule "ucwmac"]
path = ucwmac
url = git://git.ucw.cz/ucwmac.git
[submodule "katex-server"]
path = katex-server
[submodule "src/formatitko/katex-server"]
path = src/formatitko/katex-server
url = https://gitea.ks.matfyz.cz/KSP/formatitko-katex-server.git

1
katex-server

@ -1 +0,0 @@
Subproject commit 9a122d9ee999a4e14f3543fd9f01d38de40a7706

1
src/formatitko/katex-server/.gitignore

@ -0,0 +1 @@
node_modules

1
src/formatitko/katex-server/README.md

@ -0,0 +1 @@
This was made by Standa Lukeš @exyi

1
src/formatitko/katex-server/index.js

@ -0,0 +1 @@
console.log(require('katex').renderToString('\\frac{2a}{b}'))

131
src/formatitko/katex-server/index.mjs

@ -0,0 +1,131 @@
// KaTeX rendering server
// Listens on unix socket, path is provided as first argument
// Expects JSON lines, each line is a query with the following schema:
// {
// formulas: [
// {
// tex: string,
// options?: object
// }
// ],
// options?: object
// }
// see https://katex.org/docs/options.html for list of available options
// If options formulas[].options field is used, the global options field is ignored.
// For each line, returns one JSON line with the following schema:
// {
// results: [
// { html?: string } | { error?: string }
// ]
// } | { error?: string }
// If one formula is invalid, the error in results is used
// If the entire query is invalid (couldn't parse JSON, for example), the outer error field is used
import katex from 'katex'
import net from 'net'
import * as readline from 'readline'
const myArgs = process.argv.slice(2)
const unixSocketPath = myArgs[0]
if (!unixSocketPath) {
console.error('you must specify socket path')
process.exit(1)
}
// This server listens on a Unix socket at /var/run/mysocket
var unixServer = net.createServer(handleClient);
unixServer.listen(unixSocketPath);
console.log("OK")
function handleExit(signal) {
// unixServer.emit('close')
unixServer.close(function () {
});
process.exit(0); // put this into the callback to avoid closing open connections
}
process.on('SIGINT', handleExit);
process.on('SIGQUIT', handleExit);
process.on('SIGTERM', handleExit);
process.on('exit', handleExit);
const defaultOptions = {}
/**
* @param {net.Socket} socket
* @returns {Promise<void>}
* */
function socketWrite(socket, data) {
return new Promise((resolve, reject) => {
socket.write(data, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
/**
* @param {net.Socket} client
* */
async function handleClient(client) {
const rl = readline.createInterface({ input: client })
/* Added by GS: A stack of katex's `macros` objects, each group inherits
* the one from the parent group and can add its own stuff without
* affecting the parent.
*/
let macroStack = [{}]
for await (const line of rl) {
try {
// The custom commands for pushing and popping the macro stack.
if (line === "begingroup") {
// Copy the current state of macros and push it onto the stack.
macroStack.push({...macroStack.slice(-1)[0]})
continue
} else if (line === "endgroup") {
macroStack.pop()
continue
} else if (line === "init") {
macroStack = [{}]
continue
}
const query = JSON.parse(line)
const results = []
for (const input of query.formulas) {
const options = input.options ?? query.options ?? defaultOptions
// Add macros from the macros option
if (options.macros) {
for (const macro of Object.keys(options.macros)) {
macroStack.slice(-1)[macro] = options.macros[macro]
}
}
options.macros = macroStack.slice(-1)[0]
// Enforce globalGroup option, katex then saves created macros
// into the options.macros object.
options.globalGroup = true
try {
const html = katex.renderToString(input.tex, options)
results.push({ html })
} catch (e) {
results.push({ error: String(e) })
}
}
await socketWrite(client, JSON.stringify({ results }, null, query.debug ? ' ' : undefined))
await socketWrite(client, '\n')
} catch (e) {
console.error(e)
await socketWrite(client, JSON.stringify({ error: String(e) }))
await socketWrite(client, '\n')
}
}
}

42
src/formatitko/katex-server/package-lock.json

@ -0,0 +1,42 @@
{
"name": "formatitko-katex-server",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "formatitko-katex-server",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"katex": "^0.16.3"
},
"bin": {
"formatitko-katex-server": "index.mjs"
}
},
"node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"engines": {
"node": ">= 12"
}
},
"node_modules/katex": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.3.tgz",
"integrity": "sha512-3EykQddareoRmbtNiNEDgl3IGjryyrp2eg/25fHDEnlHymIDi33bptkMv6K4EOC2LZCybLW/ZkEo6Le+EM9pmA==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"dependencies": {
"commander": "^8.0.0"
},
"bin": {
"katex": "cli.js"
}
}
}
}

17
src/formatitko/katex-server/package.json

@ -0,0 +1,17 @@
{
"name": "formatitko-katex-server",
"version": "1.0.0",
"description": "",
"main": "index.mjs",
"bin": {
"formatitko-katex-server":"index.mjs"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"katex": "^0.16.3"
}
}

15
src/formatitko/katex-server/yarn.lock

@ -0,0 +1,15 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
katex@^0.16.3:
version "0.16.9"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.9.tgz#bc62d8f7abfea6e181250f85a56e4ef292dcb1fa"
integrity sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==
dependencies:
commander "^8.3.0"

28
src/formatitko/katex.py

@ -3,6 +3,7 @@ import subprocess
import tempfile
import json
import os
import shutil
class KatexError(Exception):
pass
@ -20,8 +21,10 @@ class KatexClient:
_socket_file: str
_temp_dir: tempfile.TemporaryDirectory[str]
_connected: bool
_katex_server_path: str
def __init__(self, socket: str=None, connect: bool=True):
def __init__(self, socket: str=None, connect: bool=True, katex_server_path: str=None):
self._katex_server_path = katex_server_path
if socket is not None:
self._socket_file = socket
else:
@ -38,20 +41,21 @@ class KatexClient:
self._temp_dir = tempfile.TemporaryDirectory(prefix='formatitko')
self._socket_file = self._temp_dir.name + "/katex-socket"
srcdir = os.path.dirname(os.path.realpath(__file__))
if self._katex_server_path is None:
srcdir = os.path.dirname(os.path.realpath(__file__))
# Test if `node_modules` directory exists and if not, run `npm install`
if not os.path.isdir(srcdir + "/katex-server/node_modules"):
print("Installing node dependencies for the first time...")
try:
subprocess.run(["npm", "install"], cwd=srcdir+"/katex-server", check=True)
except subprocess.CalledProcessError as e:
if e.returncode == 127:
# Test if `node_modules` directory exists and if not, run `npm install`
if not os.path.isdir(srcdir + "/katex-server/node_modules"):
print("Installing node dependencies for the first time...")
npm = shutil.which("npm") or shutil.which("yarnpkg")
if npm is None:
raise NPMNotFoundError("npm not found. Node.js is required to use KaTeX.")
else:
raise e
subprocess.run([npm, "install"], cwd=srcdir+"/katex-server", check=True)
self._katex_server_path = srcdir + "/katex-server/index.mjs"
self._server_process = subprocess.Popen(["node", srcdir + "/katex-server/index.mjs", self._socket_file], stdout=subprocess.PIPE)
self._server_process = subprocess.Popen(["node", self._katex_server_path, self._socket_file], stdout=subprocess.PIPE)
ok = self._server_process.stdout.readline()
if ok != b"OK\n":

Loading…
Cancel
Save