From 857c5acf130f50be36a8ef726480185cb1f2ee71 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Fri, 9 Aug 2024 16:31:50 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=A3=20Add=20raw=20directives/roles=20f?= =?UTF-8?q?or=20inserting=20tex/typst-specific=20content=20(#1442)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rowan Cockett --- .changeset/empty-rules-grow.md | 10 ++ docs/creating-pdf-documents.md | 14 ++- packages/myst-cli/src/transforms/raw.ts | 1 + packages/myst-directives/src/index.ts | 6 +- packages/myst-directives/src/raw.ts | 56 ++++++++- packages/myst-roles/src/index.ts | 4 + packages/myst-roles/src/raw.ts | 44 ++++++++ packages/myst-spec-ext/src/types.ts | 4 +- packages/myst-to-tex/src/index.ts | 7 ++ packages/myst-to-typst/src/index.ts | 7 ++ packages/mystmd/tests/exports.yml | 10 ++ packages/mystmd/tests/raw/.gitignore | 2 + packages/mystmd/tests/raw/index.md | 18 +++ packages/mystmd/tests/raw/myst.yml | 16 +++ packages/mystmd/tests/raw/outputs/index.json | 113 +++++++++++++++++++ packages/mystmd/tests/raw/outputs/out.tex | 8 ++ packages/mystmd/tests/raw/outputs/out.typ | 7 ++ 17 files changed, 320 insertions(+), 7 deletions(-) create mode 100644 .changeset/empty-rules-grow.md create mode 100644 packages/myst-roles/src/raw.ts create mode 100644 packages/mystmd/tests/raw/.gitignore create mode 100644 packages/mystmd/tests/raw/index.md create mode 100644 packages/mystmd/tests/raw/myst.yml create mode 100644 packages/mystmd/tests/raw/outputs/index.json create mode 100644 packages/mystmd/tests/raw/outputs/out.tex create mode 100644 packages/mystmd/tests/raw/outputs/out.typ diff --git a/.changeset/empty-rules-grow.md b/.changeset/empty-rules-grow.md new file mode 100644 index 000000000..1f1e65505 --- /dev/null +++ b/.changeset/empty-rules-grow.md @@ -0,0 +1,10 @@ +--- +'myst-directives': patch +'myst-spec-ext': patch +'myst-to-typst': patch +'myst-to-tex': patch +'myst-roles': patch +'myst-cli': patch +--- + +Add raw directives/roles for inserting tex/typst-specific content diff --git a/docs/creating-pdf-documents.md b/docs/creating-pdf-documents.md index 277299993..59954cb15 100644 --- a/docs/creating-pdf-documents.md +++ b/docs/creating-pdf-documents.md @@ -207,10 +207,22 @@ exports: Please consider [contributing your template](/jtex/contribute-a-template) to the growing list of templates so that other people can benefit and improve your work! -## Excluding Source +## Excluding Content from Specific Exports If you have a block or notebook cell that you do not want to render to your $\LaTeX$ output, add the `no-tex` tag to the cell. Similarly, to exclude a cell from Typst, use `no-typst`. To exclude a cell from both formats, use `no-pdf`. +## Including Content with Specific Exports + +If you need to inject some $\LaTeX$- or Typst-specific content into their respective exports, you may use the `{raw:latex}` or `{raw:typst}` role and directive. For example, to insert a new page in Typst with two columns: + +````markdown +```{raw:typst} +#set page(columns: 2, margin: (x: 1.5cm, y: 2cm),); +``` +```` + +The content in these directives and roles will be included exactly as written in their respective exports, and will be ignored in all other contexts. + (multi-article-exports)= ## Multi-Article Exports diff --git a/packages/myst-cli/src/transforms/raw.ts b/packages/myst-cli/src/transforms/raw.ts index 790f6255e..438c14bd9 100644 --- a/packages/myst-cli/src/transforms/raw.ts +++ b/packages/myst-cli/src/transforms/raw.ts @@ -10,6 +10,7 @@ import type { PhrasingContent } from 'myst-spec'; export async function rawDirectiveTransform(tree: GenericParent, vfile: VFile) { const rawNodes = selectAll('raw', tree) as Raw[]; rawNodes.forEach((node) => { + if (!node.value) return; if (['latex', 'tex'].includes(node.lang as string)) { const state = new TexParser(node.value, vfile); (node as GenericNode).children = state.ast.children; diff --git a/packages/myst-directives/src/index.ts b/packages/myst-directives/src/index.ts index 0d65a14a4..f6189fe28 100644 --- a/packages/myst-directives/src/index.ts +++ b/packages/myst-directives/src/index.ts @@ -16,7 +16,7 @@ import { mdastDirective } from './mdast.js'; import { mermaidDirective } from './mermaid.js'; import { mystdemoDirective } from './mystdemo.js'; import { blockquoteDirective } from './blockquote.js'; -import { rawDirective } from './raw.js'; +import { rawDirective, rawLatexDirective, rawTypstDirective } from './raw.js'; import { divDirective } from './div.js'; export const defaultDirectives = [ @@ -43,6 +43,8 @@ export const defaultDirectives = [ mermaidDirective, mystdemoDirective, rawDirective, + rawLatexDirective, + rawTypstDirective, divDirective, ]; @@ -63,5 +65,5 @@ export { mdastDirective } from './mdast.js'; export { mermaidDirective } from './mermaid.js'; export { mystdemoDirective } from './mystdemo.js'; export { blockquoteDirective } from './blockquote.js'; -export { rawDirective } from './raw.js'; +export { rawDirective, rawLatexDirective, rawTypstDirective } from './raw.js'; export { divDirective } from './div.js'; diff --git a/packages/myst-directives/src/raw.ts b/packages/myst-directives/src/raw.ts index bd77fa807..ad884fbf0 100644 --- a/packages/myst-directives/src/raw.ts +++ b/packages/myst-directives/src/raw.ts @@ -3,7 +3,7 @@ import type { Raw } from 'myst-spec-ext'; export const rawDirective: DirectiveSpec = { name: 'raw', - doc: 'Allows you to include the source or parsed version of a separate file into your document tree.', + doc: 'Allows you to include non-markdown text in your document. If the argument "latex" is provided, the text will be parsed as latex; otherwise, it will be included as raw text.', arg: { type: String, doc: 'Format of directive content - for now, only "latex" is valid', @@ -13,11 +13,61 @@ export const rawDirective: DirectiveSpec = { doc: 'Raw content to be parsed', }, run(data): Raw[] { + const lang = (data.arg as string) ?? ''; + const value = (data.body as string) ?? ''; + const tex = ['tex', 'latex'].includes(lang) ? `\n${value}\n` : undefined; + const typst = ['typst', 'typ'].includes(lang) ? `\n${value}\n` : undefined; return [ { type: 'raw', - lang: (data.arg as string) ?? '', - value: (data.body as string) ?? '', + lang, + tex, + typst, + value, + }, + ]; + }, +}; + +export const rawLatexDirective: DirectiveSpec = { + name: 'raw:latex', + alias: ['raw:tex'], + doc: 'Allows you to include tex in your document that will only be included in tex exports', + body: { + type: String, + doc: 'Raw tex content', + }, + run(data): Raw[] { + const lang = 'tex'; + const body = data.body as string; + const tex = body ? `\n${body}\n` : ''; + return [ + { + type: 'raw', + lang, + tex, + }, + ]; + }, +}; + +export const rawTypstDirective: DirectiveSpec = { + name: 'raw:typst', + alias: ['raw:typ'], + doc: 'Allows you to include typst in your document that will only be included in typst exports', + body: { + type: String, + doc: 'Raw typst content', + }, + run(data): Raw[] { + const lang = 'typst'; + const body = data.body as string; + const typst = body ? `\n${body}\n` : ''; + return [ + { + type: 'raw', + lang, + typst, }, ]; }, diff --git a/packages/myst-roles/src/index.ts b/packages/myst-roles/src/index.ts index 3981c28c8..6345f0039 100644 --- a/packages/myst-roles/src/index.ts +++ b/packages/myst-roles/src/index.ts @@ -15,6 +15,7 @@ import { subscriptRole } from './subscript.js'; import { superscriptRole } from './superscript.js'; import { underlineRole } from './underline.js'; import { keyboardRole } from './keyboard.js'; +import { rawLatexRole, rawTypstRole } from './raw.js'; export const defaultRoles = [ abbreviationRole, @@ -34,6 +35,8 @@ export const defaultRoles = [ superscriptRole, underlineRole, keyboardRole, + rawLatexRole, + rawTypstRole, ]; export { abbreviationRole } from './abbreviation.js'; export { chemRole } from './chem.js'; @@ -51,3 +54,4 @@ export { subscriptRole } from './subscript.js'; export { superscriptRole } from './superscript.js'; export { underlineRole } from './underline.js'; export { keyboardRole } from './keyboard.js'; +export { rawLatexRole, rawTypstRole } from './raw.js'; diff --git a/packages/myst-roles/src/raw.ts b/packages/myst-roles/src/raw.ts new file mode 100644 index 000000000..f315b7ed3 --- /dev/null +++ b/packages/myst-roles/src/raw.ts @@ -0,0 +1,44 @@ +import type { RoleSpec } from 'myst-common'; +import type { Raw } from 'myst-spec-ext'; + +export const rawLatexRole: RoleSpec = { + name: 'raw:latex', + alias: ['raw:tex'], + doc: 'Allows you to include tex in your document that will only be included in tex exports', + body: { + type: String, + doc: 'Raw tex content', + }, + run(data): Raw[] { + const lang = 'tex'; + const tex = (data.body as string) ?? ''; + return [ + { + type: 'raw', + lang, + tex, + }, + ]; + }, +}; + +export const rawTypstRole: RoleSpec = { + name: 'raw:typst', + alias: ['raw:typ'], + doc: 'Allows you to include typst in your document that will only be included in typst exports', + body: { + type: String, + doc: 'Raw typst content', + }, + run(data): Raw[] { + const lang = 'typst'; + const typst = (data.body as string) ?? ''; + return [ + { + type: 'raw', + lang, + typst, + }, + ]; + }, +}; diff --git a/packages/myst-spec-ext/src/types.ts b/packages/myst-spec-ext/src/types.ts index 4817e7216..4a655ffe5 100644 --- a/packages/myst-spec-ext/src/types.ts +++ b/packages/myst-spec-ext/src/types.ts @@ -233,7 +233,9 @@ export type Include = { export type Raw = { type: 'raw'; lang?: string; - value: string; + tex?: string; + typst?: string; + value?: string; children?: (FlowContent | ListContent | PhrasingContent)[]; }; diff --git a/packages/myst-to-tex/src/index.ts b/packages/myst-to-tex/src/index.ts index db511572d..f4443900a 100644 --- a/packages/myst-to-tex/src/index.ts +++ b/packages/myst-to-tex/src/index.ts @@ -455,6 +455,13 @@ const handlers: Record = { state.write('}'); } }, + raw(node, state) { + if (node.tex) { + state.write(node.tex); + } else if (node.children?.length) { + state.renderChildren(node); + } + }, }; class TexSerializer implements ITexSerializer { diff --git a/packages/myst-to-typst/src/index.ts b/packages/myst-to-typst/src/index.ts index 33c8c54b8..78a087573 100644 --- a/packages/myst-to-typst/src/index.ts +++ b/packages/myst-to-typst/src/index.ts @@ -357,6 +357,13 @@ const handlers: Record = { span(node, state) { state.renderChildren(node, 0, { trimEnd: false }); }, + raw(node, state) { + if (node.typst) { + state.write(node.typst); + } else if (node.children?.length) { + state.renderChildren(node, undefined, { trimEnd: false }); + } + }, }; class TypstSerializer implements ITypstSerializer { diff --git a/packages/mystmd/tests/exports.yml b/packages/mystmd/tests/exports.yml index 2715a19ec..66d9e6bac 100644 --- a/packages/mystmd/tests/exports.yml +++ b/packages/mystmd/tests/exports.yml @@ -270,3 +270,13 @@ cases: outputs: - path: citation-cff/CITATION.cff content: citation-cff/outputs/CITATION.cff + - title: Raw directives and roles + cwd: raw + command: myst build --all + outputs: + - path: raw/_build/out.tex + content: raw/outputs/out.tex + - path: raw/_build/out.typ + content: raw/outputs/out.typ + - path: raw/_build/site/content/index.json + content: raw/outputs/index.json diff --git a/packages/mystmd/tests/raw/.gitignore b/packages/mystmd/tests/raw/.gitignore new file mode 100644 index 000000000..a997a7be3 --- /dev/null +++ b/packages/mystmd/tests/raw/.gitignore @@ -0,0 +1,2 @@ +# MyST build outputs +/_build/ \ No newline at end of file diff --git a/packages/mystmd/tests/raw/index.md b/packages/mystmd/tests/raw/index.md new file mode 100644 index 000000000..8cbcaecf5 --- /dev/null +++ b/packages/mystmd/tests/raw/index.md @@ -0,0 +1,18 @@ +# Project with raw content + +This has raw {raw:tex}`LaTeX`{raw:typst}`Typst` content + +```{raw:latex} +\section{LaTeX Heading} +``` +```{raw:typ} += Typst Heading +``` + +```{raw} +This is *just text* +``` + +```{raw} latex +This is \textit{latex} +``` \ No newline at end of file diff --git a/packages/mystmd/tests/raw/myst.yml b/packages/mystmd/tests/raw/myst.yml new file mode 100644 index 000000000..40be0a00e --- /dev/null +++ b/packages/mystmd/tests/raw/myst.yml @@ -0,0 +1,16 @@ +# See docs at: https://mystmd.org/guide/frontmatter +version: 1 +project: + author: Franklin Koch + date: 9 Aug 2024 + exclude: outputs + exports: + - output: _build/out.tex + template: null + - output: _build/out.typ + template: null +site: + template: ../templates/site/myst/book-theme + # options: + # favicon: favicon.ico + # logo: site_logo.png diff --git a/packages/mystmd/tests/raw/outputs/index.json b/packages/mystmd/tests/raw/outputs/index.json new file mode 100644 index 000000000..dea703df0 --- /dev/null +++ b/packages/mystmd/tests/raw/outputs/index.json @@ -0,0 +1,113 @@ +{ + "kind": "Article", + "sha256": "74b7a0f11b2a55264d9aed106453b7e47e4bbcdc5127b6042b508ef30677988e", + "slug": "index", + "location": "/index.md", + "dependencies": [], + "frontmatter": { + "title": "Project with raw content", + "content_includes_title": false, + "authors": [{ "id": "Franklin Koch", "name": "Franklin Koch" }], + "date": "9 Aug 2024", + "exports": [ + { + "format": "md", + "filename": "index.md" + } + ] + }, + "mdast": { + "type": "root", + "children": [ + { + "type": "block", + "children": [ + { + "type": "paragraph", + "position": { "start": { "line": 3, "column": 1 }, "end": { "line": 3, "column": 1 } }, + "children": [ + { + "type": "text", + "value": "This has raw ", + "position": { + "start": { "line": 3, "column": 1 }, + "end": { "line": 3, "column": 1 } + } + }, + { "type": "raw", "lang": "tex", "tex": "LaTeX" }, + { "type": "raw", "lang": "typst", "typst": "Typst" }, + { + "type": "text", + "value": " content", + "position": { + "start": { "line": 3, "column": 1 }, + "end": { "line": 3, "column": 1 } + } + } + ] + }, + { + "type": "raw", + "lang": "tex", + "tex": "\n\\section{LaTeX Heading}\n" + }, + { "type": "raw", "lang": "typst", "typst": "\n= Typst Heading\n" }, + { + "type": "raw", + "lang": "", + "value": "This is *just text*", + "children": [ + { + "type": "paragraph", + "children": [{ "type": "text", "value": "This is *just text*" }] + } + ] + }, + { + "type": "raw", + "lang": "latex", + "tex": "\nThis is \\textit{latex}\n", + "value": "This is \\textit{latex}", + "children": [ + { + "type": "paragraph", + "position": { + "start": { "offset": 0, "line": 1, "column": 1 }, + "end": { "offset": 4, "line": 1, "column": 5 } + }, + "children": [ + { + "type": "text", + "position": { + "start": { "offset": 0, "line": 1, "column": 1 }, + "end": { "offset": 4, "line": 1, "column": 5 } + }, + "value": "This is " + }, + { + "type": "emphasis", + "position": { + "start": { "offset": 8, "line": 1, "column": 9 }, + "end": { "offset": 15, "line": 1, "column": 16 } + }, + "children": [ + { + "type": "text", + "position": { + "start": { "offset": 16, "line": 1, "column": 17 }, + "end": { "offset": 21, "line": 1, "column": 22 } + }, + "value": "latex" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + "references": { "cite": { "order": [], "data": {} } } +} diff --git a/packages/mystmd/tests/raw/outputs/out.tex b/packages/mystmd/tests/raw/outputs/out.tex new file mode 100644 index 000000000..3f22eb21b --- /dev/null +++ b/packages/mystmd/tests/raw/outputs/out.tex @@ -0,0 +1,8 @@ +This has raw LaTeX content + + +\section{LaTeX Heading} +This is *just text* + + +This is \textit{latex} \ No newline at end of file diff --git a/packages/mystmd/tests/raw/outputs/out.typ b/packages/mystmd/tests/raw/outputs/out.typ new file mode 100644 index 000000000..55519d7e7 --- /dev/null +++ b/packages/mystmd/tests/raw/outputs/out.typ @@ -0,0 +1,7 @@ +This has raw Typst content + + += Typst Heading +This is \*just text\* + +This is _latex_ \ No newline at end of file