From 193e7f62cb260a7879cb4b1f90f6c1547adc5341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20GOUIN?= Date: Fri, 21 Jun 2019 11:31:26 +0200 Subject: [PATCH] Prism code rendering --- package-lock.json | 75 +++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ src/config.default.json | 3 +- src/renderer.js | 23 ++++++++++++- test/renderer.test.js | 43 +++++++++++++++++++++++ 5 files changed, 144 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4328a5a..9fb4096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2543,6 +2543,17 @@ } } }, + "clipboard": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", + "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -2850,6 +2861,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -3959,6 +3976,15 @@ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "optional": true, + "requires": { + "delegate": "^3.1.2" + } + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -7145,6 +7171,35 @@ "which": "^1.3.0" } }, + "node-prismjs": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/node-prismjs/-/node-prismjs-0.1.2.tgz", + "integrity": "sha512-WKb6ZbUlPWarzS8jR2UdIbV4lYpt6sOTkIx3u5Ldz55K1Zzs982KyF6aj1zjZbrrx/UGZSZ1e0j28lIzcm3ceg==", + "requires": { + "prismjs": "~1.6.0" + }, + "dependencies": { + "clipboard": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", + "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "prismjs": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.6.0.tgz", + "integrity": "sha1-EY2V+3pm26InLjQ7NF9SNmWds2U=", + "requires": { + "clipboard": "^1.5.5" + } + } + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -7620,6 +7675,14 @@ } } }, + "prismjs": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.16.0.tgz", + "integrity": "sha512-OA4MKxjFZHSvZcisLGe14THYsug/nF6O1f0pAJc0KN0wTyAcLqmsbE+lTGKSpyh+9pEW57+k6pg2AfYR+coyHA==", + "requires": { + "clipboard": "^2.0.0" + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -8769,6 +8832,12 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", + "optional": true + }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -9323,6 +9392,12 @@ "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", "dev": true }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", diff --git a/package.json b/package.json index 9acd811..a09266c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "ejs": "^2.6.2", "express": "^4.17.1", "ncp": "^2.0.0", + "node-prismjs": "^0.1.2", + "prismjs": "^1.16.0", "rss": "^1.2.2", "showdown": "^1.9.0" }, diff --git a/src/config.default.json b/src/config.default.json index a959260..6c9827d 100644 --- a/src/config.default.json +++ b/src/config.default.json @@ -6,7 +6,8 @@ "modules": { "plantuml": false, "rss": true, - "webhook": true + "webhook": true, + "prism": true }, "home": { "index": "index.ejs", diff --git a/src/renderer.js b/src/renderer.js index 236d931..f24a81e 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -1,5 +1,6 @@ const fs = require('fs'); const showdown = require('showdown'); +const Prism = require('node-prismjs'); module.exports = (config) => { const converter = new showdown.Converter(config['showdown']); @@ -8,9 +9,29 @@ module.exports = (config) => { fs.readFile(file, {encoding: 'UTF-8'}, (err, data) => { if (err) return cb(err); + + if (config['modules']['prism']) { + const codeRegex = /```([\w-]+)\n((?:(?!```)[\s\S])*)\n```/m; + let match; + while ((match = codeRegex.exec(data))) { + const lang = match[1].trim(); + const code = match[2].trim(); + try { + const block = Prism.highlight(code, Prism.languages[lang] || Prism.languages.autoit, lang); + data = data.slice(0, match.index) + `
` + block + '
' + data.slice(match.index + match[0].length); + } catch (err) { + console.error(err); + } + } + } + + const html = converter.makeHtml(data); + + cb(null, html); }); } }; -}; \ No newline at end of file +}; + diff --git a/test/renderer.test.js b/test/renderer.test.js index 37aa81f..c2912eb 100644 --- a/test/renderer.test.js +++ b/test/renderer.test.js @@ -7,6 +7,9 @@ const dataDir = 'test_data'; const file = path.join(dataDir, 'test.md'); const config = { + 'modules': { + 'prism': true, + }, 'showdown': { 'simplifiedAutoLink': true, 'smartIndentationFix': true @@ -50,4 +53,44 @@ test('custom rules', (done) => { expect(html).toBe('

www.google.com

'); done(); }); +}); + +test('no prism', (done) => { + config['modules']['prism'] = false; + fs.writeFileSync(file, '```python\nprint("hello")\n```\n\n```python\nprint("hello")\n```'); + renderer.render(file, (err, html) => { + expect(err).toBeNull(); + expect(html).toBe('
print("hello")\n
\n
print("hello")\n
'); + config['modules']['prism'] = true; + done(); + }); +}); + +test('prism correct', (done) => { + fs.writeFileSync(file, '```python\nprint("hello")\n```'); + renderer.render(file, (err, html) => { + expect(err).toBeNull(); + expect(html).not.toBe('
print("hello")\n
'); + expect(html.indexOf('
')).toBe(0);
+    done();
+  });
+});
+
+test('prism invalid lang', (done) => {
+  fs.writeFileSync(file, '```pythdon\nprint("hello")\n```');
+  renderer.render(file, (err, html) => {
+    expect(err).toBeNull();
+    expect(html).not.toBe('
print("hello")\n
'); + expect(html.indexOf('
')).toBe(0);
+    done();
+  });
+});
+
+test('prism mutliple code blocks', (done) => {
+  fs.writeFileSync(file, '```python\n\n```\n\n```python\n\n```');
+  renderer.render(file, (err, html) => {
+    expect(err).toBeNull();
+    expect(html).toBe('
\n
'); + done(); + }); }); \ No newline at end of file