From ff49536aa3cd5f94d16f706e2e951db46a13d9b3 Mon Sep 17 00:00:00 2001 From: Rowan Cockett Date: Tue, 21 Jul 2020 11:33:09 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20TEST:=20add=20fixtures=20for=20d?= =?UTF-8?q?irectives=20and=20roles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unsure about how best to have admonitions on a single line. See: executablebooks/MyST-Parser#154 --- fixtures/directives.known.md | 41 ++++++++++ fixtures/roles.generic.md | 74 +++++++++++++++++++ fixtures/roles.known.md | 34 +++++++++ src/markdown/markdown-it-myst/myst.spec.ts | 10 +-- .../markdown-it-myst/myst_directives.ts | 46 ++++++++++-- .../{myst_role.ts => myst_roles.ts} | 0 src/markdown/markdown-it-myst/roles.ts | 9 ++- src/markdown/markdown-it-myst/tests/build.ts | 16 ++++ .../markdown-it-myst/tests/fixtures.spec.ts | 15 ++++ 9 files changed, 231 insertions(+), 14 deletions(-) create mode 100644 fixtures/directives.known.md create mode 100644 fixtures/roles.generic.md create mode 100644 fixtures/roles.known.md rename src/markdown/markdown-it-myst/{myst_role.ts => myst_roles.ts} (100%) create mode 100644 src/markdown/markdown-it-myst/tests/build.ts create mode 100644 src/markdown/markdown-it-myst/tests/fixtures.spec.ts diff --git a/fixtures/directives.known.md b/fixtures/directives.known.md new file mode 100644 index 0000000..940b505 --- /dev/null +++ b/fixtures/directives.known.md @@ -0,0 +1,41 @@ +Admonition: +. +```{admonition} This is a title +An example of an admonition with a _title_. +``` +. + +. + +Note: +. +```{note} +An example of an admonition with a _title_. +``` +. + +. + +Note on split lines: +. +```{note} An example +of an admonition on two lines. +``` +. + +. + +[FIX] Note on a single line (see https://github.com/executablebooks/MyST-Parser/issues/154): +. +```{note} An example of an admonition on a single line. +``` +. + +. diff --git a/fixtures/roles.generic.md b/fixtures/roles.generic.md new file mode 100644 index 0000000..b66bbef --- /dev/null +++ b/fixtures/roles.generic.md @@ -0,0 +1,74 @@ +Basic: +. +{abc}`xyz` +. +

{abc}[xyz]

+. + +Surrounding Text: +. +a {abc}`xyz` b +. +

a {abc}[xyz] b

+. + +In Code: +. +`` {abc}`xyz` `` +. +

{abc}`xyz`

+. + + +Surrounding Code: +. +`a` {abc}`xyz` `b` +. +

a {abc}[xyz] b

+. + +In list: +. +- {abc}`xyz` +. + +. + +In Quote: +. +> {abc}`xyz` b +. +
+

{abc}[xyz] b

+
+. + +Multiple ticks: +. +{abc}``xyz`` +. +

{abc}[xyz]

+. + +Unbalanced ticks: +. +{abc}``xyz` +. +

{abc}``xyz`

+. + +Space in name: +. +{ab c}`xyz` +. +

{ab c}xyz

+. + +Escaped: +. +\{abc}`xyz` +. +

{abc}xyz

+. diff --git a/fixtures/roles.known.md b/fixtures/roles.known.md new file mode 100644 index 0000000..a2e9d71 --- /dev/null +++ b/fixtures/roles.known.md @@ -0,0 +1,34 @@ +Subscript: +. +H{sub}`2`O +. +

H2O

+. + +Superscript: +. +4{sup}`th` of July +. +

4th of July

+. + +Abbr with title: +. +{abbr}`CSS (Cascading Style Sheets)` +. +

CSS

+. + +Abbr without title: +. +{abbr}`CSS` +. +

CSS

+. + +Abbr with poor brackets: +. +{abbr}`CSS (Cascading) Style( Sheets)` +. +

CSS (Cascading) Style

