diff --git a/scripts/markdown.js b/scripts/markdown.js
index 033a27c..e33d6c6 100644
--- a/scripts/markdown.js
+++ b/scripts/markdown.js
@@ -1,237 +1,11 @@
#!/usr/bin/env node
-class MarkdownRenderer {
- constructor() {
- this.renderers = {};
- MarkdownRenderer.instance = this;
- }
- static get current() {
- return this.instance;
- }
- registerRenderer(name, renderer) {
- this.renderers[name] = renderer;
- }
- render(node) {
- switch (node.type) {
- case 'text':
- return this.text(node);
- case 'role':
- return this.role(node);
- case 'title':
- return this.title(node);
- case 'block_role':
- return this.block_role(node);
- case 'root':
- return this.root(node);
- case 'paragraph':
- return this.paragraph(node);
- default:
- throw new Error(`Unknown node type: ${node.type}`);
- }
- }
- paragraph(node) {
- return `${node.children.map((it) => this.render(it)).join('')}\n\n`;
- }
- text(node) {
- return node.self;
- }
- role(node) {
- const renderer = this.renderers[node.name];
- if (renderer) {
- return renderer.render(node);
- }
- else {
- return node.children.map((it) => this.render(it)).join('');
- }
- }
- title(node) {
- return `${'#'.repeat(node.level)} ${node.children.map((it) => this.render(it)).join('')}\n`;
- }
- block_role(node) {
- const renderer = this.renderers[node.name];
- if (renderer) {
- const result = renderer.render(node);
- return result ? `\n${result}\n` : '';
- }
- else {
- return `
${node.children.map((it) => this.render(it)).join('')}
`;
- }
- }
- root(node) {
- return `${node.children.map((it) => this.render(it)).join('')}`;
- }
-}
-
-function escape(str) {
- return str.replace(/[`]/g, function (s) {
- return ({
- '`': '\\`'
- }[s] || s);
- });
-}
-
-const anchor = (renderer) => ({
- render(node) {
- return `[${node.children.map((it) => renderer.render(it)).join('')}](${node.args[0] || node.args['url']})`;
- }
-});
-
-const code = (renderer) => ({
- render(node) {
- if (node.type === 'role') {
- return `\`${escape(node.children.map((it) => renderer.render(it)).join(''))}\``;
- }
- return `\`\`\`\n${node.children.map((it) => renderer.render(it)).join('')}\`\`\``;
- }
-});
-
-const image = (renderer) => ({
- render(node) {
- const href = node.args[0] || node.args['url'];
- const alt = node.args[1] || node.args['alt'];
- const title = node.args[2] || node.args['title'];
- return `![${alt || ''}${title ? ` "${title}"` : ''}](${href})`;
- }
-});
-
-const decorations = (renderer) => ({
- render(node) {
- switch (node.name) {
- case 'underlined':
- case 'u':
- return `_${node.children
- .map((it) => renderer.render(it))
- .join('')}_`;
- case 'strike':
- case 's':
- return `~~${node.children
- .map((it) => renderer.render(it))
- .join('')}~~`;
- case 'italic':
- case 'i':
- return `*${node.children
- .map((it) => renderer.render(it))
- .join('')}*`;
- case 'bold':
- case 'b':
- return `**${node.children
- .map((it) => renderer.render(it))
- .join('')}**`;
- case 'br':
- return ` \n`;
- default:
- return `${renderer.render(node)}`;
- }
- }
-});
-
-const id = (renderer) => ({
- render(node) {
- const id = node.args[0] || node.args['id'];
- if (node.type === 'role') {
- return `${node.children
- .map((it) => renderer.render(it))
- .join('')}`;
- }
- return `${node.children
- .map((it) => renderer.render(it))
- .join('')}
`;
- }
-});
-
-const list = (renderer) => ({
- render(node, type) {
- type = type || node.args[0] || node.args['type'] || 'ordered';
-
- if (node.type === 'role') {
- return `${node.children
- .map((it) => renderer.render(it))
- .join('')}`;
- }
- if (node.name === 'list') {
- return node.children
- .map((it) => this.render(it))
- .join('')
- .trim();
- }
- const bullet = type === 'ordered' ? `1.` : '-';
-
- return `${bullet} ${node.children
- .map((it) => renderer.render(it))
- .join('')
- .replace(/\n/g, '\n ')}\n`;
- }
-});
-
-const table = (renderer) => ({
- render(node) {
- switch (node.name) {
- case 'table':
- return this.renderTable(node);
- case 'th':
- return this.renderHeading(node);
- case 'tr':
- return this.renderRow(node);
- case 'td':
- return this.renderColumn(node);
- default:
- return `${renderer.render(node)}
`;
- }
- },
- renderTable(node) {
- return node.children
- .map((it) => renderer.render(it))
- .join('')
- .replace(/\n\n/g, '\n');
- },
- renderHeading(node) {
- return `|${node.children
- .map((it) => renderer.render(it, false))
- .join('|')
- .replace(/\n/g, '
')}|\n|${':-:|'.repeat(node.children.length)}`;
- },
- renderRow(node) {
- return `|${node.children
- .map((it) => this.renderColumn(it, true))
- .join('|')
- .replace(/\n/g, '
')}|`;
- },
- renderColumn(node) {
- return node.children
- .map((it) => renderer.render(it))
- .join('');
- }
-});
-
(function () {
const fs = require('fs');
const limp = require('../lib');
const args = [...process.argv];
- const renderer = new MarkdownRenderer();
-
- renderer.registerRenderer('ref', anchor(renderer));
- renderer.registerRenderer('code', code(renderer));
- renderer.registerRenderer('image', image(renderer));
- renderer.registerRenderer('img', image(renderer));
- renderer.registerRenderer('bold', decorations(renderer));
- renderer.registerRenderer('italic', decorations(renderer));
- renderer.registerRenderer('underlined', decorations(renderer));
- renderer.registerRenderer('strike', decorations(renderer));
- renderer.registerRenderer('b', decorations(renderer));
- renderer.registerRenderer('i', decorations(renderer));
- renderer.registerRenderer('u', decorations(renderer));
- renderer.registerRenderer('s', decorations(renderer));
- renderer.registerRenderer('id', id(renderer));
- renderer.registerRenderer('label', id(renderer));
- renderer.registerRenderer('list', list(renderer));
- renderer.registerRenderer('item', list(renderer));
- renderer.registerRenderer('table', table(renderer));
- renderer.registerRenderer('th', table(renderer));
- renderer.registerRenderer('tr', table(renderer));
- renderer.registerRenderer('td', table(renderer));
-
for (; args.length > 0;) {
if (args.shift() === __filename) {
break;
@@ -249,10 +23,7 @@ const table = (renderer) => ({
}
const file = arg.match(/^(.*?)(?:\.[^.]+)?$/)[1];
-
- const ast = limp.parseDocument(fs.readFileSync(arg, 'utf8'));
-
- const out = renderer.render(ast)
+ const out = limp.compileToMarkdown(fs.readFileSync(arg, 'utf8'))
fs.writeFileSync(`${file}.md`, out);
}
diff --git a/src/index.ts b/src/index.ts
index 82072d7..627af37 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,5 @@
import htmlRenderer from './render/html';
+import markdownRenderer from './render/markdown';
import { parseDocument } from './parsing/parsing';
export * from './parsing/parsing';
@@ -7,3 +8,7 @@ export * from './parsing/strparsing';
export function compileToHTML(src: string): string {
return htmlRenderer.render(parseDocument(src));
}
+
+export function compileToMarkdown(src: string): string {
+ return markdownRenderer.render(parseDocument(src));
+}
diff --git a/src/render/markdown/index.ts b/src/render/markdown/index.ts
new file mode 100644
index 0000000..9f7ed77
--- /dev/null
+++ b/src/render/markdown/index.ts
@@ -0,0 +1,11 @@
+import renderer from './markdownRenderer';
+import './renderers/textDecorations';
+import './renderers/id';
+import './renderers/anchor';
+import './renderers/image';
+import './renderers/list';
+import './renderers/code';
+import './renderers/table';
+import './renderers/comment';
+
+export default renderer;
diff --git a/src/render/markdown/markdownRenderer.ts b/src/render/markdown/markdownRenderer.ts
new file mode 100644
index 0000000..dae7364
--- /dev/null
+++ b/src/render/markdown/markdownRenderer.ts
@@ -0,0 +1,47 @@
+import LimpRenderer from '..';
+import { LimpNodeOf } from '../..';
+
+export class MarkdownRenderer extends LimpRenderer {
+ paragraph(node: LimpNodeOf<'paragraph'>): string {
+ return `${node.children.map((it) => this.render(it)).join('')}\n\n`;
+ }
+
+ text(node: LimpNodeOf<'text'>): string {
+ return node.self;
+ }
+
+ role(node: LimpNodeOf<'role'>): string {
+ const renderer = this.renderers[node.name];
+ if (renderer) {
+ return renderer.render(node);
+ } else {
+ return node.children.map((it) => this.render(it)).join('');
+ }
+ }
+
+ title(node: LimpNodeOf<'title'>): string {
+ return `${'#'.repeat(node.level)} ${node.children
+ .map((it) => this.render(it))
+ .join('')}\n`;
+ }
+
+ block_role(node: LimpNodeOf<'block_role'>): string {
+ const renderer = this.renderers[node.name];
+ if (renderer) {
+ const result = renderer.render(node);
+ return result ? `\n${result}\n` : '';
+ } else {
+ return `${node.children
+ .map((it) => this.render(it))
+ .join('')}
`;
+ }
+ }
+
+ root(node: LimpNodeOf<'root'>): string {
+ return `${node.children.map((it) => this.render(it)).join('')}`;
+ }
+}
+
+const renderer = new MarkdownRenderer();
+
+export default renderer;
diff --git a/src/render/markdown/renderers/anchor.ts b/src/render/markdown/renderers/anchor.ts
new file mode 100644
index 0000000..1c12e13
--- /dev/null
+++ b/src/render/markdown/renderers/anchor.ts
@@ -0,0 +1,13 @@
+import { Renderer } from '../..';
+import { LimpNodeOf } from '../../../parsing/parsing';
+import renderer from '../markdownRenderer';
+
+class AnchorRoleRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ return `[${node.children.map((it) => renderer.render(it)).join('')}](${node.args[0] || node.args['url']})`;
+ }
+}
+
+renderer.registerRenderer('ref', new AnchorRoleRenderer());
+
+export default {};
diff --git a/src/render/markdown/renderers/code.ts b/src/render/markdown/renderers/code.ts
new file mode 100644
index 0000000..0038810
--- /dev/null
+++ b/src/render/markdown/renderers/code.ts
@@ -0,0 +1,21 @@
+import { Renderer } from '../..';
+import { LimpNodeOf } from '../../../parsing/parsing';
+import { escape } from '../utils';
+import renderer from '../markdownRenderer';
+
+class CodeRoleRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ if (node.type === 'role') {
+ return `\`${escape(
+ node.children.map((it) => renderer.render(it)).join('')
+ )}\``;
+ }
+ return `\`\`\`\n${node.children
+ .map((it) => renderer.render(it))
+ .join('')}\`\`\``;
+ }
+}
+
+renderer.registerRenderer('code', new CodeRoleRenderer());
+
+export default {};
diff --git a/src/render/markdown/renderers/comment.ts b/src/render/markdown/renderers/comment.ts
new file mode 100644
index 0000000..3a52d5a
--- /dev/null
+++ b/src/render/markdown/renderers/comment.ts
@@ -0,0 +1,32 @@
+import { Renderer } from '../..';
+import { LimpNodeOf } from '../../../parsing/parsing';
+import { escape } from '../utils';
+import renderer from '../markdownRenderer';
+
+class CommentRoleRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ if (this.isNoEmit(node)) {
+ return '';
+ }
+ return ``;
+ }
+
+ isNoEmit(node: LimpNodeOf<'role' | 'block_role'>): boolean {
+ const trues = ['yes', 'y', 'true', 'noemit'];
+
+ return (
+ node.args[0] === 'noemit' ||
+ 'noemit' in node.args ||
+ trues.includes(node.args.noemit)
+ );
+ }
+}
+
+const instance = new CommentRoleRenderer();
+
+renderer.registerRenderer('rem', instance);
+renderer.registerRenderer('comment', instance);
+
+export default {};
diff --git a/src/render/markdown/renderers/id.ts b/src/render/markdown/renderers/id.ts
new file mode 100644
index 0000000..d24df94
--- /dev/null
+++ b/src/render/markdown/renderers/id.ts
@@ -0,0 +1,25 @@
+import { Renderer } from '../..';
+import { LimpNodeOf } from '../../../parsing/parsing';
+import renderer from '../markdownRenderer';
+
+class LabelRoleRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ const id = node.args[0] || node.args['id'];
+
+ if (node.type === 'role') {
+ return `${node.children
+ .map((it) => renderer.render(it))
+ .join('')}`;
+ }
+ return `${node.children
+ .map((it) => renderer.render(it))
+ .join('')}
`;
+ }
+}
+
+const instance = new LabelRoleRenderer();
+
+renderer.registerRenderer('id', instance);
+renderer.registerRenderer('label', instance);
+
+export default {};
diff --git a/src/render/markdown/renderers/image.ts b/src/render/markdown/renderers/image.ts
new file mode 100644
index 0000000..0b26adb
--- /dev/null
+++ b/src/render/markdown/renderers/image.ts
@@ -0,0 +1,19 @@
+import { Renderer } from '../..';
+import { LimpNodeOf } from '../../../parsing/parsing';
+import renderer from '../markdownRenderer';
+
+class ImageRoleRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ const href = node.args[0] || node.args['url'];
+ const alt = node.args[1] || node.args['alt'];
+ const title = node.args[2] || node.args['title'];
+ return `![${alt || ''}${title ? ` "${title}"` : ''}](${href})`;
+ }
+}
+
+const instance = new ImageRoleRenderer();
+
+renderer.registerRenderer('image', instance);
+renderer.registerRenderer('img', instance);
+
+export default {};
diff --git a/src/render/markdown/renderers/list.ts b/src/render/markdown/renderers/list.ts
new file mode 100644
index 0000000..7c6d188
--- /dev/null
+++ b/src/render/markdown/renderers/list.ts
@@ -0,0 +1,40 @@
+import { Renderer } from '../..';
+import { LimpNodeOf, LimpNode } from '../../../parsing/parsing';
+import renderer from '../markdownRenderer';
+
+class ListRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ return this.renderInternal(node);
+ }
+
+ renderInternal(node: LimpNode, type?: string): string {
+ if (node.type !== 'block_role') {
+ return `${node.children
+ .map((it) => renderer.render(it))
+ .join('')}`;
+ }
+
+ type = type || node.args[0] || node.args['type'] || 'ordered';
+
+ if (node.name === 'list') {
+ return node.children
+ .map((it) => this.renderInternal(it as any, type))
+ .join('')
+ .trim();
+ }
+
+ const bullet = type === 'ordered' ? `1.` : '-';
+
+ return `${bullet} ${node.children
+ .map((it) => renderer.render(it))
+ .join('')
+ .replace(/\n/g, '\n ')}\n`;
+ }
+}
+
+const instance = new ListRenderer();
+
+renderer.registerRenderer('list', instance);
+renderer.registerRenderer('item', instance);
+
+export default {};
diff --git a/src/render/markdown/renderers/table.ts b/src/render/markdown/renderers/table.ts
new file mode 100644
index 0000000..4939118
--- /dev/null
+++ b/src/render/markdown/renderers/table.ts
@@ -0,0 +1,60 @@
+import { Renderer } from '../..';
+import { LimpNodeOf, LimpNode } from '../../../parsing/parsing';
+import renderer from '../markdownRenderer';
+
+class TableRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ if (node.type === 'role') {
+ return `${renderer.render(
+ node
+ )}`;
+ }
+
+ switch (node.name) {
+ case 'table':
+ return this.renderTable(node);
+ case 'th':
+ return this.renderHeading(node);
+ case 'tr':
+ return this.renderRow(node);
+ default:
+ return `${renderer.render(
+ node
+ )}
`;
+ }
+ }
+
+ renderTable(node: LimpNodeOf<'block_role'>): string {
+ return node.children
+ .map((it) => renderer.render(it))
+ .join('')
+ .replace(/\n\n/g, '\n');
+ }
+
+ renderHeading(node: LimpNodeOf<'block_role'>): string {
+ return `|${node.children
+ .map((it) => this.renderColumn(it))
+ .join('|')
+ .replace(/\n/g, '
')}|\n|${':-:|'.repeat(node.children.length)}`;
+ }
+
+ renderRow(node: LimpNodeOf<'block_role'>): string {
+ return `|${node.children
+ .map((it) => this.renderColumn(it))
+ .join('|')
+ .replace(/\n/g, '
')}|`;
+ }
+
+ renderColumn(node: LimpNode): string {
+ return node.children.map((it) => renderer.render(it)).join('');
+ }
+}
+
+const instance = new TableRenderer();
+
+renderer.registerRenderer('table', instance);
+renderer.registerRenderer('th', instance);
+renderer.registerRenderer('tr', instance);
+renderer.registerRenderer('td', instance);
+
+export default {};
diff --git a/src/render/markdown/renderers/textDecorations.ts b/src/render/markdown/renderers/textDecorations.ts
new file mode 100644
index 0000000..dd41068
--- /dev/null
+++ b/src/render/markdown/renderers/textDecorations.ts
@@ -0,0 +1,42 @@
+import { Renderer } from '../..';
+import { LimpNodeOf } from '../../../parsing/parsing';
+import renderer from '../markdownRenderer';
+
+class TextDecorationsRenderer implements Renderer {
+ render(node: LimpNodeOf<'role' | 'block_role'>): string {
+ switch (node.name) {
+ case 'underlined':
+ case 'u':
+ return `_${node.children.map((it) => renderer.render(it)).join('')}_`;
+ case 'strike':
+ case 's':
+ return `~~${node.children.map((it) => renderer.render(it)).join('')}~~`;
+ case 'italic':
+ case 'i':
+ return `*${node.children.map((it) => renderer.render(it)).join('')}*`;
+ case 'bold':
+ case 'b':
+ return `**${node.children.map((it) => renderer.render(it)).join('')}**`;
+ case 'br':
+ return ` \n`;
+ default:
+ return `${renderer.render(
+ node
+ )}`;
+ }
+ }
+}
+
+const instance = new TextDecorationsRenderer();
+
+renderer.registerRenderer('bold', instance);
+renderer.registerRenderer('underlined', instance);
+renderer.registerRenderer('italic', instance);
+renderer.registerRenderer('strike', instance);
+renderer.registerRenderer('br', instance);
+renderer.registerRenderer('b', instance);
+renderer.registerRenderer('u', instance);
+renderer.registerRenderer('i', instance);
+renderer.registerRenderer('s', instance);
+
+export default {};
diff --git a/src/render/markdown/utils.ts b/src/render/markdown/utils.ts
new file mode 100644
index 0000000..a85f7bb
--- /dev/null
+++ b/src/render/markdown/utils.ts
@@ -0,0 +1,9 @@
+export function escape(str: string): string {
+ return str.replace(/[`]/g, function (s) {
+ return (
+ {
+ '`': '\\`',
+ }[s] || s
+ );
+ });
+}