Skip to content
This repository has been archived by the owner on Aug 4, 2022. It is now read-only.

Commit

Permalink
🧪 TEST: add fixtures for directives and roles
Browse files Browse the repository at this point in the history
Unsure about how best to have admonitions on a single line.

See: executablebooks/MyST-Parser#154
  • Loading branch information
rowanc1 committed Jul 21, 2020
1 parent 68afe77 commit ff49536
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 14 deletions.
41 changes: 41 additions & 0 deletions fixtures/directives.known.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Admonition:
.
```{admonition} This is a title
An example of an admonition with a _title_.
```
.
<aside class="callout note"><header>This is a title</header>
<p>An example of an admonition with a <em>title</em>.</p>
</aside>
.

Note:
.
```{note}
An example of an admonition with a _title_.
```
.
<aside class="callout note"><header>Note</header>
<p>An example of an admonition with a <em>title</em>.</p>
</aside>
.

Note on split lines:
.
```{note} An example
of an admonition on two lines.
```
.
<aside class="callout note"><header>Note</header>
<p>An example of an admonition on two lines.</p>
</aside>
.

[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.
```
.
<aside class="callout note"><header>Note</header>
</aside>
.
74 changes: 74 additions & 0 deletions fixtures/roles.generic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
Basic:
.
{abc}`xyz`
.
<p><code class="myst-role">{abc}[xyz]</code></p>
.

Surrounding Text:
.
a {abc}`xyz` b
.
<p>a <code class="myst-role">{abc}[xyz]</code> b</p>
.

In Code:
.
`` {abc}`xyz` ``
.
<p><code>{abc}`xyz`</code></p>
.


Surrounding Code:
.
`a` {abc}`xyz` `b`
.
<p><code>a</code> <code class="myst-role">{abc}[xyz]</code> <code>b</code></p>
.

In list:
.
- {abc}`xyz`
.
<ul>
<li><code class="myst-role">{abc}[xyz]</code></li>
</ul>
.

In Quote:
.
> {abc}`xyz` b
.
<blockquote>
<p><code class="myst-role">{abc}[xyz]</code> b</p>
</blockquote>
.

Multiple ticks:
.
{abc}``xyz``
.
<p><code class="myst-role">{abc}[xyz]</code></p>
.

Unbalanced ticks:
.
{abc}``xyz`
.
<p>{abc}``xyz`</p>
.

Space in name:
.
{ab c}`xyz`
.
<p>{ab c}<code>xyz</code></p>
.

Escaped:
.
\{abc}`xyz`
.
<p>{abc}<code>xyz</code></p>
.
34 changes: 34 additions & 0 deletions fixtures/roles.known.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Subscript:
.
H{sub}`2`O
.
<p>H<sub>2</sub>O</p>
.

Superscript:
.
4{sup}`th` of July
.
<p>4<sup>th</sup> of July</p>
.

Abbr with title:
.
{abbr}`CSS (Cascading Style Sheets)`
.
<p><abbr title="Cascading Style Sheets">CSS</abbr></p>
.

Abbr without title:
.
{abbr}`CSS`
.
<p><abbr>CSS</abbr></p>
.

Abbr with poor brackets:
.
{abbr}`CSS (Cascading) Style( Sheets)`
.
<p><abbr title="Sheets">CSS (Cascading) Style</abbr></p>
.
10 changes: 5 additions & 5 deletions src/markdown/markdown-it-myst/myst.spec.ts
Original file line number Diff line number Diff line change
@@ -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 });
Expand All @@ -23,11 +23,11 @@ describe('Markdown', () => {
'Well <abbr title="Cascading Style Sheets">CSS</abbr> is cool?',
));
it('parses myst admonitions, ``` style', () => sameF(
'```{warning} A title\nThis is *directive* content\n```',
'<details><summary>A title</summary>\n<p>This is <em>directive</em> content</p>\n</details>',
'```{warning} This is\n*directive* content\n```',
'<aside class="callout warning"><header>Warning</header>\n<p>This is <em>directive</em> content</p>\n</aside>',
));
it('parses myst admonitions, ::: style', () => sameF(
':::warning A title\nThis is *directive* content\n:::',
'<details><summary>A title</summary>\n<p>This is <em>directive</em> content</p>\n</details>',
':::warning\nThis is *directive* content\n:::',
'<aside class="callout warning"><header>Warning</header>\n<p>This is <em>directive</em> content</p>\n</aside>',
));
});
46 changes: 40 additions & 6 deletions src/markdown/markdown-it-myst/myst_directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import { RuleCore } from 'markdown-it/lib/parser_core';

type ContainerOpts = Parameters<typeof container>[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-]+):(.*)$/;

Expand All @@ -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 `<details><summary>${escapeHtml(title)}</summary>\n`;
return '</details>\n';
if (tokens[idx].nesting === 1) return `<aside class="callout ${className}"><header>${escapeHtml(title)}</header>\n`;
return '</aside>\n';
},
};

Expand All @@ -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);
Expand All @@ -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);
}

9 changes: 6 additions & 3 deletions src/markdown/markdown-it-myst/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ const knownRoles: Record<string, RoleConstructor> = {
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 `<abbr title="${escapeHtml(token.attrGet('title') ?? '')}">${escapeHtml(token.content)}</abbr>`;
const title = token.attrGet('title') ?? '';
const titleAttr = title ? ` title="${escapeHtml(title)}"` : '';
return `<abbr${titleAttr}>${escapeHtml(token.content)}</abbr>`;
},
},
sub: {
Expand All @@ -63,7 +65,7 @@ const genericRole: RoleConstructor = {
const token = tokens[idx];
const name = token.meta?.name ?? 'unknown';
return (
`<code class="myst-role">\n{{${name}}}[${escapeHtml(token.content)}]\n</code>`
`<code class="myst-role">{${name}}[${escapeHtml(token.content)}]</code>`
);
},
};
Expand All @@ -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;
}
16 changes: 16 additions & 0 deletions src/markdown/markdown-it-myst/tests/build.ts
Original file line number Diff line number Diff line change
@@ -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;
}
15 changes: 15 additions & 0 deletions src/markdown/markdown-it-myst/tests/fixtures.spec.ts
Original file line number Diff line number Diff line change
@@ -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`));
});
});

0 comments on commit ff49536

Please sign in to comment.