+. diff --git a/src/markdown/markdown-it-myst/myst.spec.ts b/src/markdown/markdown-it-myst/myst.spec.ts index 92b637b..b46fb95 100644 --- a/src/markdown/markdown-it-myst/myst.spec.ts +++ b/src/markdown/markdown-it-myst/myst.spec.ts @@ -1,5 +1,5 @@ import MarkdownIt from 'markdown-it'; -import { myst_role_plugin } from './myst_role'; +import { myst_role_plugin } from './myst_roles'; import { myst_directives_plugin } from './myst_directives'; const tokenizer = MarkdownIt('commonmark', { html: false }); @@ -23,11 +23,11 @@ describe('Markdown', () => { 'Well CSS is cool?', )); it('parses myst admonitions, ``` style', () => sameF( - '```{warning} A title\nThis is *directive* content\n```', - '
A title\n

This is directive content

\n
', + '```{warning} This is\n*directive* content\n```', + '', )); it('parses myst admonitions, ::: style', () => sameF( - ':::warning A title\nThis is *directive* content\n:::', - '
A title\n

This is directive content

\n
', + ':::warning\nThis is *directive* content\n:::', + '', )); }); diff --git a/src/markdown/markdown-it-myst/myst_directives.ts b/src/markdown/markdown-it-myst/myst_directives.ts index 308867f..586ac49 100644 --- a/src/markdown/markdown-it-myst/myst_directives.ts +++ b/src/markdown/markdown-it-myst/myst_directives.ts @@ -7,7 +7,12 @@ import { RuleCore } from 'markdown-it/lib/parser_core'; type ContainerOpts = Parameters[2]; -const admonitionTypes = new Set(['attention', 'caution', 'danger', 'error', 'important', 'hint', 'note', 'seealso', 'tip', 'warning']); +const admonitionTypes = new Set(['admonition', 'attention', 'caution', 'danger', 'error', 'important', 'hint', 'note', 'seealso', 'tip', 'warning']); +const admonitionTitles = { + attention: 'Attention', caution: 'Caution', danger: 'Danger', error: 'Error', important: 'Important', hint: 'Hint', note: 'Note', seealso: 'See Also', tip: 'Tip', warning: 'Warning', +}; +const DEFAULT_ADMONITION_CLASS = 'note'; +type AdmonitionTypes = keyof typeof admonitionTitles | 'admonition'; const ADMONITIONS = /^\{?([a-z]*)\}?\s*(.*)$/; const QUICK_PARAMETERS = /^:([a-z-]+):(.*)$/; @@ -17,9 +22,11 @@ const admonitions: ContainerOpts = { return match != null && admonitionTypes.has(match[1]); }, render(tokens, idx) { + const kind = tokens[idx].attrGet('kind') ?? 'note'; + const className = kind === 'admonition' ? DEFAULT_ADMONITION_CLASS : kind; const title = tokens[idx].attrGet('title') ?? ''; - if (tokens[idx].nesting === 1) return `
${escapeHtml(title)}\n`; - return '
\n'; + if (tokens[idx].nesting === 1) return `\n'; }, }; @@ -45,17 +52,43 @@ function addDirectiveOptions(parent: Token, token: Token) { token.content = modified; } -const stripOptions: RuleCore = (state) => { + +const cleanAdmonitions: RuleCore = (state) => { const { tokens } = state; let inContainer: Token | false = false; + // If there is a title on the first line when not required, bump it to the first inline + let bumpTitle = ''; for (let index = 0; index < tokens.length; index += 1) { const token = tokens[index]; if (token.type.startsWith('container_') && token.type.endsWith('_open')) { inContainer = token; const match = token.info.trim().match(ADMONITIONS); - token.attrSet('kind', match?.[1] ?? ''); - token.attrSet('title', (match?.[2] ?? '').trim()); + const kind: AdmonitionTypes = match?.[1] as AdmonitionTypes; + const title = (match?.[2] ?? '').trim(); + if (kind !== 'admonition') bumpTitle = title; + token.attrSet('kind', kind); + token.attrSet('title', kind === 'admonition' ? title : admonitionTitles[kind]); + } + if (token.type.startsWith('container_') && token.type.endsWith('_close')) { + // TODO: https://github.com/executablebooks/MyST-Parser/issues/154 + // If the bumped title needs to be rendered - put it here somehow. + bumpTitle = ''; + inContainer = false; } + if (inContainer && bumpTitle && token.type === 'inline') { + token.content = `${bumpTitle} ${token.content}`; + bumpTitle = ''; + } + } + return true; +}; + +const stripOptions: RuleCore = (state) => { + const { tokens } = state; + let inContainer: Token | false = false; + for (let index = 0; index < tokens.length; index += 1) { + const token = tokens[index]; + if (token.type.startsWith('container_') && token.type.endsWith('_open')) inContainer = token; if (token.type.startsWith('container_') && token.type.endsWith('_close')) inContainer = false; if (inContainer && token.type === 'inline') { addDirectiveOptions(inContainer, token); @@ -68,5 +101,6 @@ export function myst_directives_plugin(md: MarkdownIt) { md.use(container, 'admonitions', admonitions); md.use(container, 'admonitions', { ...admonitions, marker: '`' }); md.core.ruler.after('block', 'strip_options', stripOptions); + md.core.ruler.after('strip_options', 'clean_admonitions', cleanAdmonitions); } diff --git a/src/markdown/markdown-it-myst/myst_role.ts b/src/markdown/markdown-it-myst/myst_roles.ts similarity index 100% rename from src/markdown/markdown-it-myst/myst_role.ts rename to src/markdown/markdown-it-myst/myst_roles.ts diff --git a/src/markdown/markdown-it-myst/roles.ts b/src/markdown/markdown-it-myst/roles.ts index e708756..9a2403f 100644 --- a/src/markdown/markdown-it-myst/roles.ts +++ b/src/markdown/markdown-it-myst/roles.ts @@ -34,11 +34,13 @@ const knownRoles: Record = { const match = ABBR_PATTERN.exec(content); if (match == null) return { attrs: { title: '' }, content }; const [, modified, title] = match; - return { attrs: { title }, content: modified.trim() }; + return { attrs: { title: title.trim() }, content: modified.trim() }; }, renderer: (tokens, idx) => { const token = tokens[idx]; - return `${escapeHtml(token.content)}`; + const title = token.attrGet('title') ?? ''; + const titleAttr = title ? ` title="${escapeHtml(title)}"` : ''; + return `${escapeHtml(token.content)}`; }, }, sub: { @@ -63,7 +65,7 @@ const genericRole: RoleConstructor = { const token = tokens[idx]; const name = token.meta?.name ?? 'unknown'; return ( - `\n{{${name}}}[${escapeHtml(token.content)}]\n` + `{${name}}[${escapeHtml(token.content)}]` ); }, }; @@ -84,4 +86,5 @@ export function addRenderers(md: MarkdownIt) { if (md.renderer.rules[token]) return; md.renderer.rules[token] = renderer; }); + md.renderer.rules[genericRole.token] = genericRole.renderer; } diff --git a/src/markdown/markdown-it-myst/tests/build.ts b/src/markdown/markdown-it-myst/tests/build.ts new file mode 100644 index 0000000..933b653 --- /dev/null +++ b/src/markdown/markdown-it-myst/tests/build.ts @@ -0,0 +1,16 @@ +import fs from 'fs'; +import MarkdownIt from 'markdown-it'; +import { myst_role_plugin } from '../myst_roles'; +import { myst_directives_plugin } from '../myst_directives'; + +export function getFixtures(name: string) { + const fixtures = fs.readFileSync(`fixtures/${name}.md`).toString(); + return fixtures.split('\n.\n\n').map((s) => s.split('\n.\n')); +} + +export function getTokenizer() { + const tokenizer = MarkdownIt('commonmark', { html: false }); + tokenizer.use(myst_role_plugin); + tokenizer.use(myst_directives_plugin); + return tokenizer; +} diff --git a/src/markdown/markdown-it-myst/tests/fixtures.spec.ts b/src/markdown/markdown-it-myst/tests/fixtures.spec.ts new file mode 100644 index 0000000..5519cbb --- /dev/null +++ b/src/markdown/markdown-it-myst/tests/fixtures.spec.ts @@ -0,0 +1,15 @@ +import { getFixtures, getTokenizer } from './build'; + +const tokenizer = getTokenizer(); + +describe('Roles', () => { + getFixtures('roles.generic').forEach(([name, md, html]) => { + it(name, () => expect(tokenizer.render(md)).toEqual(`${html}\n`)); + }); + getFixtures('roles.known').forEach(([name, md, html]) => { + it(name, () => expect(tokenizer.render(md)).toEqual(`${html}\n`)); + }); + getFixtures('directives.known').forEach(([name, md, html]) => { + it(name, () => expect(tokenizer.render(md)).toEqual(`${html}\n`)); + }); +});