Skip to content

Commit

Permalink
add markdown renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
frodo821 committed Feb 18, 2022
1 parent 63bcf48 commit 43867af
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 230 deletions.
231 changes: 1 addition & 230 deletions scripts/markdown.js
Original file line number Diff line number Diff line change
@@ -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 `<div class="limp-unknown limp-${node.name}">${node.children.map((it) => this.render(it)).join('')}</div>`;
}
}
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 `<span class="limp-inline limp-${node.name}">${renderer.render(node)}</span>`;
}
}
});

const id = (renderer) => ({
render(node) {
const id = node.args[0] || node.args['id'];
if (node.type === 'role') {
return `<span id="${id}">${node.children
.map((it) => renderer.render(it))
.join('')}</span>`;
}
return `<div id="${id}">${node.children
.map((it) => renderer.render(it))
.join('')}</div>`;
}
});

const list = (renderer) => ({
render(node, type) {
type = type || node.args[0] || node.args['type'] || 'ordered';

if (node.type === 'role') {
return `<span>${node.children
.map((it) => renderer.render(it))
.join('')}</span>`;
}
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 `<div class="limp-block limp-malformed">${renderer.render(node)}</div>`;
}
},
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, '<br/>')}|\n|${':-:|'.repeat(node.children.length)}`;
},
renderRow(node) {
return `|${node.children
.map((it) => this.renderColumn(it, true))
.join('|')
.replace(/\n/g, '<br/>')}|`;
},
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;
Expand All @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import htmlRenderer from './render/html';
import markdownRenderer from './render/markdown';
import { parseDocument } from './parsing/parsing';

export * from './parsing/parsing';
Expand All @@ -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));
}
11 changes: 11 additions & 0 deletions src/render/markdown/index.ts
Original file line number Diff line number Diff line change
@@ -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;
47 changes: 47 additions & 0 deletions src/render/markdown/markdownRenderer.ts
Original file line number Diff line number Diff line change
@@ -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 `<div class="limp-unknown limp-${node.name}">${node.children
.map((it) => this.render(it))
.join('')}</div>`;
}
}

root(node: LimpNodeOf<'root'>): string {
return `${node.children.map((it) => this.render(it)).join('')}`;
}
}

const renderer = new MarkdownRenderer();

export default renderer;
13 changes: 13 additions & 0 deletions src/render/markdown/renderers/anchor.ts
Original file line number Diff line number Diff line change
@@ -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 {};
21 changes: 21 additions & 0 deletions src/render/markdown/renderers/code.ts
Original file line number Diff line number Diff line change
@@ -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 {};
32 changes: 32 additions & 0 deletions src/render/markdown/renderers/comment.ts
Original file line number Diff line number Diff line change
@@ -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 `<!--${escape(
node.children.map((it) => renderer.render(it)).join('')
)}-->`;
}

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 {};
Loading

0 comments on commit 43867af

Please sign in to comment.