From c8fe6150ff8fbbb8a76b9977620bb709adcdd15a Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Mon, 19 Feb 2024 12:04:13 -0500 Subject: [PATCH 1/5] Add examples --- examples/README.md | 11 ++ examples/count-macros.ts | 59 +++++++++ examples/custom-macros.ts | 47 ++++++++ examples/expanding-or-replacing-macros.ts | 139 ++++++++++++++++++++++ examples/ignore-defaults.ts | 59 +++++++++ examples/using-unified.ts | 127 ++++++++++++++++++++ 6 files changed, 442 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/count-macros.ts create mode 100644 examples/custom-macros.ts create mode 100644 examples/expanding-or-replacing-macros.ts create mode 100644 examples/ignore-defaults.ts create mode 100644 examples/using-unified.ts diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..4fe6f538 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +# Examples + +Here you can find annotated examples of how to use `unified-latex` to accomplish common tasks. The examples can be run +with `npx vite-node `. + +- `count-macros.ts` - goes through the basics of parsing source to a `unified-latex` AST and walking the tree to gather + information about its contents. +- `custom-macros.ts` - shows how to add your own macros to the parse process. +- `ignore-defaults.ts` - shows how to parse a string without including any default packages (not even LaTeX2e standard ones). +- `expanding-or-replacing-macros.ts` - shows how to expand or replace macros present in an AST. +- `using-unified.ts` - shows how to use `unified` in combination with `unified-latex` to build a processing pipeline. diff --git a/examples/count-macros.ts b/examples/count-macros.ts new file mode 100644 index 00000000..87a97509 --- /dev/null +++ b/examples/count-macros.ts @@ -0,0 +1,59 @@ +/** + * This example shows how to count macros in a tex string and print out statistics. + */ +import { getParser } from "@unified-latex/unified-latex-util-parse"; +import { visit } from "@unified-latex/unified-latex-util-visit"; +import { anyMacro } from "@unified-latex/unified-latex-util-match"; +import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; + +const TEX_SOURCE = String.raw` +This is \textbf{an} example of a \LaTeX{} document with \textit{some} macros. +\[ + e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!}. +\] +What an \textit{\textbf{amazing}} formula! +`; + +// The quickest way to get started is to create a parser with `getParser`. +const parser = getParser(); +const ast = parser.parse(TEX_SOURCE); + +const macroInfo: Record = {}; +const mathMacros: string[] = []; + +visit(ast, (node, info) => { + if (!anyMacro(node)) { + return; + } + // If we're here, we are a macro node. + macroInfo[node.content] = macroInfo[node.content] || []; + // `printRaw` will print `node` (and its content) without any formatting. + macroInfo[node.content].push(printRaw(node)); + + // `info.context` contains information about where in the parse tree we currently are. + if (info.context.inMathMode) { + // Save just the macro "name". + mathMacros.push(node.content); + } +}); + +// Prints +// +// ``` +// Macro statistics: +// +// All macros: { +// textbf: [ '\\textbf{an}', '\\textbf{amazing}' ], +// LaTeX: [ '\\LaTeX' ], +// textit: [ '\\textit{some}', '\\textit{\\textbf{amazing}}' ], +// '^': [ '^{x}', '^{\\infty}', '^{n}' ], +// sum: [ '\\sum' ], +// _: [ '_{n=0}' ], +// infty: [ '\\infty' ], +// frac: [ '\\frac{x^{n}}{n!}' ] +// } +// Math mode macros: [ '^', 'sum', '_', '^', 'infty', 'frac', '^' ] +// ``` +console.log("Macro statistics:\n"); +console.log("All macros:", macroInfo); +console.log("Math mode macros:", mathMacros); diff --git a/examples/custom-macros.ts b/examples/custom-macros.ts new file mode 100644 index 00000000..b48bd028 --- /dev/null +++ b/examples/custom-macros.ts @@ -0,0 +1,47 @@ +/** + * This example shows how include your own macros for parsing. + */ +import { unified } from "unified"; +import { unifiedLatexFromString } from "@unified-latex/unified-latex-util-parse"; +import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; + +const TEX_SOURCE = String.raw` +My \textbf{custom} \abc{macro}. +`; + +// The default parser for `unified-latex` recognizes macros coming from several packages (those listed in `unified-latex-ctan/package`), +// but you may want to add your own macros to the parsing pipeline. + +// Parser with defaults +const processor1 = unified().use(unifiedLatexFromString); +const ast1 = processor1.parse(TEX_SOURCE); +// Prints `\textbf{custom} \abc`. Notice how the argument of `\xxx` is not included. +console.log(printRaw(ast1.content[2]), printRaw(ast1.content[4])); + +/** + * Adding a custom macro specification + */ + +// We can pass in custom macro (and environment) specifications to the parser. +const processor2 = unified().use(unifiedLatexFromString, { + // We define the macro `\abc` to take one mandatory argument. The `signature` is specified + // using the syntax of the `xparse` package: https://ctan.org/pkg/xparse + macros: { abc: { signature: "m" } }, +}); +const ast2 = processor2.parse(TEX_SOURCE); +// Prints `\textbf{custom} \abc{macro}`. Notice how the argument of `\abc` is included. +console.log(printRaw(ast2.content[2]), printRaw(ast2.content[4])); + +// Any specification you add take precedence over the built in ones. +const processor3 = unified().use(unifiedLatexFromString, { + macros: { textbf: { signature: "" }, abc: { signature: "m" } }, +}); +const ast3 = processor3.parse(TEX_SOURCE); +// Prints `\textbf \abc{macro}`. +// Notice how the argument of `\textbf` is not included. Te index of `\abc` also changed +// because there are additional nodes (since `\textbf` didn't take its argument). +console.log( + printRaw(ast3.content[2]), + printRaw(ast3.content[5]), + printRaw(ast3.content[4]) +); diff --git a/examples/expanding-or-replacing-macros.ts b/examples/expanding-or-replacing-macros.ts new file mode 100644 index 00000000..b5814902 --- /dev/null +++ b/examples/expanding-or-replacing-macros.ts @@ -0,0 +1,139 @@ +/** + * This example shows how expand or replace macros. + */ +import { unified, Plugin } from "unified"; +import { unifiedLatexFromString } from "@unified-latex/unified-latex-util-parse"; +import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; +import { replaceNode } from "@unified-latex/unified-latex-util-replace"; +import { match } from "@unified-latex/unified-latex-util-match"; +import { visit } from "@unified-latex/unified-latex-util-visit"; +import { + expandMacrosExcludingDefinitions, + listNewcommands, +} from "@unified-latex/unified-latex-util-macros"; +import { attachMacroArgs } from "@unified-latex/unified-latex-util-arguments"; +import * as Ast from "@unified-latex/unified-latex-types"; +import { unifiedLatexStringCompiler } from "@unified-latex/unified-latex-util-to-string"; + +const TEX_SOURCE = String.raw` +\newcommand{\abc}[1]{ABC} +My \textbf{custom} \abc{macro}. +`; + +/** + * Replacing macros with `replaceNode` + */ + +// A common task is to replace a macro with some other content. One way to do this is with `replaceNode`. + +const processor1 = unified().use(unifiedLatexFromString, { + // Parse `\abc` as taking a mandatory argument. + macros: { abc: { signature: "m" } }, +}); +const ast1 = processor1.parse(TEX_SOURCE); +// Prints: `\newcommand{\abc}[1]{ABC} My \textbf{custom} \abc{macro}.` +console.log(printRaw(ast1)); + +replaceNode(ast1, (node) => { + if (match.macro(node, "newcommand")) { + // Remove any `\newcommand` macros from the tree. + return null; + } + if (match.macro(node, "abc")) { + // Replace `\abc` with `ABC`. + return { type: "string", content: "ABC" }; + } +}); +// Prints: ` My \textbf{custom} ABC.` +console.log(printRaw(ast1)); + +/** + * Replacing macros only in math mode + */ + +// Using the `info` object, you can get extra information about context before replacing a node. +const ast2 = processor1.parse(String.raw`\abc{fun} $x=\abc{times}$`); +replaceNode(ast2, (node, info) => { + if (info.context.inMathMode && match.macro(node, "abc")) { + // Replace `\abc` with `ABC` only in math mode. + return { type: "string", content: "ABC" }; + } +}); +// Prints: `\abc{fun} $x=ABC$` +console.log(printRaw(ast2)); + +/** + * Replacing during `visit` + */ + +// `replaceNode` is really just a wrapper around `visit`. You can use `visit` directly to replace nodes. +const ast3 = processor1.parse(TEX_SOURCE); +visit(ast3, (node, info) => { + if (match.macro(node, "newcommand")) { + // Replace `\newcommand` with the empty string. + // `replaceNode` actually _removes_ nodes from the tree, which we could do too, + // but it would involve quite a bit more work. + + // We are directly manipulating a node and changing its type, + // TypeScript doesn't like this, so we have to do some casting. + node = node as unknown as Ast.String; + node.type = "string"; + node.content = ""; + } + if (match.macro(node, "abc")) { + // Replace `\abc` with `ABC`. + + // We are directly manipulating a node and changing its type, + // TypeScript doesn't like this, so we have to do some casting. + node = node as unknown as Ast.String; + node.type = "string"; + node.content = "ABC"; + } +}); +// Prints: ` My \textbf{custom} ABC.` +console.log(printRaw(ast3)); + +/** + * Expanding `\newcommand` directly + */ + +// We can expand macros defined via `\newcommand`, `\NewDocumentCommand`, etc. by creating a plugin. + +/** + * Plugin that expands the specified macros by name. These macros must be defined in the document via + * `\newcommand...` or equivalent. + */ +export const expandDocumentMacrosPlugin: Plugin = + function () { + return (tree) => { + const newcommands = listNewcommands(tree); + + const macroInfo = Object.fromEntries( + newcommands.map((m) => [m.name, { signature: m.signature }]) + ); + // We need to attach the arguments to each macro before we process it! + attachMacroArgs(tree, macroInfo); + // We want to expand all macros, except ones mentioned in actual `\newcommand` commands. + expandMacrosExcludingDefinitions(tree, newcommands); + + // Finally, let's remove the `\newcommand`s from the tree. + // Our document could have used `\newcommand` or `\NewDocumentCommand`, etc. We will remove + // all of these. + const newcommandsUsed = Object.fromEntries( + newcommands.map((x) => [x.definition.content, true]) + ); + replaceNode(tree, (node) => { + if (match.anyMacro(node) && newcommandsUsed[node.content]) { + return null; + } + }); + }; + }; + +const processor4 = unified() + .use(unifiedLatexFromString) + .use(expandDocumentMacrosPlugin) + .use(unifiedLatexStringCompiler, { pretty: true }); +const processed4 = processor4.processSync(TEX_SOURCE); +// Prints: ` My \textbf{custom} ABC.` +console.log(String(processed4)); diff --git a/examples/ignore-defaults.ts b/examples/ignore-defaults.ts new file mode 100644 index 00000000..f2615433 --- /dev/null +++ b/examples/ignore-defaults.ts @@ -0,0 +1,59 @@ +/** + * This example shows how ignore all default parsing and use exclusively custom macros. + */ +import { unified } from "unified"; +import { + unifiedLatexAstComplier, + unifiedLatexFromStringMinimal, + unifiedLatexProcessMacrosAndEnvironmentsWithMathReparse, +} from "@unified-latex/unified-latex-util-parse"; +import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; +import { macros as xcolorMacros } from "@unified-latex/unified-latex-ctan/package/xcolor"; +import { Root } from "@unified-latex/unified-latex-types"; + +const TEX_SOURCE = String.raw` +My \textbf{custom} \abc{macro}. +`; + +// The default parser for `unified-latex` recognizes macros coming from several packages (those listed in `unified-latex-ctan/package`), +// but your use case may involve only custom macros (or you may want the speed boost of not processing many macros). +// Parsing with `unifiedLatexFromStringMinimal` parses a string into its "most abstract" form, where no macro arguments are attached. +// This means that a string like `\textbf{foo}` will be parsed as the macro `\textbf` followed by the group containing `foo`. + +// Parser with defaults +const processor1 = unified().use(unifiedLatexFromStringMinimal); +const ast1 = processor1.parse(TEX_SOURCE); +// Prints `\textbf \abc`. Notice how `\xxx` is at position 3 (instead of 2 like in `custom-macros.ts`). +// This is because `unifiedLatexFromStringMinimal` doesn't trim any leading or trailing whitespace. +console.log(printRaw(ast1.content[3]), printRaw(ast1.content[6])); + +// You may want to process a string as if it were in math mode. This can be done by setting `mode: "math"` in the parser options. +const processor2 = unified().use(unifiedLatexFromStringMinimal, { + mode: "math", +}); +const ast2 = processor2.parse(`x^2`); +// Prints `^`. +console.log(printRaw(ast2.content[1])); + +/** + * Using specific packages + */ + +// We can build a parsing pipeline that only recognizes macros from specific packages. +const processor3 = unified() + .use(unifiedLatexFromStringMinimal) + // We could manually use `attachMacroArgs` and write a custom plugin, + // but the `unifiedLatexProcessMacrosAndEnvironmentsWithMathReparse` is already here for us. + // It will also reparse the content of custom "math" environments so their content is in math mode. + // (Ths is how `\begin{equation}...\end{equation}` end up with their contents parsed in math mode.) + .use(unifiedLatexProcessMacrosAndEnvironmentsWithMathReparse, { + // Only process macros from the `xcolor` package. + macros: xcolorMacros, + environments: {}, + }) + .use(unifiedLatexAstComplier); +const processed3 = processor3.processSync(String.raw`\color{blue}\textbf{foo}`) + .result as Root; +// Print the parsed AST with a space between each node. +// Prints `\color{blue} \textbf {foo}`. +console.log(processed3.content.map((c) => printRaw(c)).join(" ")); diff --git a/examples/using-unified.ts b/examples/using-unified.ts new file mode 100644 index 00000000..c8afc72f --- /dev/null +++ b/examples/using-unified.ts @@ -0,0 +1,127 @@ +/** + * This example shows how to build a parser with the `unified` library. + */ +import { unified } from "unified"; +import { + unifiedLatexAstComplier, + unifiedLatexFromString, +} from "@unified-latex/unified-latex-util-parse"; +import { unifiedLatexStringCompiler } from "@unified-latex/unified-latex-util-to-string"; +import { visit } from "@unified-latex/unified-latex-util-visit"; +import { match } from "@unified-latex/unified-latex-util-match"; +import type { Root } from "@unified-latex/unified-latex-types"; + +const TEX_SOURCE = String.raw` +This is \textbf{an} example of a \LaTeX{} document with \textit{some} macros. +\[ + e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!}. +\] +What an \textit{\textbf{amazing}} formula! +`; + +// The `unified` framework runs in three steps: +// 1. Parse a string to an AST. +// 2. Transform the AST. +// 3. Compile the AST to a final format (normally a string). +// We can interface with `unified` at any of these steps. + +/** + * Basic Parsing + */ + +// This processor can only parse (step 1). +const processor1 = unified().use(unifiedLatexFromString); +const ast1 = processor1.parse(TEX_SOURCE); +// Prints `{ type: "macro", content: "textbf", ... }` +console.log(ast1.content[4]); + +/** + * Parsing and Transforming + */ + +// We can add a transformer to the processor to transform the AST (step 2). +// These take the form of `unified` plugins. +const processor2 = unified() + .use(unifiedLatexFromString) + .use(function transformer() { + return (ast) => { + visit(ast, (node) => { + if (match.macro(node, "textbf")) { + // Change all `\textbf` macros into `\emph` macros. + node.content = "emph"; + } + }); + }; + }); +// To use the transformer, `processor2` must be called in two steps. +const ast2 = processor2.parse(TEX_SOURCE); +// `processor2.run` executes all the transformer plugins. These operations mutate +// the source. +processor2.run(ast2); +// Prints `{ type: "macro", content: "emph", ... }` +console.log(ast2.content[4]); + +/** + * Parsing, Transforming, and Stringifying + */ + +// If we want unified to run all steps together, we need to provide a _compiler_ plugin. +const processor3 = unified() + .use(unifiedLatexFromString) + // Same transformer as before. + .use(function transformer() { + return (ast) => { + visit(ast, (node) => { + if (match.macro(node, "textbf")) { + // Change all `\textbf` macros into `\emph` macros. + node.content = "emph"; + } + }); + }; + }) + // When we turn the LaTeX into a string, pretty-print it. + .use(unifiedLatexStringCompiler, { pretty: true }); +const processed3 = processor3.processSync(TEX_SOURCE); +// `processSync` returns a `VFile` object which contains the output string along with +// additional information. Calling `String(...)` ont the `VFile` is the preferred way +// to get the output string. +// +// Prints: +// ``` +// This is \emph{an} example of a \LaTeX{} document with \textit{some} macros. +// \[ +// e^{x} = \sum_{n=0}^{\infty}\frac{x^{n}}{n!}. +// \] +// What an \textit{\emph{amazing}} formula! +// ``` +console.log(String(processed3)); + +/** + * Parsing, Transforming, and _not_ Stringifying + */ + +// Sometimes you wan to use the convenience of `processSync` without the overhead. +// Since `processSync` only runs if all three steps are present in your processor, +// `unified-latex` provides a cheat: `unifiedLatexAstComplier` is a compiler plugin +// that doesn't do anything--just returns the AST as it was. +const processor4 = unified() + .use(unifiedLatexFromString) + // Same transformer as before. + .use(function transformer() { + return (ast) => { + visit(ast, (node) => { + if (match.macro(node, "textbf")) { + // Change all `\textbf` macros into `\emph` macros. + node.content = "emph"; + } + }); + }; + }) + // This processor won't touch the AST + .use(unifiedLatexAstComplier); +const processed4 = processor4.processSync(TEX_SOURCE); +// The AST is stored in the `result` prop of the `VFile`. +// Unfortunately, type information is lost here, but we know it's an `Ast.Root`. +const ast4 = processed4.result as Root; +// Prints `{ type: "macro", content: "emph", ... }` +console.log(ast4.content[4]); From c68457233d0f648a5ca76d5dedcc739ec75f35ce Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Mon, 19 Feb 2024 12:04:41 -0500 Subject: [PATCH 2/5] Prettier --- examples/count-macros.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/count-macros.ts b/examples/count-macros.ts index 87a97509..b9231469 100644 --- a/examples/count-macros.ts +++ b/examples/count-macros.ts @@ -41,7 +41,7 @@ visit(ast, (node, info) => { // // ``` // Macro statistics: -// +// // All macros: { // textbf: [ '\\textbf{an}', '\\textbf{amazing}' ], // LaTeX: [ '\\LaTeX' ], From 93e3fef280b4071bf9bcc0f252d084dc68a51312 Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Mon, 19 Feb 2024 13:13:51 -0500 Subject: [PATCH 3/5] Test examples --- examples/tsconfig.json | 5 + .../__snapshots__/doc-examples.test.ts.snap | 114 ++++++++++++++++++ .../tests/doc-examples.test.ts | 69 +++++++++++ packages/unified-latex-types/package.json | 6 +- tsconfig.test.json | 9 +- 5 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 examples/tsconfig.json create mode 100644 packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap create mode 100644 packages/unified-latex-cli/tests/doc-examples.test.ts diff --git a/examples/tsconfig.json b/examples/tsconfig.json new file mode 100644 index 00000000..78d9d703 --- /dev/null +++ b/examples/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { "rootDir": "./" }, + "include": ["./**/*.ts"], + "extends": "../tsconfig.build.json", +} diff --git a/packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap b/packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap new file mode 100644 index 00000000..ca93c6c4 --- /dev/null +++ b/packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap @@ -0,0 +1,114 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`unified-latex-doc-examples > example count-macros.ts 1`] = ` +"Building in mode: development. + +Macro statistics: + +All macros: { + textbf: [ '\\\\\\\\textbf{an}', '\\\\\\\\textbf{amazing}' ], + LaTeX: [ '\\\\\\\\LaTeX' ], + textit: [ '\\\\\\\\textit{some}', '\\\\\\\\textit{\\\\\\\\textbf{amazing}}' ], + '^': [ '^{x}', '^{\\\\\\\\infty}', '^{n}' ], + sum: [ '\\\\\\\\sum' ], + _: [ '_{n=0}' ], + infty: [ '\\\\\\\\infty' ], + frac: [ '\\\\\\\\frac{x^{n}}{n!}' ] +} +Math mode macros: [ '^', 'sum', '_', '^', 'infty', 'frac', '^' ] +" +`; + +exports[`unified-latex-doc-examples > example custom-macros.ts 1`] = ` +"Building in mode: development. + +\\\\textbf{custom} \\\\abc +\\\\textbf{custom} \\\\abc{macro} +\\\\textbf \\\\abc{macro} +" +`; + +exports[`unified-latex-doc-examples > example expanding-or-replacing-macros.ts 1`] = ` +"Building in mode: development. + +\\\\newcommand{\\\\abc}[1]{ABC} My \\\\textbf{custom} \\\\abc{macro}. + My \\\\textbf{custom} ABC. +\\\\abc{fun} $x=ABC$ + My \\\\textbf{custom} ABC. + My \\\\textbf{custom} ABC. +" +`; + +exports[`unified-latex-doc-examples > example ignore-defaults.ts 1`] = ` +"Building in mode: development. + +\\\\textbf \\\\abc +^ +\\\\color{blue} \\\\textbf {foo} +" +`; + +exports[`unified-latex-doc-examples > example using-unified.ts 1`] = ` +"Building in mode: development. + +{ + type: 'macro', + content: 'textbf', + position: { + source: undefined, + start: { offset: 9, line: 2, column: 9 }, + end: { offset: 16, line: 2, column: 16 } + }, + _renderInfo: { inParMode: true }, + args: [ + { + type: 'argument', + content: [Array], + openMark: '{', + closeMark: '}' + } + ] +} +{ + type: 'macro', + content: 'emph', + position: { + source: undefined, + start: { offset: 9, line: 2, column: 9 }, + end: { offset: 16, line: 2, column: 16 } + }, + _renderInfo: { inParMode: true }, + args: [ + { + type: 'argument', + content: [Array], + openMark: '{', + closeMark: '}' + } + ] +} +This is \\\\emph{an} example of a \\\\LaTeX{} document with \\\\textit{some} macros. +\\\\[ + e^{x} = \\\\sum_{n=0}^{\\\\infty}\\\\frac{x^{n}}{n!}. +\\\\] +What an \\\\textit{\\\\emph{amazing}} formula! +{ + type: 'macro', + content: 'emph', + position: { + source: undefined, + start: { offset: 9, line: 2, column: 9 }, + end: { offset: 16, line: 2, column: 16 } + }, + _renderInfo: { inParMode: true }, + args: [ + { + type: 'argument', + content: [Array], + openMark: '{', + closeMark: '}' + } + ] +} +" +`; diff --git a/packages/unified-latex-cli/tests/doc-examples.test.ts b/packages/unified-latex-cli/tests/doc-examples.test.ts new file mode 100644 index 00000000..be0476f6 --- /dev/null +++ b/packages/unified-latex-cli/tests/doc-examples.test.ts @@ -0,0 +1,69 @@ +/** + * Rus the files in `../../examples`. This test need not be in this directory, but it was a convenient place to put it (2024-02-19). + */ +import { describe, it, expect } from "vitest"; +import util from "util"; +import "../../test-common"; +import { exec as _exec } from "node:child_process"; +import * as path from "node:path"; +import spawn from "cross-spawn"; +import glob from "glob"; + +/* eslint-env jest */ + +// Make console.log pretty-print by default +const origLog = console.log; +console.log = (...args) => { + origLog(...args.map((x) => util.inspect(x, false, 10, true))); +}; + +describe( + "unified-latex-doc-examples", + () => { + const examplesPath = path.resolve(__dirname, "../../../examples"); + const exampleFiles = glob.sync(`${examplesPath}/*.ts`); + for (const exampleFile of exampleFiles) { + it(`example ${exampleFile.split("/").pop()}`, async () => { + const stdout = await executeCommand(`npx`, [ + "vite-node", + exampleFile, + ]); + expect(stdout).toMatchSnapshot(); + }); + } + }, + { + timeout: 60 * 1000, + } +); + +/** + * Run commands with arguments using "cross-spawn", which correctly escapes arguments + * so that end results are the same across different shells. + */ +function executeCommand( + executablePath: string, + args: string[] +): Promise { + return new Promise((resolve, reject) => { + const childProcess = spawn(executablePath, args, { stdio: "pipe" }); + + let stdoutData = ""; + + childProcess.stdout!.on("data", (data) => { + stdoutData += data.toString(); + }); + + childProcess.on("error", (err) => { + reject(err); + }); + + childProcess.on("close", (code) => { + if (code === 0) { + resolve(stdoutData); + } else { + reject(new Error(`Child process exited with code ${code}`)); + } + }); + }); +} diff --git a/packages/unified-latex-types/package.json b/packages/unified-latex-types/package.json index 8254b97b..a5999c86 100644 --- a/packages/unified-latex-types/package.json +++ b/packages/unified-latex-types/package.json @@ -12,11 +12,13 @@ ], "exports": { ".": { - "default": "./index.ts" + "prebuilt": "./dist/index.js", + "import": "./index.ts" }, "./*js": "./dist/*js", "./*": { - "default": "./dist/*.ts" + "prebuilt": "./dist/*.js", + "import": "./*.ts" } }, "scripts": { diff --git a/tsconfig.test.json b/tsconfig.test.json index 92e95d24..c0b42a32 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,7 +1,14 @@ { "extends": "./tsconfig.build.json", "include": ["**/*.ts"], - "exclude": ["**/*.d.ts", "node_modules", "scripts", "*.config.ts"], + "exclude": [ + "**/*.d.ts", + "node_modules", + "scripts", + "*.config.ts", + "examples/**/*", + "test/**/*" + ], "compilerOptions": { "rootDir": "./packages", "paths": { From 735d4506cff1b5a09e484f4830a9aacd4f962f33 Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Mon, 19 Feb 2024 13:15:45 -0500 Subject: [PATCH 4/5] Add reference in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 97c5bf8f..20631d46 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ based on knowledge of special macros. (e.g., some macros are known to take an argument, like `\mathbb`. Such arguments are not detected in the PEG processing stage). +See the [`examples/`](https://github.com/siefkenj/unified-latex/tree/main/examples) folder for usage samples. + ## Development You should develop in each project's subfolder in the `packages/` directory. From 20e137bf1fad44b9d6a22592e076e7598d30b417 Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Mon, 19 Feb 2024 13:28:26 -0500 Subject: [PATCH 5/5] Update snapshots --- .../__snapshots__/doc-examples.test.ts.snap | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap b/packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap index ca93c6c4..2d9b826b 100644 --- a/packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap +++ b/packages/unified-latex-cli/tests/__snapshots__/doc-examples.test.ts.snap @@ -1,9 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`unified-latex-doc-examples > example count-macros.ts 1`] = ` -"Building in mode: development. - -Macro statistics: +"Macro statistics: All macros: { textbf: [ '\\\\\\\\textbf{an}', '\\\\\\\\textbf{amazing}' ], @@ -20,18 +18,14 @@ Math mode macros: [ '^', 'sum', '_', '^', 'infty', 'frac', '^' ] `; exports[`unified-latex-doc-examples > example custom-macros.ts 1`] = ` -"Building in mode: development. - -\\\\textbf{custom} \\\\abc +"\\\\textbf{custom} \\\\abc \\\\textbf{custom} \\\\abc{macro} \\\\textbf \\\\abc{macro} " `; exports[`unified-latex-doc-examples > example expanding-or-replacing-macros.ts 1`] = ` -"Building in mode: development. - -\\\\newcommand{\\\\abc}[1]{ABC} My \\\\textbf{custom} \\\\abc{macro}. +"\\\\newcommand{\\\\abc}[1]{ABC} My \\\\textbf{custom} \\\\abc{macro}. My \\\\textbf{custom} ABC. \\\\abc{fun} $x=ABC$ My \\\\textbf{custom} ABC. @@ -40,18 +34,14 @@ exports[`unified-latex-doc-examples > example expanding-or-replacing-macros.ts 1 `; exports[`unified-latex-doc-examples > example ignore-defaults.ts 1`] = ` -"Building in mode: development. - -\\\\textbf \\\\abc +"\\\\textbf \\\\abc ^ \\\\color{blue} \\\\textbf {foo} " `; exports[`unified-latex-doc-examples > example using-unified.ts 1`] = ` -"Building in mode: development. - -{ +"{ type: 'macro', content: 'textbf', position: {