From 651ed4f26cef2cfadf38bf007de51e0cd0ab8370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Madeleine=20Sydney=20=C5=9Alaga?= Date: Mon, 16 Feb 2026 03:55:07 -0700 Subject: [PATCH] feat: tex rendering via temml --- doerg/deps.edn | 3 +- .../doerg-parser/{package.nix => default.nix} | 0 doerg/doerg-tex/README.org | 1 + doerg/doerg-tex/default.nix | 19 ++ doerg/doerg-tex/index.js | 55 +++++ doerg/doerg-tex/our-tex.nix | 43 ++++ doerg/doerg-tex/package-lock.json | 227 ++++++++++++++++++ doerg/doerg-tex/package.json | 6 + doerg/doerg-tex/serialise.clj | 16 ++ .../resources/net/deertopia/doerg/prelude.tex | 153 ++++++++++++ .../net/deertopia/doerg/tuftesque.css | 4 +- doerg/src/net/deertopia/doerg/common.clj | 18 ++ doerg/src/net/deertopia/doerg/element.clj | 22 +- doerg/src/net/deertopia/doerg/render.clj | 22 +- doerg/src/net/deertopia/doerg/tex.clj | 49 ++++ .../test/net/deertopia/doerg/common_test.clj | 32 +++ .../test/net/deertopia/doerg/element_test.clj | 28 --- flake.lock | 37 ++- flake.nix | 15 +- 19 files changed, 690 insertions(+), 60 deletions(-) rename doerg/doerg-parser/{package.nix => default.nix} (100%) create mode 100644 doerg/doerg-tex/README.org create mode 100644 doerg/doerg-tex/default.nix create mode 100755 doerg/doerg-tex/index.js create mode 100644 doerg/doerg-tex/our-tex.nix create mode 100644 doerg/doerg-tex/package-lock.json create mode 100644 doerg/doerg-tex/package.json create mode 100644 doerg/doerg-tex/serialise.clj create mode 100644 doerg/resources/net/deertopia/doerg/prelude.tex create mode 100644 doerg/src/net/deertopia/doerg/common.clj create mode 100644 doerg/src/net/deertopia/doerg/tex.clj create mode 100644 doerg/test/net/deertopia/doerg/common_test.clj diff --git a/doerg/deps.edn b/doerg/deps.edn index 956b86c..a401d36 100644 --- a/doerg/deps.edn +++ b/doerg/deps.edn @@ -7,5 +7,6 @@ {:git/sha "531d629b7f05f37232261cf9e8927a4b5915714f"} hiccup/hiccup {:mvn/version "2.0.0-RC4"} com.rpl/specter {:mvn/version "1.1.6"} - lambdaisland/deep-diff2 {:mvn/version "2.12.219"}} + lambdaisland/deep-diff2 {:mvn/version "2.12.219"} + mvxcvi/clj-cbor {:mvn/version "1.1.1"}} :paths ["src" "resources" "test"]} diff --git a/doerg/doerg-parser/package.nix b/doerg/doerg-parser/default.nix similarity index 100% rename from doerg/doerg-parser/package.nix rename to doerg/doerg-parser/default.nix diff --git a/doerg/doerg-tex/README.org b/doerg/doerg-tex/README.org new file mode 100644 index 0000000..cd36387 --- /dev/null +++ b/doerg/doerg-tex/README.org @@ -0,0 +1 @@ +#+title: doerg-tex diff --git a/doerg/doerg-tex/default.nix b/doerg/doerg-tex/default.nix new file mode 100644 index 0000000..74b5e06 --- /dev/null +++ b/doerg/doerg-tex/default.nix @@ -0,0 +1,19 @@ +{ buildNpmPackage +, importNpmLock +, makeWrapper +, ibm-plex +, callPackage +, our-tex ? callPackage ./our-tex.nix {} +}: + +buildNpmPackage { + pname = "doerg-tex"; + version = "0.1.0"; + src = ./.; + npmDeps = importNpmLock { npmRoot = ./.; }; + npmConfigHook = importNpmLock.npmConfigHook; + dontNpmBuild = true; + buildInputs = [ + our-tex + ]; +} diff --git a/doerg/doerg-tex/index.js b/doerg/doerg-tex/index.js new file mode 100755 index 0000000..de97de9 --- /dev/null +++ b/doerg/doerg-tex/index.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +const commandLineArgs = require ("command-line-args") +const temml = require ("temml") +const fs = require ("node:fs") +const path = require ("node:path") +const { DecoderStream, EncoderStream } = require ("cbor-x") +const { Transform } = require('node:stream') + +const cli_spec = + [ { name: "preamble" } + ] + +function load_preambles (preamble) { + const data = fs.readFileSync (preamble, "utf8") .toString () + return temml.definePreamble (data) +} + +let macros = {} + +function is_render_command (cmd) { + return cmd instanceof Array + && cmd.length === 1 + && typeof cmd[0] === "string" +} + +function do_command (cmd) { + try { + if (typeof cmd === "string") { + return temml.renderToString (cmd, {macros}) + } else if (is_render_command (cmd)) { + return temml.renderToString (cmd[0], {displayMode: true,macros}) + } else { + return null + } + } catch (e) { + return e + } +} + +function main () { + const options = commandLineArgs (cli_spec) + console.error (options) + macros = load_preambles (options.preamble) + const decoder = new DecoderStream () + const encoder = new EncoderStream () + process.stdin.pipe (decoder) + const command_responses = decoder.map (do_command) + decoder + .map (do_command) + .pipe (encoder) + .pipe (process.stdout) +} + +main () diff --git a/doerg/doerg-tex/our-tex.nix b/doerg/doerg-tex/our-tex.nix new file mode 100644 index 0000000..fa70de0 --- /dev/null +++ b/doerg/doerg-tex/our-tex.nix @@ -0,0 +1,43 @@ +{ texlive +, syd-plex-latex +}: + +texlive.combine { + inherit syd-plex-latex; + inherit (texlive) + scheme-basic + xkeyval + changepage + fancyhdr + geometry + natbib + paralist + placeins + ragged2e + setspace + textcase + titlesec + xcolor + hardwrap + xifthen + catchfile + fontspec + latexmk + xetex + dvisvgm dvipng wrapfig # For Org-mode previews/export. + amsmath # Essential for mathematics. + spath3 # TikZ dependency? + ulem hyperref + capt-of + pgf # Includes TikZ. + tikz-cd # Commutative diagrams w/ TikZ. + quiver # Commutative diagrams w/ TikZ & q.uiver.app. + bookmark + metafont + preview # For new-gen org-latex-preview. + mylatexformat # For new-gen org-latex-preview. + collection-fontsrecommended # Essential fonts. + etoolbox # For Org-mode exports. + caption + ; +} diff --git a/doerg/doerg-tex/package-lock.json b/doerg/doerg-tex/package-lock.json new file mode 100644 index 0000000..e6e6eca --- /dev/null +++ b/doerg/doerg-tex/package-lock.json @@ -0,0 +1,227 @@ +{ + "name": "doerg-tex", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "doerg-tex", + "version": "0.1.0", + "dependencies": { + "cbor-x": "^1.6.0", + "command-line-args": "^6.0.1", + "temml": "^0.13.1" + }, + "bin": { + "doerg-tex": "index.js" + } + }, + "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-darwin-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-win32-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, + "node_modules/cbor-extract": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0" + } + }, + "node_modules/cbor-x": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.0.tgz", + "integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==", + "license": "MIT", + "optionalDependencies": { + "cbor-extract": "^2.2.0" + } + }, + "node_modules/command-line-args": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.1.tgz", + "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==", + "license": "MIT", + "dependencies": { + "array-back": "^6.2.2", + "find-replace": "^5.0.2", + "lodash.camelcase": "^4.3.0", + "typical": "^7.2.0" + }, + "engines": { + "node": ">=12.20" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/temml": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/temml/-/temml-0.13.1.tgz", + "integrity": "sha512-/fL1utq8QUD9YpcLeZHPRnp9Cbzbexq5hZl5uSBhf8mNYiKkcS4eYbLidDB+/nF8C+RHAcBQbKw2bKoS83mz1Q==", + "license": "MIT", + "engines": { + "node": ">=18.13.0" + } + }, + "node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + } + } +} diff --git a/doerg/doerg-tex/package.json b/doerg/doerg-tex/package.json new file mode 100644 index 0000000..4fb091d --- /dev/null +++ b/doerg/doerg-tex/package.json @@ -0,0 +1,6 @@ +{"dependencies":{"temml":"^0.13.1" + ,"command-line-args":"^6.0.1" + ,"cbor-x":"^1.6.0"} +,"name":"doerg-tex" +,"version":"0.1.0" +,"bin":{"doerg-tex":"index.js"}} diff --git a/doerg/doerg-tex/serialise.clj b/doerg/doerg-tex/serialise.clj new file mode 100644 index 0000000..1c8e9dd --- /dev/null +++ b/doerg/doerg-tex/serialise.clj @@ -0,0 +1,16 @@ +(ns serialise + (:require [clj-cbor.core :as cbor] + [clojure.string :as str])) + +(defn w [x] + (cbor/encode cbor/default-codec System/out x)) + +(defn c [x] + (->> x cbor/encode (map #(format "%02x" %)) (str/join " "))) + +(w "\\naturalto") + +#_#_#_ +(w "c = \\sqrt{a^2 + y^2}") +(w "c = \\sqrt{a^ + y^2") +(w "\\alpha^\\beta") diff --git a/doerg/resources/net/deertopia/doerg/prelude.tex b/doerg/resources/net/deertopia/doerg/prelude.tex new file mode 100644 index 0000000..a0cb5b4 --- /dev/null +++ b/doerg/resources/net/deertopia/doerg/prelude.tex @@ -0,0 +1,153 @@ +% Of the highest importance: +\renewcommand{\phi}{\varphi} +\renewcommand{\epsilon}{\varepsilon} + +% Saner abbreviations for font faces (take an argument rather than +% applying to the current context). +\renewcommand{\rm}[1]{\mathrm{#1}} +\newcommand{\bb}[1]{\mathbb{#1}} +\renewcommand{\bf}[1]{\mathbf{#1}} + +% \cA … \cZ +\newcommand{\cA}{\mathcal{A}} +\newcommand{\cB}{\mathcal{B}} +\newcommand{\cC}{\mathcal{C}} +\newcommand{\cD}{\mathcal{D}} +\newcommand{\cE}{\mathcal{E}} +\newcommand{\cF}{\mathcal{F}} +\newcommand{\cG}{\mathcal{G}} +\newcommand{\cH}{\mathcal{H}} +\newcommand{\cI}{\mathcal{I}} +\newcommand{\cJ}{\mathcal{J}} +\newcommand{\cK}{\mathcal{K}} +\newcommand{\cL}{\mathcal{L}} +\newcommand{\cM}{\mathcal{M}} +\newcommand{\cN}{\mathcal{N}} +\newcommand{\cO}{\mathcal{O}} +\newcommand{\cP}{\mathcal{P}} +\newcommand{\cQ}{\mathcal{Q}} +\newcommand{\cR}{\mathcal{R}} +\newcommand{\cS}{\mathcal{S}} +\newcommand{\cT}{\mathcal{T}} +\newcommand{\cU}{\mathcal{U}} +\newcommand{\cV}{\mathcal{V}} +\newcommand{\cW}{\mathcal{W}} +\newcommand{\cX}{\mathcal{X}} +\newcommand{\cY}{\mathcal{Y}} +\newcommand{\cZ}{\mathcal{Z}} + +% \bA … \bZ +\newcommand{\bA}{\mathbb{A}} +\newcommand{\bB}{\mathbb{B}} +\newcommand{\bC}{\mathbb{C}} +\newcommand{\bD}{\mathbb{D}} +\newcommand{\bE}{\mathbb{E}} +\newcommand{\bF}{\mathbb{F}} +\newcommand{\bG}{\mathbb{G}} +\newcommand{\bH}{\mathbb{H}} +\newcommand{\bI}{\mathbb{I}} +\newcommand{\bJ}{\mathbb{J}} +\newcommand{\bK}{\mathbb{K}} +\newcommand{\bL}{\mathbb{L}} +\newcommand{\bM}{\mathbb{M}} +\newcommand{\bN}{\mathbb{N}} +\newcommand{\bO}{\mathbb{O}} +\newcommand{\bP}{\mathbb{P}} +\newcommand{\bQ}{\mathbb{Q}} +\newcommand{\bR}{\mathbb{R}} +\newcommand{\bS}{\mathbb{S}} +\newcommand{\bT}{\mathbb{T}} +\newcommand{\bU}{\mathbb{U}} +\newcommand{\bV}{\mathbb{V}} +\newcommand{\bW}{\mathbb{W}} +\newcommand{\bX}{\mathbb{X}} +\newcommand{\bY}{\mathbb{Y}} +\newcommand{\bZ}{\mathbb{Z}} + +% Notation for lambda abstractions and function application. $~$ +% is the spacing that should be used between arguments. +\newcommand{\lam}[2]{\lambda #1.\ #2} + +% Notation for paths, the interval, and its operations. +\newcommand{\PathP}[3]{\rm{PathP}~ {#1}~ {#2}~ {#3}} +\newcommand{\Path}[3]{\rm{Path}~ {#1}~ {#2}~ {#3}} +\newcommand{\Square}[4]{\rm{Square}~ {#1}~ {#2}~ {#3}~ {#4}} +\newcommand{\ap}[2]{\rm{ap}~ {#1}~ {#2}} + +\newcommand{\subst}{\operatorname{subst}} +\newcommand{\ua}{\operatorname{ua}} + +\newcommand{\iZ}{\rm{i0}} +\newcommand{\iO}{\rm{i1}} + +\newcommand{\ineg}{\lnot} +\newcommand{\imin}{\land} +\newcommand{\imax}{\lor} + +\newcommand{\transport}[2]{\rm{transport}~ #1~ #2} +\newcommand{\transp}[3]{\rm{transp}~ #1~ #2~ #3} +\newcommand{\Partial}[2]{\rm{Partial}~ #1~ #2} +\newcommand{\dcomp}{\mathrel{\cdot\cdot}} +\newcommand{\Extn}[2]{{#1}[#2]} + +\newcommand{\thecat}[1]{\mathbf{#1}} % Names of "concrete" categories +\newcommand{\ca}[1]{\mathcal{#1}} % Names of variable categories + +%% \newcommand{\knowncat}[1]{\newcommand{\csname #1\endcsname}{\thecat{#1}}} +%% \newcommand{\knownbicat}[1]{\newcommand{\csname #1\endcsname}{\thebicat{#1}}} + +\newcommand{\ty}{\rm{Type}} +%% \newcommand{\set}{\rm{Set}} +\newcommand{\prop}{\rm{Prop}} + +%% \DeclareMathOperator{\id}{id} +%% \DeclareMathOperator{\Id}{Id} + +%% \knowncat{Sets} +%% \knowncat{Ab} +%% \knowncat{Graphs} +%% \knowncat{MarkedGraphs} +%% \knowncat{Props} +%% \knowncat{FinSets} +%% \knowncat{Rings} +%% \knowncat{Grp} +%% \knowncat{Rel} +%% \knowncat{Par} +%% \knowncat{Pos} + +\renewcommand{\Set}{\thecat{Set}} + +% "Postfix" operators that have a subscript and are annoying to write +% out: +\newcommand{\inv}{^{-1}} +\renewcommand{\op}{^{\rm{op}}} + +\newcommand{\eps}{\varepsilon} + +\newcommand{\B}[1]{\mathbf{B} #1} +\newcommand{\point}[1]{\bullet_{#1}} +\newcommand{\List}[1]{\operatorname{List}(#1)} + +\renewcommand{\hom}{\mathbf{Hom}} + +\newcommand{\refl}{\mathrm{refl}} + +\DeclareMathOperator{\isiso}{is-iso} +\DeclareMathOperator{\isequiv}{is-equiv} + +% Relations +\newcommand{\pathto}{\equiv} +\newcommand{\is}{\pathto} +\newcommand{\definedto}{≔} +\newcommand{\equivto}{\simeq} +\newcommand{\homotopicto}{\sim} +\newcommand{\naturalto}{\Rightarrow} +\newcommand{\isoto}{\cong} +\newcommand{\monicto}{\rightarrowtail} +\newcommand{\epicto}{\twoheadrightarrow} + +% Category names +\newcommand{\C}[1]{\mathbf{#1}} + +\newcommand{\homset}[3]{{{#1} \left[ {#2} \to {#3} \right]}} +\newcommand{\horizontalcompose}{\ast} diff --git a/doerg/resources/net/deertopia/doerg/tuftesque.css b/doerg/resources/net/deertopia/doerg/tuftesque.css index f1c27de..31925bd 100644 --- a/doerg/resources/net/deertopia/doerg/tuftesque.css +++ b/doerg/resources/net/deertopia/doerg/tuftesque.css @@ -105,8 +105,8 @@ p, dl, ol, ul { - font-size: 1.4rem; - line-height: 2rem; + font-size: 1.2rem; + line-height: 1.5rem; } p { diff --git a/doerg/src/net/deertopia/doerg/common.clj b/doerg/src/net/deertopia/doerg/common.clj new file mode 100644 index 0000000..cc1b4eb --- /dev/null +++ b/doerg/src/net/deertopia/doerg/common.clj @@ -0,0 +1,18 @@ +(ns net.deertopia.doerg.common + (:require [babashka.process :as p] + [clojure.string :as str])) + +(defn deref-with-timeout [process ms] + (let [p (promise) + process-future (future (deliver p @process)) + timeout-future (future (Thread/sleep ms) + (future-cancel process-future) + (p/destroy-tree process) + (deliver p ::timed-out))] + (if (= @p ::timed-out) + (throw (ex-info (format "external command `%s' timed out after %.2fs." + (str/join " " (:cmd process)) + (/ (double ms) 1000)) + {:process process + :timed-out-after-milliseconds ms})) + @p))) diff --git a/doerg/src/net/deertopia/doerg/element.clj b/doerg/src/net/deertopia/doerg/element.clj index 65c1781..632ae6c 100644 --- a/doerg/src/net/deertopia/doerg/element.clj +++ b/doerg/src/net/deertopia/doerg/element.clj @@ -1,5 +1,6 @@ (ns net.deertopia.doerg.element (:require [babashka.process :as p] + [net.deertopia.doerg.common :as common] [clojure.string :as str] [clojure.zip] [babashka.fs :as fs] @@ -16,24 +17,11 @@ (defonce ^:private uniorg-script-path-atom (atom nil)) -(def ^:dynamic *uniorg-timeout-after-milliseconds* +(def ^:dynamic *uniorg-timeout-duration* + "Number of milliseconds to wait before killing the external Uniorg + process." (* 10 1000)) -(defn deref-with-timeout [process ms] - (let [p (promise) - process-future (future (deliver p @process)) - timeout-future (future (Thread/sleep ms) - (future-cancel process-future) - (p/destroy-tree process) - (deliver p ::timed-out))] - (if (= @p ::timed-out) - (throw (ex-info (format "external command `%s' timed out after %.2fs." - (str/join " " (:cmd process)) - (/ (double ms) 1000)) - {:process process - :timed-out-after-milliseconds ms})) - @p))) - (defn- camel->kebab [s] (->> (str/split s #"(?<=[a-z])(?=[A-Z])") (map str/lower-case) @@ -44,7 +32,7 @@ (let [r (-> (p/process {:in in :out :string} "doerg-parser") - (deref-with-timeout *uniorg-timeout-after-milliseconds*))] + (common/deref-with-timeout *uniorg-timeout-duration*))] (if (zero? (:exit r)) (-> r :out (json/parse-string (comp keyword camel->kebab)))))) diff --git a/doerg/src/net/deertopia/doerg/render.clj b/doerg/src/net/deertopia/doerg/render.clj index 89b33b0..77de4e6 100644 --- a/doerg/src/net/deertopia/doerg/render.clj +++ b/doerg/src/net/deertopia/doerg/render.clj @@ -7,7 +7,9 @@ [clojure.tools.logging.readable :as lr] [com.rpl.specter :as sp] [net.deertopia.doerg.html :as doerg-html] + [hiccup2.core :as hiccup] [clojure.pprint] + [net.deertopia.doerg.tex :as tex] [clojure.zip :as z])) ;;; Top-level API @@ -43,15 +45,16 @@ (defn org-element-recursive "Recursively render an Org-mode element to Hiccup." [e] - (->> e - ;; gather-footnotes - (sp/transform + (tex/binding-tex-worker + (->> e + ;; gather-footnotes + (sp/transform [element/postorder-walker view-children-as-seq] (fn [node] (try (org-element node) (catch Throwable e (lr/error e "Error in renderer" {:node node}) - (render-renderer-error e))))))) + (render-renderer-error e)))))))) (defn org-document "Recursively render an Org-mode document to Hiccup." @@ -276,11 +279,16 @@ (defmethod org-element "citation-reference" [{:keys [key]}] (str "@" key)) -(defmethod org-element "latex-fragment" [{:keys [value]}] - [:span.latex-fragment value]) +(defmethod org-element "latex-fragment" [{:keys [contents value] :as e}] + (let [display? (str/starts-with? value "\\[")] + #_ + (render-pprint (assoc e :display? display?)) + [:span.latex-fragment + (hiccup/raw (tex/render contents :display? display?))])) (defmethod org-element "latex-environment" [{:keys [value]}] - [:pre [:code value]]) + [:span.latex-fragment + (hiccup/raw (tex/render value :display? true))]) (defmethod org-element "example-block" [{:keys [value]}] [:pre value]) diff --git a/doerg/src/net/deertopia/doerg/tex.clj b/doerg/src/net/deertopia/doerg/tex.clj new file mode 100644 index 0000000..e7d854f --- /dev/null +++ b/doerg/src/net/deertopia/doerg/tex.clj @@ -0,0 +1,49 @@ +(ns net.deertopia.doerg.tex + (:require [babashka.process :as p] + [net.deertopia.doerg.common :as common] + [clj-cbor.core :as cbor] + [clojure.java.io :as io])) + +(def ^:dynamic *tex-worker-timeout-duration* + "Number of milliseconds to wait before killing the external Uniorg + process." + (* 10 1000)) + +(def ^:dynamic *worker*) + +(defn tex-worker [& {:keys [preamble]}] + (p/process + {:shutdown p/destroy-tree + :err :inherit} + #_"doerg-tex" + "./doerg-tex/index.js" + "--preamble" + "resources/net/deertopia/doerg/prelude.tex")) + +(defn finish [tw] + (.close (:in tw))) + +(defmacro with-tex-worker [tw & body] + `(let [~tw (tex-worker)] + (try + (do ~@body) + (finally + (finish ~tw) + (p/destroy-tree ~tw))))) + +(defmacro binding-tex-worker [& body] + `(binding [*worker* (tex-worker)] + (try + ~@body + (finally + (finish *worker*))))) + +(defn command [x] + (cbor/encode cbor/default-codec (:in *worker*) x) + (.flush (:in *worker*)) + (cbor/decode cbor/default-codec (:out *worker*))) + +(defn render [s & {:keys [display?]}] + (if display? + (command [s]) + (command s))) diff --git a/doerg/test/net/deertopia/doerg/common_test.clj b/doerg/test/net/deertopia/doerg/common_test.clj new file mode 100644 index 0000000..9305aec --- /dev/null +++ b/doerg/test/net/deertopia/doerg/common_test.clj @@ -0,0 +1,32 @@ +(ns net.deertopia.doerg.common-test + (:require [net.deertopia.doerg.common :as sut] + [babashka.process :as p] + [clojure.test :as t])) + +(defn sleep-vs-timeout [& {:keys [sleep timeout]}] + (sut/deref-with-timeout + (p/process "sleep" (format "%ds" sleep)) + (* timeout 1000))) + +;; Ideally we would test the following property: +;; +;; For natural numbers n and m, evaluating the form +;; (sut/deref-with-timeout +;; (p/process "sleep" (format "%ds" n)) +;; (* m 1000)) +;; will throw an exception iff n < m (probably with some margin of +;; error lol). +;; +;; But, this is not something that we want to run dozens-to-hundreds +;; of times. }:p + +(t/deftest long-sleep-vs-short-timeout + (t/testing "long sleep vs. short timeout" + (t/is (thrown-with-msg? + Exception #".*timed out.*" + (sleep-vs-timeout :sleep 5 :timeout 1))))) + +(t/deftest short-sleep-vs-long-timeout + (t/testing "short sleep vs. long timeout" + (t/is (instance? babashka.process.Process + (sleep-vs-timeout :sleep 1 :timeout 5))))) diff --git a/doerg/test/net/deertopia/doerg/element_test.clj b/doerg/test/net/deertopia/doerg/element_test.clj index 981c30b..3406bb1 100644 --- a/doerg/test/net/deertopia/doerg/element_test.clj +++ b/doerg/test/net/deertopia/doerg/element_test.clj @@ -6,34 +6,6 @@ [clojure.java.io :as io] [com.rpl.specter :as sp])) -(defn sleep-vs-timeout [& {:keys [sleep timeout]}] - (sut/deref-with-timeout - (p/process "sleep" (format "%ds" sleep)) - (* timeout 1000))) - -;; Ideally we would test the following property: -;; -;; For natural numbers n and m, evaluating the form -;; (sut/deref-with-timeout -;; (p/process "sleep" (format "%ds" n)) -;; (* m 1000)) -;; will throw an exception iff n < m (probably with some margin of -;; error lol). -;; -;; But, this is not something that we want to run dozens-to-hundreds -;; of times. }:p - -(t/deftest long-sleep-vs-short-timeout - (t/testing "long sleep vs. short timeout" - (t/is (thrown-with-msg? - Exception #".*timed out.*" - (sleep-vs-timeout :sleep 5 :timeout 1))))) - -(t/deftest short-sleep-vs-long-timeout - (t/testing "short sleep vs. long timeout" - (t/is (instance? babashka.process.Process - (sleep-vs-timeout :sleep 1 :timeout 5))))) - (defn- first-child-of-type [parent type] (some #(and (sut/of-type? % type) %) (:children parent))) diff --git a/flake.lock b/flake.lock index fd47f11..39abbbd 100644 --- a/flake.lock +++ b/flake.lock @@ -143,11 +143,46 @@ "type": "github" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1750386251, + "narHash": "sha256-1ovgdmuDYVo5OUC5NzdF+V4zx2uT8RtsgZahxidBTyw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "076e8c6678d8c54204abcb4b1b14c366835a58bb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "clj-nix": "clj-nix", "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_2", + "sydpkgs": "sydpkgs" + } + }, + "sydpkgs": { + "inputs": { + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1767975357, + "narHash": "sha256-MDVh3/aVhkD1bh/r8c0gs9DL4e78CrUbUxOZHWlCwLM=", + "owner": "msyds", + "repo": "sydpkgs", + "rev": "233479ab277d47b1dbda202eafca50e61c659151", + "type": "github" + }, + "original": { + "owner": "msyds", + "repo": "sydpkgs", + "type": "github" } }, "systems": { diff --git a/flake.nix b/flake.nix index d00a376..d7ee44a 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; clj-nix.url = "github:jlesquembre/clj-nix"; - # clj-nix.url = "path:///home/msyds/git/clj-nix"; + sydpkgs.url = "github:msyds/sydpkgs"; }; outputs = { self, nixpkgs, clj-nix, ... }@inputs: @@ -19,6 +19,7 @@ pkgs = import nixpkgs { inherit system; overlays = [ + inputs.sydpkgs.overlays.default clj-nix.overlays.default self.overlays.default ]; @@ -31,7 +32,7 @@ _pkgs = each-system ({ pkgs, ... }: pkgs); packages = each-system ({ pkgs, ... }: { - inherit (pkgs) publisher doerg doerg-parser; + inherit (pkgs) publisher doerg doerg-parser doerg-tex; default = pkgs.publisher; }); @@ -42,7 +43,8 @@ inherit (vendored) ibm-plex-web; publisher = final.callPackage ./publisher/package.nix {}; doerg = final.callPackage ./doerg/package.nix {}; - doerg-parser = final.callPackage ./doerg/doerg-parser/package.nix {}; + doerg-parser = final.callPackage ./doerg/doerg-parser {}; + doerg-tex = final.callPackage ./doerg/doerg-tex {}; }; checks = each-system ({ pkgs, system, ... }: { @@ -54,10 +56,15 @@ devShells = each-system ({ pkgs, system, ... }: { default = pkgs.mkShell { - inputsFrom = [ pkgs.doerg ]; + inputsFrom = [ + pkgs.doerg + pkgs.doerg-parser + pkgs.doerg-tex + ]; packages = with pkgs; [ clojure-lsp doerg-parser + doerg-tex # wahhh ibm-plex-web is a dependency of doerg... why must # i specify it hereeee. # ibm-plex-web