-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmarkdown-it-myplugin.js
More file actions
155 lines (130 loc) · 5 KB
/
Copy pathmarkdown-it-myplugin.js
File metadata and controls
155 lines (130 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* 各種ダイアグラムなどを埋め込むプラグイン */
/* */
/* オプションの指定方法 */
/* コードフェンスの名前のあとにJSON形式で指定する */
/* 例 */
/* ```dita {"ext": "png, "width": 100} */
/* オプション一覧 */
/* ext: 図を保存するときの拡張子 */
/* width: 図の幅 */
/* height: 図の高さ */
/* 特にキャッシュなどは行わないので遅い */
const execSync = require('child_process').execSync;
const escape = require('escape-html');
const crypto = require('crypto');
const fs = require('fs');
// キャッシュとして作成するファイル名を算出する
let calcCacheName = (content, ext) => {
const digest = crypto.createHash('sha1').update(JSON.stringify(content)).digest('hex');
return `${digest}.${ext}`
};
// 各コードフェンスでのオプションのデフォルト値
const defaultOptions = {
ditaa: {
ext: 'svg',
},
dot: {
ext: 'svg',
},
plantuml: {
ext: 'svg',
}
};
// 変換対象の名前と変換処理の対応づけ
const renderFunctions = {
ditaa: (code, options) => {
const ext = options.ext || defaultOptions.ditaa.ext;
const validExtList = ["png", "svg"];
if ( validExtList.indexOf(ext) == -1 ) {
throw `extension "${ext}" is invalid for ditaa code fence`;
}
// 全角文字の直後にスペースをつけて幅を揃える
const paddedCode = code.replace(/([^\x01-\x7E]+)/g, (match) => {
let padded = match;
for (let i = 0; i < match.length; i++) {
padded += '\u200B'; // zero width space
// padded += ' ';
}
return padded;
});
const extCmdOption = ext === 'svg' ? '--svg' : '';
const command = `ditaa ${extCmdOption} --no-separation --no-shadows -`;
const output = execSync(command, {input: paddedCode});
return output;
},
dot: (code, options) => {
const ext = options.ext || defaultOptions.dot.ext;
const validExtList = ["png", "jpg", "jpeg", "svg"];
if ( validExtList.indexOf(ext) == -1 ) {
throw `extension "${ext}" is invalid for dot code fence`;
}
const command = `dot -T${ext}`;
const output = execSync(command, {input: code});
return output;
},
plantuml: (code, options) => {
const ext = options.ext || defaultOptions.plantuml.ext;
const validExtList = ["png", "jpg", "jpeg", "svg"];
if ( validExtList.indexOf(ext) == -1 ) {
throw `extension "${ext}" is invalid for plantuml code fence`;
}
const command = `plantuml -t${ext} -pipe`;
const output = execSync(command, {input: code});
return output;
}
};
let myPlugin = (md, options) => {
// もともとのフェンスレンダリング処理を取得(thisキーワードへの対策のためbindを使用する)
const defautFenceFunction = md.renderer.rules.fence.bind(md.renderer.rules);
md.renderer.rules.fence = function (tokens, idx, options, env, slf) {
const token = tokens[idx];
const info = token.info ? md.utils.unescapeAll(token.info).trim() : '';
let langName = '';
let fenceOptions = {};
let isTarget = false;
if (info) {
langName = info.split(/\s+/g)[0];
}
isTarget = langName in renderFunctions;
if (isTarget) {
const fenceOptionsStr = info.substring(langName.length).trim();
if (fenceOptionsStr) {
fenceOptions = JSON.parse(fenceOptionsStr);
}
}
if (isTarget) {
const code = token.content;
const ext = fenceOptions.ext || 'svg';
const imgWidthStr = fenceOptions.width ? `width=${escape(fenceOptions.width)}` : '';
const imgHeightStr = fenceOptions.height ? `height=${escape(fenceOptions.height)}` : '';
try {
// cacheディレクトリの作成
if (!fs.existsSync('cache')) {
fs.mkdirSync('cache');
}
const cacheName = calcCacheName({langName: langName, info: info, code: code}, ext);
const cachePath = `cache/${cacheName}`;
// キャッシュがある場合は処理を実行しない
if (!fs.existsSync(cachePath)) {
console.log(`${cachePath} is not exists. Creating it ...`);
const output = renderFunctions[langName](code, fenceOptions);
console.log(typeof (output));
fs.writeFileSync(cachePath, output, 'utf-8');
}
else {
console.log(`${cachePath} is already exists. Use this cache.`);
}
const tag = `<div><img src="../${cachePath}" ${imgWidthStr} ${imgHeightStr} /></div>`
return tag;
}
catch (ex) {
const escapedErrStr = escape(ex);
const tag = `<div><pre>${escapedErrStr}</pre></div>`
return tag;
}
}
// デフォルトの処理を行う
return defautFenceFunction(tokens, idx, options, env, slf);
}
}
module.exports = myPlugin;