@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 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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](https://www.typescriptlang.org/)
7
+ [![Runtime deps](https://img.shields.io/badge/runtime%20deps-0-success.svg)](#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
+ | [![software engineer](examples/rendered/software-engineer.png)](examples/software-engineer.retex) | [![two column](examples/rendered/two-column.png)](examples/two-column.retex) | [![research paper](examples/rendered/research-paper.png)](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.