@regmisatyam/retex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +492 -0
- package/dist/index.cjs +3444 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +916 -0
- package/dist/index.d.ts +916 -0
- package/dist/index.js +3370 -0
- package/dist/index.js.map +1 -0
- package/dist/react-v8gyKEAs.d.cts +420 -0
- package/dist/react-v8gyKEAs.d.ts +420 -0
- package/dist/react.cjs +805 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +1 -0
- package/dist/react.d.ts +1 -0
- package/dist/react.js +802 -0
- package/dist/react.js.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ReTeX contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# ReTeX
|
|
2
|
+
|
|
3
|
+
**A modern, LaTeX-inspired markup language and compiler for resumes, research papers, and developer portfolios.**
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](#zero-dependencies)
|
|
8
|
+
|
|
9
|
+
ReTeX (a.k.a. **ResumeTeX**) gives you the clean, declarative authoring feel of
|
|
10
|
+
LaTeX without the toolchain. You write a small, readable markup language; ReTeX
|
|
11
|
+
tokenizes, parses, validates, and renders it to **HTML**, **React**, **JSON**, or
|
|
12
|
+
**PDF** — all from a single, dependency-free TypeScript library.
|
|
13
|
+
|
|
14
|
+
```latex
|
|
15
|
+
\name{Ada Lovelace}
|
|
16
|
+
\title{Software Engineer}
|
|
17
|
+
\email{ada@example.com}
|
|
18
|
+
\website{https://ada.dev}
|
|
19
|
+
|
|
20
|
+
\section{Experience}
|
|
21
|
+
\job{title=Senior Engineer, company=Analytical Engines, start=2021, end=Present}{
|
|
22
|
+
Led the design of the first general-purpose compiler. Reduced build times by 40%.
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Preview
|
|
29
|
+
|
|
30
|
+
The three [examples](examples/) rendered to HTML (default, modern, and classic
|
|
31
|
+
themes). Regenerate with `npx tsx examples/build.ts`.
|
|
32
|
+
|
|
33
|
+
| Single column (`default`) | Two column (`modern`) | Academic CV (`classic`) |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| [](examples/software-engineer.retex) | [](examples/two-column.retex) | [](examples/research-paper.retex) |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What ReTeX is — and is not
|
|
40
|
+
|
|
41
|
+
ReTeX **is** a focused markup language for *documents about people and projects*:
|
|
42
|
+
résumés, CVs, academic bio pages, and developer portfolios. It ships a curated
|
|
43
|
+
set of commands (`\name`, `\job`, `\education`, `\skills`, `\section`, …), strong
|
|
44
|
+
theming, and four production-ready renderers.
|
|
45
|
+
|
|
46
|
+
ReTeX **is not** a full LaTeX implementation. There is no math mode, no macro
|
|
47
|
+
programming, no package ecosystem, no `\def`. The syntax is *LaTeX-inspired* —
|
|
48
|
+
familiar braces and backslash commands — but the language is deliberately small,
|
|
49
|
+
safe, and predictable. (Notably, `%` is a literal percent sign, not a comment;
|
|
50
|
+
see [Syntax](docs/SYNTAX.md).)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- **Typography** — `\textbf`, `\textit`, `\emph`, `\underline`, `\sout`,
|
|
57
|
+
`\textcolor`, `\fontsize`, `\fontfamily`, and scoped switches (`\small`,
|
|
58
|
+
`\large`, `\Large`, `\Huge`, `\bfseries`, `\itshape`, `\ttfamily`).
|
|
59
|
+
- **Hyperlinks** — `\href{url}{label}` and `\url{url}`, with URL sanitization.
|
|
60
|
+
- **Sections** — `\section` and `\subsection`.
|
|
61
|
+
- **Résumé components** — `\name`, `\title`, `\email`, `\phone`, `\location`,
|
|
62
|
+
`\website`, and rich entries `\job`, `\education`, `\project`, `\skills`.
|
|
63
|
+
- **Lists & layout** — `itemize`, `enumerate`, and a flexible `columns`
|
|
64
|
+
environment.
|
|
65
|
+
- **Icons** — inline SVG icon set (`\icon{github}`, `\icon{linkedin}`, …),
|
|
66
|
+
extensible at runtime.
|
|
67
|
+
- **Theming** — four presets (`default`, `modern`, `classic`, `compact`) plus a
|
|
68
|
+
fully data-driven, deep-mergeable custom theme model.
|
|
69
|
+
- **Plugins** — register new commands, environments, icons, renderers, or theme
|
|
70
|
+
patches on a per-engine basis. Two example plugins ship in the box
|
|
71
|
+
(`badgePlugin`, `ratingPlugin`).
|
|
72
|
+
- **Four renderers** — HTML fragments and full documents, a framework-agnostic
|
|
73
|
+
React element tree, a structured JSON AST, and print-ready HTML/PDF.
|
|
74
|
+
- **Editor tooling** — completion, hover docs, semantic tokens, diagnostics,
|
|
75
|
+
formatting, and AST inspection via `EditorService`.
|
|
76
|
+
- **Incremental parsing** — block-level caching for fast live preview.
|
|
77
|
+
- **Security** — every text node and attribute is escaped; every URL is vetted.
|
|
78
|
+
No `eval`, no `Function`, no code execution of source.
|
|
79
|
+
- <a name="zero-dependencies"></a>**Zero runtime dependencies.** React and
|
|
80
|
+
Puppeteer are *optional* peers used only if you opt into React/PDF output.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Install
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install retex
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
React is an **optional peer dependency** — install it only if you use the React
|
|
91
|
+
renderer:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install react
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
PDF export lazily imports `puppeteer` if present; otherwise you can supply your
|
|
98
|
+
own headless-browser launcher (Playwright, a pooled Chromium, etc.) or print the
|
|
99
|
+
generated HTML to PDF in the browser.
|
|
100
|
+
|
|
101
|
+
The package exposes two entry points:
|
|
102
|
+
|
|
103
|
+
| Import path | Contents |
|
|
104
|
+
| --------------- | ----------------------------------------------------- |
|
|
105
|
+
| `retex` | The engine, pipeline, AST utilities, all renderers\*, theming, plugins, security, icons, editor, incremental compiler. |
|
|
106
|
+
| `retex/react` | The React renderer (`ReactRenderer`, `renderReact`). |
|
|
107
|
+
|
|
108
|
+
\* The React renderer is also re-exported from `retex`, but lives behind
|
|
109
|
+
`retex/react` so the core bundle never imports React.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Quick start
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { ReTeXEngine } from "retex";
|
|
117
|
+
|
|
118
|
+
const engine = new ReTeXEngine();
|
|
119
|
+
|
|
120
|
+
const source = String.raw`
|
|
121
|
+
\name{Ada Lovelace}
|
|
122
|
+
\title{Software Engineer}
|
|
123
|
+
\email{ada@example.com}
|
|
124
|
+
|
|
125
|
+
\section{Experience}
|
|
126
|
+
\job{title=Senior Engineer, company=Analytical Engines, start=2021, end=Present}{
|
|
127
|
+
Designed the first general-purpose compiler.
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
// A clean HTML fragment:
|
|
132
|
+
const html = engine.toHtml(source);
|
|
133
|
+
|
|
134
|
+
// …or a complete, styled, standalone page:
|
|
135
|
+
const page = engine.toHtmlDocument(source, { title: "Ada Lovelace — Résumé" });
|
|
136
|
+
|
|
137
|
+
// The stylesheet for the active theme, if you render the fragment yourself:
|
|
138
|
+
const css = engine.styles();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`engine.toHtml` accepts either a source string or a parsed `DocumentNode`, so you
|
|
142
|
+
can parse once and render to several targets.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## A realistic résumé
|
|
147
|
+
|
|
148
|
+
```latex
|
|
149
|
+
%% --- header ---
|
|
150
|
+
\name{Grace Hopper}
|
|
151
|
+
\title{Distinguished Engineer \& Compiler Pioneer}
|
|
152
|
+
\email{grace@example.com}
|
|
153
|
+
\phone{+1 (555) 010-1999}
|
|
154
|
+
\location{Arlington, VA}
|
|
155
|
+
\website{https://gracehopper.dev}
|
|
156
|
+
|
|
157
|
+
\section{Summary}
|
|
158
|
+
Systems engineer with 30+ years building compilers and developer tools.
|
|
159
|
+
Coined the term \emph{debugging}. Reduced batch latency by 60%.
|
|
160
|
+
|
|
161
|
+
\section{Experience}
|
|
162
|
+
\job{title=Distinguished Engineer, company=US Navy, location=Washington, DC, start=1959, end=1986}{
|
|
163
|
+
\begin{itemize}
|
|
164
|
+
\item Led development of \textbf{COBOL}, the first English-like programming language.
|
|
165
|
+
\item Built the first compiler, \textbf{A-0}, decades ahead of its time.
|
|
166
|
+
\end{itemize}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
\job{title=Senior Mathematician, company=Eckert--Mauchly, start=1949, end=1959}{
|
|
170
|
+
Programmed the UNIVAC I; pioneered machine-independent programming.
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
\section{Education}
|
|
174
|
+
\education{school=Yale University, degree=PhD Mathematics, start=1930, end=1934}
|
|
175
|
+
\education{school=Vassar College, degree=BA Mathematics \& Physics, end=1928}
|
|
176
|
+
|
|
177
|
+
\section{Skills}
|
|
178
|
+
\skills{Compilers, COBOL, Systems Programming, Mentorship, Public Speaking}
|
|
179
|
+
|
|
180
|
+
\section{Links}
|
|
181
|
+
\icon{github} \href{https://github.com/gracehopper}{github.com/gracehopper}
|
|
182
|
+
\hspace{1em}
|
|
183
|
+
\icon{linkedin} \href{https://linkedin.com/in/gracehopper}{LinkedIn}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
See the full language reference in **[docs/SYNTAX.md](docs/SYNTAX.md)**.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Compiler pipeline
|
|
191
|
+
|
|
192
|
+
ReTeX is a real compiler with four well-separated stages. Each stage is pure,
|
|
193
|
+
never throws, and threads precise source ranges through so diagnostics map back
|
|
194
|
+
to the exact bytes the author typed.
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
ReTeX source
|
|
198
|
+
│
|
|
199
|
+
▼
|
|
200
|
+
┌──────────────┐ tokens ┌──────────────┐ AST ┌──────────────┐ diagnostics ┌──────────────┐
|
|
201
|
+
│ Tokenizer │ ────────────▶ │ Parser │ ───────────▶ │ Validator │ ──────────────▶ │ Renderers │
|
|
202
|
+
│ (lexer) │ │ (recursive │ │ (semantic │ │ HTML / React │
|
|
203
|
+
│ │ │ descent + │ │ checks) │ │ JSON / PDF │
|
|
204
|
+
│ │ │ recovery) │ │ │ │ │
|
|
205
|
+
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
|
206
|
+
│ │ │ │
|
|
207
|
+
Token[] DocumentNode Diagnostic[] string / element /
|
|
208
|
+
JSON / Uint8Array
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```mermaid
|
|
212
|
+
flowchart LR
|
|
213
|
+
S["ReTeX source"] --> T["Tokenizer<br/>Token[]"]
|
|
214
|
+
T --> P["Parser<br/>(recursive descent + error recovery)"]
|
|
215
|
+
P --> A["DocumentNode (AST)"]
|
|
216
|
+
A --> V["Validator<br/>Diagnostic[]"]
|
|
217
|
+
A --> R{"Renderers"}
|
|
218
|
+
R --> H["HTML"]
|
|
219
|
+
R --> J["JSON"]
|
|
220
|
+
R --> X["React"]
|
|
221
|
+
R --> D["PDF"]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
- **Tokenizer** — `tokenize(source)` → `{ tokens, diagnostics }`. Permissive,
|
|
225
|
+
never throws. Handles ReTeX's resume-friendly lexing (`%` literal, `%%`
|
|
226
|
+
comments, escapes, `\\` line breaks, blank-line paragraph breaks).
|
|
227
|
+
- **Parser** — recursive-descent, fully error-recovering. Consults the
|
|
228
|
+
**command registry** for argument signatures and AST builders, handles scoped
|
|
229
|
+
font switches and `\begin…\end` environments, and emits "did you mean?"
|
|
230
|
+
suggestions for unknown commands.
|
|
231
|
+
- **Validator** — semantic checks over the AST (empty sections, stray `\item`,
|
|
232
|
+
unknown theme colors, …). Pure and optional.
|
|
233
|
+
- **Renderers** — share structuring helpers (`toRegions`, `splitPreamble`,
|
|
234
|
+
`entryParts`) so every target produces the same logical layout.
|
|
235
|
+
|
|
236
|
+
Read the deep dive in **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Rendering
|
|
241
|
+
|
|
242
|
+
### HTML
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
const engine = new ReTeXEngine();
|
|
246
|
+
|
|
247
|
+
engine.toHtml(source); // fragment: <div class="retex-resume">…</div>
|
|
248
|
+
engine.toHtmlDocument(source, { title: "CV" }); // full <!DOCTYPE html> page with <style>
|
|
249
|
+
engine.styles(); // the theme's CSS (for the fragment case)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Or use the stage functions directly:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { tokenize, parse, validate, renderHtml } from "retex";
|
|
256
|
+
|
|
257
|
+
const { ast } = parse(tokenize(source).tokens);
|
|
258
|
+
const diagnostics = validate(ast);
|
|
259
|
+
const html = renderHtml(ast, { classPrefix: "cv" });
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### React
|
|
263
|
+
|
|
264
|
+
The React renderer has **no hard dependency on React** — you pass the JSX
|
|
265
|
+
factory yourself, so it works with React, Preact, or any compatible runtime.
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import React from "react";
|
|
269
|
+
import { ReTeXEngine } from "retex";
|
|
270
|
+
|
|
271
|
+
function Resume({ source }: { source: string }) {
|
|
272
|
+
const engine = React.useMemo(() => new ReTeXEngine(), []);
|
|
273
|
+
const tree = engine.toReact(source, {
|
|
274
|
+
createElement: React.createElement,
|
|
275
|
+
Fragment: React.Fragment,
|
|
276
|
+
});
|
|
277
|
+
return (
|
|
278
|
+
<>
|
|
279
|
+
<style>{engine.styles()}</style>
|
|
280
|
+
{tree as React.ReactNode}
|
|
281
|
+
</>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Standalone, via the dedicated subpath:
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
import React from "react";
|
|
290
|
+
import { renderReact } from "retex/react";
|
|
291
|
+
import { parse, tokenize } from "retex";
|
|
292
|
+
|
|
293
|
+
const { ast } = parse(tokenize(source).tokens);
|
|
294
|
+
const element = renderReact(ast, {
|
|
295
|
+
createElement: React.createElement,
|
|
296
|
+
Fragment: React.Fragment,
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### JSON
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
engine.toJson(source); // pretty-printed AST
|
|
304
|
+
engine.toJson(source, { stripMeta: true }); // drop range/hash for stable diffs
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### PDF
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
// Print-ready, standalone HTML (no browser required):
|
|
311
|
+
const printHtml = engine.toPrintHtml(source, { title: "Résumé" });
|
|
312
|
+
|
|
313
|
+
// A PDF byte buffer (lazily imports puppeteer, or pass your own launcher):
|
|
314
|
+
const pdf: Uint8Array = await engine.toPdf(source);
|
|
315
|
+
|
|
316
|
+
// Bring your own headless browser (e.g. Playwright):
|
|
317
|
+
const pdf2 = await engine.toPdf(source, {
|
|
318
|
+
launch: async () => myPlaywrightBrowserAdapter(),
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Theming
|
|
325
|
+
|
|
326
|
+
Themes are plain data, deep-merged over the default. Pass a full `Theme` or a
|
|
327
|
+
partial patch.
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { ReTeXEngine, modernTheme } from "retex";
|
|
331
|
+
|
|
332
|
+
// Use a preset:
|
|
333
|
+
const a = new ReTeXEngine({ theme: modernTheme });
|
|
334
|
+
|
|
335
|
+
// Or a partial override (deep-merged over the default):
|
|
336
|
+
const b = new ReTeXEngine({
|
|
337
|
+
theme: {
|
|
338
|
+
name: "brand",
|
|
339
|
+
colors: { primary: "#7c3aed", text: "#0f172a" },
|
|
340
|
+
fonts: { heading: '"Space Grotesk", system-ui, sans-serif' },
|
|
341
|
+
sectionStyle: "underline",
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Swap at runtime:
|
|
346
|
+
b.setTheme({ colors: { primary: "#059669" } });
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Presets: `defaultTheme`, `modernTheme`, `classicTheme`, `compactTheme` (also
|
|
350
|
+
available by name via `getTheme("modern")` or the `themes` map). Every theme
|
|
351
|
+
color is exposed as a CSS variable (`--retex-color-<token>`), usable from
|
|
352
|
+
`\themecolor{token}{…}`.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Plugins
|
|
357
|
+
|
|
358
|
+
A plugin can contribute commands, environments, icons, render overrides, and
|
|
359
|
+
theme patches. The simplest case — a custom inline command with a render
|
|
360
|
+
function and no custom AST node:
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
import { ReTeXEngine } from "retex";
|
|
364
|
+
|
|
365
|
+
const engine = new ReTeXEngine();
|
|
366
|
+
|
|
367
|
+
engine.registerCommand({
|
|
368
|
+
name: "badge",
|
|
369
|
+
category: "inline",
|
|
370
|
+
args: [{ kind: "content", name: "label" }],
|
|
371
|
+
summary: "A small inline badge.",
|
|
372
|
+
example: "\\badge{New}",
|
|
373
|
+
render: {
|
|
374
|
+
html: (node, ctx, renderChildren) => {
|
|
375
|
+
const inner = renderChildren((node as any).args[0]?.children ?? []);
|
|
376
|
+
return `<span class="${ctx.cls("badge")}">${inner}</span>`;
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
engine.toHtml("\\badge{Open to work}");
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Or package it as a reusable plugin and install with `.use()`:
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
import { ReTeXEngine, badgePlugin, ratingPlugin } from "retex";
|
|
388
|
+
|
|
389
|
+
const engine = new ReTeXEngine({ plugins: [badgePlugin, ratingPlugin] });
|
|
390
|
+
engine.toHtml("\\badge{New} \\rating{4}");
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
`badgePlugin` is the canonical reference for a plugin that ships both HTML and
|
|
394
|
+
React renderers; `ratingPlugin` shows a `string` argument with an HTML-only
|
|
395
|
+
override. See **[docs/API.md](docs/API.md)** for the full `ReTeXPlugin` contract.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Editor integration
|
|
400
|
+
|
|
401
|
+
`EditorService` provides everything a Monaco/CodeMirror/LSP integration needs.
|
|
402
|
+
All methods are pure and synchronous.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { EditorService } from "retex";
|
|
406
|
+
|
|
407
|
+
const editor = new EditorService();
|
|
408
|
+
|
|
409
|
+
editor.getCompletions(source, offset); // CompletionItem[] (commands, envs, fields)
|
|
410
|
+
editor.getHover(source, offset); // HoverInfo | null (markdown docs)
|
|
411
|
+
editor.getSemanticTokens(source); // SemanticToken[] (highlighting)
|
|
412
|
+
editor.getDiagnostics(source); // Diagnostic[] (errors/warnings/hints)
|
|
413
|
+
editor.format(source); // canonical, re-printed source
|
|
414
|
+
editor.inspect(source); // DocumentNode (for debugging)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Diagnostics carry stable codes (e.g. `RTX2001` for an unknown command) plus
|
|
418
|
+
optional quick-fixes, so editors can wire up code actions and doc links.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Security
|
|
423
|
+
|
|
424
|
+
ReTeX renders untrusted markup, so safety is built into every layer:
|
|
425
|
+
|
|
426
|
+
- **HTML escaping** — every text node and attribute value is escaped
|
|
427
|
+
(`escapeHtml`, `escapeAttribute`).
|
|
428
|
+
- **URL sanitization** — `sanitizeUrl` allow-lists safe protocols
|
|
429
|
+
(`http`, `https`, `mailto`, `tel`, `ftp`, `sms`), strips obfuscating control
|
|
430
|
+
characters, and blocks `javascript:`, `data:`, `vbscript:`, `file:` and
|
|
431
|
+
friends. URLs are vetted at parse time *and* re-checked at render time.
|
|
432
|
+
- **CSS value vetting** — colors (`isSafeColor`) and dimensions
|
|
433
|
+
(`isSafeDimension`) are validated before they reach a `style` attribute, so
|
|
434
|
+
no `expression(...)` or `url(javascript:…)` can be smuggled in.
|
|
435
|
+
- **No code execution** — the engine never uses `eval`, `Function`, or
|
|
436
|
+
template-string interpolation of user input into executable contexts.
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Performance & incremental parsing
|
|
441
|
+
|
|
442
|
+
The engine caches compiled documents (LRU, configurable via `cacheSize`). For
|
|
443
|
+
live editing, the `IncrementalCompiler` splits the document into balanced blocks
|
|
444
|
+
at blank-line boundaries and caches each block by its exact text — so editing one
|
|
445
|
+
paragraph re-parses only that block while the rest are served from cache and
|
|
446
|
+
cheaply re-positioned.
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
import { IncrementalCompiler } from "retex";
|
|
450
|
+
|
|
451
|
+
const inc = new IncrementalCompiler();
|
|
452
|
+
const { ast, diagnostics, stats } = inc.compile(source);
|
|
453
|
+
// stats: { segments, cacheHits, cacheMisses }
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Scripts
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
npm run build # bundle with tsup (ESM + CJS + d.ts)
|
|
462
|
+
npm run typecheck # tsc --noEmit
|
|
463
|
+
npm test # run the test suite (vitest)
|
|
464
|
+
npm run test:watch # watch mode
|
|
465
|
+
npm run test:cov # coverage
|
|
466
|
+
npm run bench # performance benchmarks
|
|
467
|
+
npm run lint # eslint
|
|
468
|
+
npm run format # prettier
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Documentation
|
|
474
|
+
|
|
475
|
+
- **[docs/SYNTAX.md](docs/SYNTAX.md)** — the complete language reference: every
|
|
476
|
+
command and environment, plus ReTeX's lexing rules.
|
|
477
|
+
- **[docs/API.md](docs/API.md)** — the public TypeScript API, grouped by area.
|
|
478
|
+
- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — how the compiler works,
|
|
479
|
+
end to end, and how to extend it.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Contributing
|
|
484
|
+
|
|
485
|
+
Contributions are welcome. The codebase is strict TypeScript with a layered
|
|
486
|
+
architecture (`tokenizer` → `parser` → `validator` → `renderers`) and broad test
|
|
487
|
+
coverage. Please run `npm run typecheck`, `npm test`, and `npm run lint` before
|
|
488
|
+
opening a PR.
|
|
489
|
+
|
|
490
|
+
## License
|
|
491
|
+
|
|
492
|
+
[MIT](LICENSE) © ReTeX contributors.
|