Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI #2

Merged
merged 11 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"packages": [
"packages/*"
],
"version": "1.0.8"
"version": "1.0.11"
}
2,164 changes: 1,687 additions & 477 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,25 @@
"dependencies": {
"@types/color": "^3.0.3",
"@types/cssesc": "^3.0.0",
"@types/text-table": "^0.2.2",
"camelcase": "^6.3.0",
"chokidar": "^3.5.3",
"color": "^4.2.3",
"cssesc": "^3.0.0",
"fault": "^2.0.1",
"hastscript": "^7.0.2",
"json5": "^2.2.1",
"minimist": "^1.2.6",
"peggy": "^1.2.0",
"rehype-format": "^4.0.1",
"text-table": "^0.2.0",
"unified": "^10.1.2",
"unified-engine": "^9.1.0",
"unified-lint-rule": "^2.1.1",
"unist-util-position": "^4.0.3"
"unist-util-position": "^4.0.3",
"vfile-reporter-json": "^3.1.0",
"vfile-reporter-position": "^0.1.7",
"vfile-reporter-pretty": "^6.1.0"
},
"name": "unified-latex"
}
2 changes: 1 addition & 1 deletion packages/structured-clone/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unified-latex/structured-clone",
"version": "1.0.5",
"version": "1.0.11",
"description": "A cheap (and not correct) structured-clone polyfill with types",
"main": "dist/index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/support-tables/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unified-latex/support-tables",
"version": "1.0.8",
"version": "1.0.11",
"description": "Lists of special data needed for the internals of unified-latex",
"type": "module",
"files": [
Expand Down
4 changes: 2 additions & 2 deletions packages/unified-latex-builder/package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "@unified-latex/unified-latex-builder",
"version": "1.0.8",
"version": "1.0.11",
"description": "Tools for constructing unified-latex ASTs",
"main": "dist/index.js",
"type": "module",
"dependencies": {
"@unified-latex/unified-latex-types": "^1.0.7"
"@unified-latex/unified-latex-types": "^1.0.11"
},
"files": [
"dist/**/*.ts",
Expand Down
61 changes: 61 additions & 0 deletions packages/unified-latex-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!-- DO NOT MODIFY -->
<!-- This file was autogenerated by build-docs.ts -->
<!-- Edit the docstring in index.ts and regenerate -->
<!-- rather than editing this file directly. -->
# unified-latex-cli

## What is this?

Command line interface to common `unified-latex` functions.

## When should I use this?

If you want to reformat, process, or gather statistic on LaTeX files from the command line.

## Examples

Reformat and pretty-print a file

```bash
unified-latex input.tex -o output.tex
```

List all commands defined via `\newcommand` and friends (and hide the file output).

```bash
unified-latex input.tex --no-stdout --stats
```

Expand the definition of the macro `\foo{...}`, which takes one argument.

```bash
unified-latex input.tex -e "\\newcommand{foo}[1]{FOO(#1)}"
```

View the parsed AST.

```bash
unified-latex input.tex --inspect
```

Convert the file to HTML. (Note, you will need to include and configure a library like *MathJax* or *KaTeX* to render
any math in the resulting HTML. Warnings are provided for macros that aren't recognized by the converter.)

```bash
unified-latex input.tex -o output.html --html
```

Lint all tex files in the current directory and watch for changes.

```bash
unified-latex . --no-stdout --lint-all --watch
```

## Install

```bash
npm install @unified-latex/unified-latex-cli
```

This package contains both esm and commonjs exports. To explicitly access the esm export,
import the `.js` file. To explicitly access the commonjs export, import the `.cjs` file.
53 changes: 53 additions & 0 deletions packages/unified-latex-cli/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import esbuild from "esbuild";
import fs from "node:fs/promises";

// Automatically exclude all node_modules from the bundled version
import { nodeExternalsPlugin } from "esbuild-node-externals";

(async () => {
const packageJson = JSON.parse(
await fs.readFile(new URL("./package.json", import.meta.url))
);

// We want to externalize modules that are explicitly installed as a dependency
const explicitDeps = Object.keys(packageJson.dependencies || {}).concat([
"node:stream",
"node:process",
]);

const commonConfig = {
entryPoints: ["./index.ts"],
outfile: "./dist/index.js",
bundle: true,
minify: false,
sourcemap: true,
format: "esm",
target: "node14",
plugins: [nodeExternalsPlugin()],
external: [...explicitDeps],
};

// Build the ESM
esbuild.build(commonConfig).catch(() => process.exit(1));

// Build a CommonJS version as well
esbuild
.build({
...commonConfig,
outfile: "./dist/index.cjs",
format: "cjs",
})
.catch(() => process.exit(1));

// Build a standalone version as well
esbuild
.build({
...commonConfig,
outfile: "./dist/unified-latex-cli.mjs",
format: "esm",
banner: {
js: "#!/usr/bin/env node\n",
},
})
.catch(() => process.exit(1));
})();
64 changes: 64 additions & 0 deletions packages/unified-latex-cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { processLatexViaUnified } from "./libs/unified-latex";
import { unifiedArgs } from "./libs/unified-args";

unifiedArgs({
processor: processLatexViaUnified,
name: "unified-latex",
description: "LaTeX processor powered by unified-latex",
version: "1.0.8",
extensions: ["tex"],
ignoreName: ".unifiedlatexignore",
packageField: "unifiedLatexConfig",
rcName: ".unifiedlatexrc",
pluginPrefix: "@unified-latex/",
});

// NOTE: The docstring comment must be the last item in the index.ts file!
/**
* ## What is this?
*
* Command line interface to common `unified-latex` functions.
*
* ## When should I use this?
*
* If you want to reformat, process, or gather statistic on LaTeX files from the command line.
*
* ## Examples
*
* Reformat and pretty-print a file
*
* ```bash
* unified-latex input.tex -o output.tex
* ```
*
* List all commands defined via `\newcommand` and friends (and hide the file output).
*
* ```bash
* unified-latex input.tex --no-stdout --stats
* ```
*
* Expand the definition of the macro `\foo{...}`, which takes one argument.
*
* ```bash
* unified-latex input.tex -e "\\newcommand{foo}[1]{FOO(#1)}"
* ```
*
* View the parsed AST.
*
* ```bash
* unified-latex input.tex --inspect
* ```
*
* Convert the file to HTML. (Note, you will need to include and configure a library like _MathJax_ or _KaTeX_ to render
* any math in the resulting HTML. Warnings are provided for macros that aren't recognized by the converter.)
*
* ```bash
* unified-latex input.tex -o output.html --html
* ```
*
* Lint all tex files in the current directory and watch for changes.
*
* ```bash
* unified-latex . --no-stdout --lint-all --watch
* ```
*/
20 changes: 20 additions & 0 deletions packages/unified-latex-cli/libs/html/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Root } from "hastscript/lib/core";
import rehypeStringify from "rehype-stringify";
import Prettier from "prettier";
import { Plugin, unified } from "unified";

/**
* Plugin that pretty-prints HTML.
*/
export const prettyPrintHtmlPlugin: Plugin<void[], Root, string> = function () {
const processor = unified().use(rehypeStringify);
this.Compiler = (tree, file) => {
file.extname = ".html";

const html = processor.stringify(tree, file);
try {
return Prettier.format(html, { parser: "html", useTabs: true });
} catch {}
return html;
};
};
8 changes: 8 additions & 0 deletions packages/unified-latex-cli/libs/lints/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { lints } from "@unified-latex/unified-latex-lint";

export const availableLints = Object.fromEntries(
Object.values(lints).map((lint) => [
lint.name.replace(/^unified-latex-lint:/, ""),
lint,
])
);
25 changes: 25 additions & 0 deletions packages/unified-latex-cli/libs/macros/attach-macro-args-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as Ast from "@unified-latex/unified-latex-types";
import { Plugin } from "unified";
import { attachMacroArgs } from "@unified-latex/unified-latex-util-arguments";

type PluginOptions = {
macros: { name: string; signature: string }[];
};

/**
* Plugin that attaches the arguments of the specified macros.
*/
export const attachMacroArgsPlugin: Plugin<
PluginOptions[],
Ast.Root,
Ast.Root
> = function (options) {
const { macros = [] } = options || {};
const macroInfo = Object.fromEntries(
macros.map((m) => [m.name, { signature: m.signature }])
);
return (tree) => {
// We need to attach the arguments to each macro before we process it!
attachMacroArgs(tree, macroInfo);
};
};
25 changes: 25 additions & 0 deletions packages/unified-latex-cli/libs/macros/expand-macros-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as Ast from "@unified-latex/unified-latex-types";
import { expandMacros } from "@unified-latex/unified-latex-util-macros";
import { Plugin } from "unified";
import { attachMacroArgs } from "@unified-latex/unified-latex-util-arguments";
import { printRaw } from "@unified-latex/unified-latex-util-print-raw";

type PluginOptions = {
macros: { name: string; signature: string; body: Ast.Node[] }[];
};

/**
* Plugin that expands the specified macros.
*/
export const expandMacrosPlugin: Plugin<PluginOptions[], Ast.Root, Ast.Root> =
function (options) {
const { macros = [] } = options || {};
const macroInfo = Object.fromEntries(
macros.map((m) => [m.name, { signature: m.signature }])
);
return (tree) => {
// We need to attach the arguments to each macro before we process it!
attachMacroArgs(tree, macroInfo);
expandMacros(tree, macros);
};
};
47 changes: 47 additions & 0 deletions packages/unified-latex-cli/libs/macros/parse-macro-expansion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import json5 from "json5";
import * as Ast from "@unified-latex/unified-latex-types";
import {
newcommandMacroToName,
newcommandMacroToSpec,
newcommandMacroToSubstitutionAst,
} from "@unified-latex/unified-latex-util-macros";
import { parse } from "@unified-latex/unified-latex-util-parse";

/**
* Parse a macro specification given on the command line as either a "\newcommand" string
* or a JSON object specifying `name`, `signature`, and `body`.
*/
export function parseMacroExpansion(def: string): {
name: string;
signature: string;
body: Ast.Node[];
} {
if (def.startsWith("\\")) {
const macro = parse(def).content[0] as Ast.Macro;
const name = newcommandMacroToName(macro);
if (!name) {
// If there was no name specified, it must not have been a `\newcommand` or other recognized macro
throw new Error(
`Could extract macro definition from "${def}"; expected the macro to be defined via \\newcommand or similar syntax`
);
}
const signature = newcommandMacroToSpec(macro);
const body = newcommandMacroToSubstitutionAst(macro);

return { name, signature, body };
}
// If it wasn't specified via a `\newcommand` macro, assume it's specified as JSON
const parsedSpec = json5.parse(def);
if (parsedSpec.name == null || parsedSpec.body == null) {
throw new Error(
`Expected a "name" field and a "body" field to be defined on ${def}`
);
}
parsedSpec.signature = parsedSpec.signature || "";

return {
name: parsedSpec.name,
signature: parsedSpec.signature,
body: parse(parsedSpec.body).content,
};
}
Loading