feat: tex rendering via temml

This commit is contained in:
2026-02-16 03:55:07 -07:00
parent a26df4011a
commit 651ed4f26c
19 changed files with 690 additions and 60 deletions

View File

@@ -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"]}

View File

@@ -0,0 +1 @@
#+title: doerg-tex

View File

@@ -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
];
}

55
doerg/doerg-tex/index.js Executable file
View File

@@ -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 ()

View File

@@ -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
;
}

227
doerg/doerg-tex/package-lock.json generated Normal file
View File

@@ -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"
}
}
}
}

View File

@@ -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"}}

View File

@@ -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")

View File

@@ -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}

View File

@@ -105,8 +105,8 @@ p,
dl,
ol,
ul {
font-size: 1.4rem;
line-height: 2rem;
font-size: 1.2rem;
line-height: 1.5rem;
}
p {

View File

@@ -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)))

View File

@@ -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))))))

View File

@@ -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])

View File

@@ -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)))

View File

@@ -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)))))

View File

@@ -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)))

37
flake.lock generated
View File

@@ -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": {

View File

@@ -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