markdownly.js 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/dist/cli.js +741 -0
- package/dist/index.js +720 -0
- package/package.json +51 -0
- package/readme.md +219 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/parser.ts
|
|
4
|
+
function parse(markdown) {
|
|
5
|
+
const lines = markdown.split(`
|
|
6
|
+
`);
|
|
7
|
+
const tokens = [];
|
|
8
|
+
let i = 0;
|
|
9
|
+
while (i < lines.length) {
|
|
10
|
+
const line = lines[i];
|
|
11
|
+
if (line.trim() === "") {
|
|
12
|
+
i++;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (line.match(/^```/)) {
|
|
16
|
+
const lang = line.slice(3).trim();
|
|
17
|
+
const codeLines = [];
|
|
18
|
+
i++;
|
|
19
|
+
while (i < lines.length && !lines[i].match(/^```/)) {
|
|
20
|
+
codeLines.push(lines[i]);
|
|
21
|
+
i++;
|
|
22
|
+
}
|
|
23
|
+
tokens.push({
|
|
24
|
+
type: "code_block",
|
|
25
|
+
content: codeLines.join(`
|
|
26
|
+
`),
|
|
27
|
+
meta: { lang }
|
|
28
|
+
});
|
|
29
|
+
i++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (line.match(/^ /)) {
|
|
33
|
+
const codeLines = [];
|
|
34
|
+
while (i < lines.length && (lines[i].match(/^ /) || lines[i].trim() === "")) {
|
|
35
|
+
codeLines.push(lines[i].replace(/^ /, ""));
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
tokens.push({
|
|
39
|
+
type: "code_block",
|
|
40
|
+
content: codeLines.join(`
|
|
41
|
+
`).trim(),
|
|
42
|
+
meta: { lang: "" }
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
47
|
+
if (headerMatch) {
|
|
48
|
+
const level = headerMatch[1].length;
|
|
49
|
+
tokens.push({
|
|
50
|
+
type: `h${level}`,
|
|
51
|
+
content: headerMatch[2],
|
|
52
|
+
children: parseInline(headerMatch[2])
|
|
53
|
+
});
|
|
54
|
+
i++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (line.match(/^(\*{3,}|-{3,}|_{3,})$/)) {
|
|
58
|
+
tokens.push({ type: "hr", content: "" });
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const alertMatch = line.match(/^>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]/);
|
|
63
|
+
if (alertMatch) {
|
|
64
|
+
const alertType = alertMatch[1].toLowerCase();
|
|
65
|
+
const alertLines = [];
|
|
66
|
+
i++;
|
|
67
|
+
while (i < lines.length && lines[i].match(/^>/)) {
|
|
68
|
+
alertLines.push(lines[i].replace(/^>\s?/, ""));
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
tokens.push({
|
|
72
|
+
type: "alert",
|
|
73
|
+
content: alertLines.join(`
|
|
74
|
+
`),
|
|
75
|
+
meta: { alertType },
|
|
76
|
+
children: parseInline(alertLines.join(`
|
|
77
|
+
`))
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (line.match(/^>/)) {
|
|
82
|
+
const quoteLines = [];
|
|
83
|
+
while (i < lines.length && lines[i].match(/^>/)) {
|
|
84
|
+
quoteLines.push(lines[i].replace(/^>\s?/, ""));
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
tokens.push({
|
|
88
|
+
type: "blockquote",
|
|
89
|
+
content: quoteLines.join(`
|
|
90
|
+
`),
|
|
91
|
+
children: parse(quoteLines.join(`
|
|
92
|
+
`))
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (i + 1 < lines.length && lines[i + 1].match(/^\|?[\s\-:|]+\|?$/)) {
|
|
97
|
+
const tableLines = [];
|
|
98
|
+
while (i < lines.length && lines[i].includes("|")) {
|
|
99
|
+
tableLines.push(lines[i]);
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
tokens.push({
|
|
103
|
+
type: "table",
|
|
104
|
+
content: "",
|
|
105
|
+
meta: { rows: parseTable(tableLines) }
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const listMatch = line.match(/^(\s*)([*+-]|\d+\.)\s/);
|
|
110
|
+
if (listMatch) {
|
|
111
|
+
const { items, endIndex } = parseList(lines, i);
|
|
112
|
+
tokens.push({
|
|
113
|
+
type: "list",
|
|
114
|
+
content: "",
|
|
115
|
+
children: items
|
|
116
|
+
});
|
|
117
|
+
i = endIndex;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const paraLines = [];
|
|
121
|
+
while (i < lines.length && lines[i].trim() !== "" && !lines[i].match(/^(#{1,6}\s|```|>|\*{3,}|-{3,}|_{3,}|(\s*)([*+-]|\d+\.)\s|\|)/)) {
|
|
122
|
+
paraLines.push(lines[i]);
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
if (paraLines.length > 0) {
|
|
126
|
+
const content = paraLines.join(`
|
|
127
|
+
`);
|
|
128
|
+
tokens.push({
|
|
129
|
+
type: "paragraph",
|
|
130
|
+
content,
|
|
131
|
+
children: parseInline(content)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return tokens;
|
|
136
|
+
}
|
|
137
|
+
function parseList(lines, startIndex) {
|
|
138
|
+
const items = [];
|
|
139
|
+
let i = startIndex;
|
|
140
|
+
while (i < lines.length) {
|
|
141
|
+
const line = lines[i];
|
|
142
|
+
const match = line.match(/^(\s*)([*+-]|\d+\.)\s(.*)$/);
|
|
143
|
+
if (!match && line.trim() === "") {
|
|
144
|
+
i++;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!match)
|
|
148
|
+
break;
|
|
149
|
+
const [, indent, marker, content] = match;
|
|
150
|
+
const ordered = /\d+\./.test(marker);
|
|
151
|
+
const checked = content.match(/^\[([ xX])\]\s*(.*)$/);
|
|
152
|
+
let itemContent = checked ? checked[2] : content;
|
|
153
|
+
const isChecked = checked ? checked[1].toLowerCase() === "x" : undefined;
|
|
154
|
+
i++;
|
|
155
|
+
while (i < lines.length) {
|
|
156
|
+
const nextLine = lines[i];
|
|
157
|
+
if (nextLine.trim() === "") {
|
|
158
|
+
i++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (nextLine.match(/^(\s*)([*+-]|\d+\.)\s/))
|
|
162
|
+
break;
|
|
163
|
+
if (nextLine.match(/^\s{2,}/)) {
|
|
164
|
+
itemContent += `
|
|
165
|
+
` + nextLine.trim();
|
|
166
|
+
i++;
|
|
167
|
+
} else {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
items.push({
|
|
172
|
+
type: "list_item",
|
|
173
|
+
content: itemContent,
|
|
174
|
+
children: parseInline(itemContent),
|
|
175
|
+
meta: { ordered, checked: isChecked }
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return { items, endIndex: i };
|
|
179
|
+
}
|
|
180
|
+
function parseTable(lines) {
|
|
181
|
+
const parseRow = (line) => line.split("|").map((c) => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length - 1 || arr.length <= 2);
|
|
182
|
+
const headers = parseRow(lines[0]);
|
|
183
|
+
const alignLine = lines[1];
|
|
184
|
+
const alignments = parseRow(alignLine).map((cell) => {
|
|
185
|
+
if (cell.startsWith(":") && cell.endsWith(":"))
|
|
186
|
+
return "center";
|
|
187
|
+
if (cell.endsWith(":"))
|
|
188
|
+
return "right";
|
|
189
|
+
return "left";
|
|
190
|
+
});
|
|
191
|
+
const rows = lines.slice(2).map(parseRow);
|
|
192
|
+
return { headers, alignments, rows };
|
|
193
|
+
}
|
|
194
|
+
function parseInline(text) {
|
|
195
|
+
const tokens = [];
|
|
196
|
+
let remaining = text;
|
|
197
|
+
while (remaining.length > 0) {
|
|
198
|
+
let matched = false;
|
|
199
|
+
const imgMatch = remaining.match(/^!\[([^\]]*)\]\(([^)]+)\)/);
|
|
200
|
+
if (imgMatch) {
|
|
201
|
+
tokens.push({
|
|
202
|
+
type: "image",
|
|
203
|
+
content: imgMatch[1] || "Image",
|
|
204
|
+
meta: { url: imgMatch[2] }
|
|
205
|
+
});
|
|
206
|
+
remaining = remaining.slice(imgMatch[0].length);
|
|
207
|
+
matched = true;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/);
|
|
211
|
+
if (linkMatch) {
|
|
212
|
+
tokens.push({
|
|
213
|
+
type: "link",
|
|
214
|
+
content: linkMatch[1],
|
|
215
|
+
meta: { url: linkMatch[2] }
|
|
216
|
+
});
|
|
217
|
+
remaining = remaining.slice(linkMatch[0].length);
|
|
218
|
+
matched = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const codeMatch = remaining.match(/^`([^`]+)`/);
|
|
222
|
+
if (codeMatch) {
|
|
223
|
+
tokens.push({ type: "code_inline", content: codeMatch[1] });
|
|
224
|
+
remaining = remaining.slice(codeMatch[0].length);
|
|
225
|
+
matched = true;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const boldMatch = remaining.match(/^(\*\*|__)([^*_]+)\1/);
|
|
229
|
+
if (boldMatch) {
|
|
230
|
+
tokens.push({
|
|
231
|
+
type: "bold",
|
|
232
|
+
content: boldMatch[2],
|
|
233
|
+
children: parseInline(boldMatch[2])
|
|
234
|
+
});
|
|
235
|
+
remaining = remaining.slice(boldMatch[0].length);
|
|
236
|
+
matched = true;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const italicMatch = remaining.match(/^(\*|_)([^*_]+)\1/);
|
|
240
|
+
if (italicMatch) {
|
|
241
|
+
tokens.push({
|
|
242
|
+
type: "italic",
|
|
243
|
+
content: italicMatch[2],
|
|
244
|
+
children: parseInline(italicMatch[2])
|
|
245
|
+
});
|
|
246
|
+
remaining = remaining.slice(italicMatch[0].length);
|
|
247
|
+
matched = true;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const strikeMatch = remaining.match(/^~~([^~]+)~~/);
|
|
251
|
+
if (strikeMatch) {
|
|
252
|
+
tokens.push({ type: "strikethrough", content: strikeMatch[1] });
|
|
253
|
+
remaining = remaining.slice(strikeMatch[0].length);
|
|
254
|
+
matched = true;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const markMatch = remaining.match(/^==([^=]+)==/);
|
|
258
|
+
if (markMatch) {
|
|
259
|
+
tokens.push({ type: "mark", content: markMatch[1] });
|
|
260
|
+
remaining = remaining.slice(markMatch[0].length);
|
|
261
|
+
matched = true;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const insMatch = remaining.match(/^\+\+([^+]+)\+\+/);
|
|
265
|
+
if (insMatch) {
|
|
266
|
+
tokens.push({ type: "ins", content: insMatch[1] });
|
|
267
|
+
remaining = remaining.slice(insMatch[0].length);
|
|
268
|
+
matched = true;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const supMatch = remaining.match(/^\^([^^]+)\^/);
|
|
272
|
+
if (supMatch) {
|
|
273
|
+
tokens.push({ type: "sup", content: supMatch[1] });
|
|
274
|
+
remaining = remaining.slice(supMatch[0].length);
|
|
275
|
+
matched = true;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const subMatch = remaining.match(/^~([^~]+)~/);
|
|
279
|
+
if (subMatch) {
|
|
280
|
+
tokens.push({ type: "sub", content: subMatch[1] });
|
|
281
|
+
remaining = remaining.slice(subMatch[0].length);
|
|
282
|
+
matched = true;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (remaining.startsWith(`
|
|
286
|
+
`)) {
|
|
287
|
+
tokens.push({ type: "br", content: `
|
|
288
|
+
` });
|
|
289
|
+
remaining = remaining.slice(1);
|
|
290
|
+
matched = true;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!matched) {
|
|
294
|
+
const nextSpecial = remaining.slice(1).search(/[*_`~\[!\^=+\n]/);
|
|
295
|
+
const end = nextSpecial === -1 ? remaining.length : nextSpecial + 1;
|
|
296
|
+
tokens.push({ type: "text", content: remaining.slice(0, end) });
|
|
297
|
+
remaining = remaining.slice(end);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return tokens;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/ansi.ts
|
|
304
|
+
var ESC = "\x1B[";
|
|
305
|
+
var RESET = `${ESC}0m`;
|
|
306
|
+
var wrap = (open, close) => (s) => `${ESC}${open}m${s}${ESC}${close}m`;
|
|
307
|
+
var red = wrap("31", "39");
|
|
308
|
+
var green = wrap("32", "39");
|
|
309
|
+
var yellow = wrap("33", "39");
|
|
310
|
+
var blue = wrap("34", "39");
|
|
311
|
+
var magenta = wrap("35", "39");
|
|
312
|
+
var cyan = wrap("36", "39");
|
|
313
|
+
var grey = wrap("90", "39");
|
|
314
|
+
var white = wrap("37", "39");
|
|
315
|
+
var black = wrap("30", "39");
|
|
316
|
+
var redBright = wrap("91", "39");
|
|
317
|
+
var greenBright = wrap("92", "39");
|
|
318
|
+
var yellowBright = wrap("93", "39");
|
|
319
|
+
var blueBright = wrap("94", "39");
|
|
320
|
+
var magentaBright = wrap("95", "39");
|
|
321
|
+
var cyanBright = wrap("96", "39");
|
|
322
|
+
var bgBlack = wrap("40", "49");
|
|
323
|
+
var bgRed = wrap("41", "49");
|
|
324
|
+
var bgGreen = wrap("42", "49");
|
|
325
|
+
var bgYellow = wrap("43", "49");
|
|
326
|
+
var bgBlue = wrap("44", "49");
|
|
327
|
+
var bgMagenta = wrap("45", "49");
|
|
328
|
+
var bgCyan = wrap("46", "49");
|
|
329
|
+
var bgWhite = wrap("47", "49");
|
|
330
|
+
var bold = wrap("1", "22");
|
|
331
|
+
var dim = wrap("2", "22");
|
|
332
|
+
var italic = wrap("3", "23");
|
|
333
|
+
var underline = wrap("4", "24");
|
|
334
|
+
var strikethrough = wrap("9", "29");
|
|
335
|
+
var compose = (...fns) => (s) => fns.filter(Boolean).reduce((acc, fn) => fn(acc), s);
|
|
336
|
+
var getTerminalWidth = () => {
|
|
337
|
+
try {
|
|
338
|
+
return process.stdout.columns || 80;
|
|
339
|
+
} catch {
|
|
340
|
+
return 80;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/highlight.ts
|
|
345
|
+
function highlight(code, lang) {
|
|
346
|
+
const l = lang.toLowerCase();
|
|
347
|
+
if (["js", "javascript", "ts", "typescript", "jsx", "tsx"].includes(l)) {
|
|
348
|
+
return highlightJS(code);
|
|
349
|
+
}
|
|
350
|
+
if (["go", "golang"].includes(l)) {
|
|
351
|
+
return highlightGo(code);
|
|
352
|
+
}
|
|
353
|
+
if (["py", "python"].includes(l)) {
|
|
354
|
+
return highlightPython(code);
|
|
355
|
+
}
|
|
356
|
+
if (["sh", "bash", "shell", "zsh"].includes(l)) {
|
|
357
|
+
return highlightBash(code);
|
|
358
|
+
}
|
|
359
|
+
if (["json"].includes(l)) {
|
|
360
|
+
return highlightJSON(code);
|
|
361
|
+
}
|
|
362
|
+
if (["yaml", "yml"].includes(l)) {
|
|
363
|
+
return highlightYAML(code);
|
|
364
|
+
}
|
|
365
|
+
if (["html", "xml"].includes(l)) {
|
|
366
|
+
return highlightHTML(code);
|
|
367
|
+
}
|
|
368
|
+
if (["css", "scss", "less"].includes(l)) {
|
|
369
|
+
return highlightCSS(code);
|
|
370
|
+
}
|
|
371
|
+
return yellowBright(code);
|
|
372
|
+
}
|
|
373
|
+
function highlightJS(code) {
|
|
374
|
+
const keywords = /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|new|this|class|extends|import|export|from|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|null|undefined|true|false|void|delete|yield)\b/g;
|
|
375
|
+
const strings = /(["'`])(?:(?!\1)[^\\]|\\.)*\1/g;
|
|
376
|
+
const comments = /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm;
|
|
377
|
+
const numbers = /\b(\d+\.?\d*)\b/g;
|
|
378
|
+
const funcs = /\b([a-zA-Z_]\w*)\s*\(/g;
|
|
379
|
+
const tokens = [];
|
|
380
|
+
let m;
|
|
381
|
+
while ((m = comments.exec(code)) !== null) {
|
|
382
|
+
tokens.push({ start: m.index, end: m.index + m[0].length, style: grey });
|
|
383
|
+
}
|
|
384
|
+
while ((m = strings.exec(code)) !== null) {
|
|
385
|
+
tokens.push({ start: m.index, end: m.index + m[0].length, style: green });
|
|
386
|
+
}
|
|
387
|
+
tokens.sort((a, b) => a.start - b.start);
|
|
388
|
+
let result = "";
|
|
389
|
+
let pos = 0;
|
|
390
|
+
for (const t of tokens) {
|
|
391
|
+
if (t.start < pos)
|
|
392
|
+
continue;
|
|
393
|
+
result += applyInlineStyles(code.slice(pos, t.start), keywords, numbers, funcs);
|
|
394
|
+
result += t.style(code.slice(t.start, t.end));
|
|
395
|
+
pos = t.end;
|
|
396
|
+
}
|
|
397
|
+
result += applyInlineStyles(code.slice(pos), keywords, numbers, funcs);
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
function applyInlineStyles(text, keywords, numbers, funcs) {
|
|
401
|
+
return text.replace(funcs, (match, name) => blue(name) + "(").replace(keywords, magenta("$1")).replace(numbers, yellow("$1"));
|
|
402
|
+
}
|
|
403
|
+
function highlightGo(code) {
|
|
404
|
+
const keywords = /\b(package|import|func|return|if|else|for|range|switch|case|break|continue|go|defer|select|chan|map|struct|interface|type|const|var|nil|true|false|make|new|len|cap|append|copy|delete|panic|recover)\b/g;
|
|
405
|
+
const strings = /(["'`])(?:(?!\1)[^\\]|\\.)*\1/g;
|
|
406
|
+
const comments = /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm;
|
|
407
|
+
const numbers = /\b(\d+\.?\d*)\b/g;
|
|
408
|
+
const tokens = [];
|
|
409
|
+
let m;
|
|
410
|
+
while ((m = comments.exec(code)) !== null) {
|
|
411
|
+
tokens.push({ start: m.index, end: m.index + m[0].length, style: grey });
|
|
412
|
+
}
|
|
413
|
+
while ((m = strings.exec(code)) !== null) {
|
|
414
|
+
tokens.push({ start: m.index, end: m.index + m[0].length, style: green });
|
|
415
|
+
}
|
|
416
|
+
tokens.sort((a, b) => a.start - b.start);
|
|
417
|
+
let result = "";
|
|
418
|
+
let pos = 0;
|
|
419
|
+
for (const t of tokens) {
|
|
420
|
+
if (t.start < pos)
|
|
421
|
+
continue;
|
|
422
|
+
result += code.slice(pos, t.start).replace(keywords, magenta("$1")).replace(numbers, yellow("$1"));
|
|
423
|
+
result += t.style(code.slice(t.start, t.end));
|
|
424
|
+
pos = t.end;
|
|
425
|
+
}
|
|
426
|
+
result += code.slice(pos).replace(keywords, magenta("$1")).replace(numbers, yellow("$1"));
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
function highlightPython(code) {
|
|
430
|
+
const keywords = /\b(def|class|return|if|elif|else|for|while|break|continue|import|from|as|try|except|finally|raise|with|lambda|yield|global|nonlocal|pass|None|True|False|and|or|not|in|is)\b/g;
|
|
431
|
+
const strings = /("""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g;
|
|
432
|
+
const comments = /(#.*$)/gm;
|
|
433
|
+
const numbers = /\b(\d+\.?\d*)\b/g;
|
|
434
|
+
const tokens = [];
|
|
435
|
+
let m;
|
|
436
|
+
while ((m = comments.exec(code)) !== null) {
|
|
437
|
+
tokens.push({ start: m.index, end: m.index + m[0].length, style: grey });
|
|
438
|
+
}
|
|
439
|
+
while ((m = strings.exec(code)) !== null) {
|
|
440
|
+
tokens.push({ start: m.index, end: m.index + m[0].length, style: green });
|
|
441
|
+
}
|
|
442
|
+
tokens.sort((a, b) => a.start - b.start);
|
|
443
|
+
let result = "";
|
|
444
|
+
let pos = 0;
|
|
445
|
+
for (const t of tokens) {
|
|
446
|
+
if (t.start < pos)
|
|
447
|
+
continue;
|
|
448
|
+
result += code.slice(pos, t.start).replace(keywords, magenta("$1")).replace(numbers, yellow("$1"));
|
|
449
|
+
result += t.style(code.slice(t.start, t.end));
|
|
450
|
+
pos = t.end;
|
|
451
|
+
}
|
|
452
|
+
result += code.slice(pos).replace(keywords, magenta("$1")).replace(numbers, yellow("$1"));
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
function highlightBash(code) {
|
|
456
|
+
const keywords = /\b(if|then|else|elif|fi|for|while|do|done|case|esac|function|return|exit|echo|cd|ls|rm|cp|mv|mkdir|cat|grep|sed|awk|export|source|local)\b/g;
|
|
457
|
+
const strings = /("(?:[^"\\]|\\.)*"|'[^']*')/g;
|
|
458
|
+
const comments = /(#.*$)/gm;
|
|
459
|
+
const vars = /(\$\{?[a-zA-Z_]\w*\}?)/g;
|
|
460
|
+
return code.replace(comments, grey("$1")).replace(strings, green("$1")).replace(vars, cyan("$1")).replace(keywords, magenta("$1"));
|
|
461
|
+
}
|
|
462
|
+
function highlightJSON(code) {
|
|
463
|
+
const keys = /("(?:[^"\\]|\\.)*")\s*:/g;
|
|
464
|
+
const strings = /:\s*("(?:[^"\\]|\\.)*")/g;
|
|
465
|
+
const numbers = /:\s*(\d+\.?\d*)/g;
|
|
466
|
+
const bools = /\b(true|false|null)\b/g;
|
|
467
|
+
return code.replace(keys, cyan("$1") + ":").replace(strings, ": " + green("$1")).replace(numbers, ": " + yellow("$1")).replace(bools, magenta("$1"));
|
|
468
|
+
}
|
|
469
|
+
function highlightYAML(code) {
|
|
470
|
+
const keys = /^(\s*[a-zA-Z_-][a-zA-Z0-9_-]*):/gm;
|
|
471
|
+
const strings = /:\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g;
|
|
472
|
+
const comments = /(#.*$)/gm;
|
|
473
|
+
const bools = /\b(true|false|null|yes|no)\b/gi;
|
|
474
|
+
return code.replace(comments, grey("$1")).replace(keys, cyan("$1") + ":").replace(strings, ": " + green("$1")).replace(bools, magenta("$1"));
|
|
475
|
+
}
|
|
476
|
+
function highlightHTML(code) {
|
|
477
|
+
const comments = /(<!--[\s\S]*?-->)/g;
|
|
478
|
+
const tags = /(<\/?[a-zA-Z][a-zA-Z0-9]*)/g;
|
|
479
|
+
const attrs = /(\s[a-zA-Z-]+)=/g;
|
|
480
|
+
const strings = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g;
|
|
481
|
+
return code.replace(comments, grey("$1")).replace(tags, blue("$1")).replace(attrs, cyan("$1") + "=").replace(strings, green("$1"));
|
|
482
|
+
}
|
|
483
|
+
function highlightCSS(code) {
|
|
484
|
+
const comments = /(\/\*[\s\S]*?\*\/)/g;
|
|
485
|
+
const selectors = /([.#]?[a-zA-Z_-][a-zA-Z0-9_-]*)\s*\{/g;
|
|
486
|
+
const props = /([a-zA-Z-]+)\s*:/g;
|
|
487
|
+
const values = /(#[0-9a-fA-F]{3,8}|\d+\.?\d*(px|em|rem|%|vh|vw)?)/g;
|
|
488
|
+
return code.replace(comments, grey("$1")).replace(selectors, blue("$1") + " {").replace(props, cyan("$1") + ":").replace(values, yellow("$1"));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/renderer.ts
|
|
492
|
+
var getWidth = () => Math.min(120, getTerminalWidth() - 2);
|
|
493
|
+
function render(tokens) {
|
|
494
|
+
const lines = [];
|
|
495
|
+
const width = getWidth();
|
|
496
|
+
for (const token of tokens) {
|
|
497
|
+
const rendered = renderToken(token, { width, indent: "" });
|
|
498
|
+
if (rendered)
|
|
499
|
+
lines.push(rendered);
|
|
500
|
+
}
|
|
501
|
+
return `
|
|
502
|
+
` + lines.join(`
|
|
503
|
+
|
|
504
|
+
`) + `
|
|
505
|
+
|
|
506
|
+
`;
|
|
507
|
+
}
|
|
508
|
+
function renderToken(token, ctx) {
|
|
509
|
+
switch (token.type) {
|
|
510
|
+
case "h1":
|
|
511
|
+
return `${compose(red, bold)("#")} ${renderInline(token.children || [])}`;
|
|
512
|
+
case "h2":
|
|
513
|
+
return `${compose(blue, bold)("##")} ${renderInline(token.children || [])}`;
|
|
514
|
+
case "h3":
|
|
515
|
+
return `${compose(blue, bold)("###")} ${renderInline(token.children || [])}`;
|
|
516
|
+
case "h4":
|
|
517
|
+
return `${compose(cyan, bold)("####")} ${renderInline(token.children || [])}`;
|
|
518
|
+
case "h5":
|
|
519
|
+
return `${cyan("#####")} ${renderInline(token.children || [])}`;
|
|
520
|
+
case "h6":
|
|
521
|
+
return `${cyan("######")} ${renderInline(token.children || [])}`;
|
|
522
|
+
case "paragraph":
|
|
523
|
+
return wrapText(renderInline(token.children || []), ctx.width);
|
|
524
|
+
case "hr":
|
|
525
|
+
return grey("─".repeat(ctx.width));
|
|
526
|
+
case "blockquote":
|
|
527
|
+
return renderBlockquote(token, ctx);
|
|
528
|
+
case "alert":
|
|
529
|
+
return renderAlert(token, ctx);
|
|
530
|
+
case "code_block":
|
|
531
|
+
return renderCodeBlock(token, ctx);
|
|
532
|
+
case "list":
|
|
533
|
+
return renderList(token, ctx);
|
|
534
|
+
case "table":
|
|
535
|
+
return renderTable(token, ctx);
|
|
536
|
+
default:
|
|
537
|
+
return token.content;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function renderInline(tokens) {
|
|
541
|
+
return tokens.map((t) => {
|
|
542
|
+
switch (t.type) {
|
|
543
|
+
case "text":
|
|
544
|
+
return t.content;
|
|
545
|
+
case "bold":
|
|
546
|
+
return bold(renderInline(t.children || [{ type: "text", content: t.content }]));
|
|
547
|
+
case "italic":
|
|
548
|
+
return italic(renderInline(t.children || [{ type: "text", content: t.content }]));
|
|
549
|
+
case "strikethrough":
|
|
550
|
+
return strikethrough(t.content);
|
|
551
|
+
case "code_inline":
|
|
552
|
+
return compose(bgBlack, yellowBright)(t.content);
|
|
553
|
+
case "link":
|
|
554
|
+
return compose(blue, underline)(t.content);
|
|
555
|
+
case "image":
|
|
556
|
+
return cyan("!") + grey("[") + cyan(t.content) + grey("]");
|
|
557
|
+
case "mark":
|
|
558
|
+
return compose(bgYellow, black)(t.content);
|
|
559
|
+
case "ins":
|
|
560
|
+
return compose(bgGreen, black)(t.content);
|
|
561
|
+
case "sup":
|
|
562
|
+
case "sub":
|
|
563
|
+
return t.content;
|
|
564
|
+
case "br":
|
|
565
|
+
return `
|
|
566
|
+
`;
|
|
567
|
+
default:
|
|
568
|
+
return t.content;
|
|
569
|
+
}
|
|
570
|
+
}).join("");
|
|
571
|
+
}
|
|
572
|
+
function renderBlockquote(token, ctx) {
|
|
573
|
+
const innerCtx = { ...ctx, width: ctx.width - 2 };
|
|
574
|
+
const content = token.children ? token.children.map((t) => renderToken(t, innerCtx)).join(`
|
|
575
|
+
|
|
576
|
+
`) : token.content;
|
|
577
|
+
return content.split(`
|
|
578
|
+
`).map((line) => grey("│ ") + line).join(`
|
|
579
|
+
`);
|
|
580
|
+
}
|
|
581
|
+
function renderAlert(token, ctx) {
|
|
582
|
+
const alertType = token.meta?.alertType || "note";
|
|
583
|
+
const colors = {
|
|
584
|
+
note: blue,
|
|
585
|
+
tip: green,
|
|
586
|
+
important: red,
|
|
587
|
+
warning: yellow,
|
|
588
|
+
caution: magenta
|
|
589
|
+
};
|
|
590
|
+
const color = colors[alertType] || blue;
|
|
591
|
+
const title = alertType.charAt(0).toUpperCase() + alertType.slice(1);
|
|
592
|
+
const innerCtx = { ...ctx, width: ctx.width - 2 };
|
|
593
|
+
const content = wrapText(renderInline(token.children || []), innerCtx.width);
|
|
594
|
+
const lines = [
|
|
595
|
+
color("│ ") + color(`• ${title}`),
|
|
596
|
+
...content.split(`
|
|
597
|
+
`).map((line) => color("│ ") + line)
|
|
598
|
+
];
|
|
599
|
+
return lines.join(`
|
|
600
|
+
`);
|
|
601
|
+
}
|
|
602
|
+
function renderCodeBlock(token, ctx) {
|
|
603
|
+
const lang = token.meta?.lang || "";
|
|
604
|
+
const code = token.content;
|
|
605
|
+
const highlighted = lang ? highlight(code, lang) : yellowBright(code);
|
|
606
|
+
const lines = highlighted.split(`
|
|
607
|
+
`);
|
|
608
|
+
const numWidth = String(lines.length).length;
|
|
609
|
+
return lines.map((line, i) => {
|
|
610
|
+
const num = compose(grey, dim)(String(i + 1).padStart(numWidth));
|
|
611
|
+
return ` ${num} ${line}`;
|
|
612
|
+
}).join(`
|
|
613
|
+
`);
|
|
614
|
+
}
|
|
615
|
+
function renderList(token, ctx) {
|
|
616
|
+
const items = token.children || [];
|
|
617
|
+
let orderedNum = 1;
|
|
618
|
+
return items.map((item) => {
|
|
619
|
+
const ordered = item.meta?.ordered;
|
|
620
|
+
const checked = item.meta?.checked;
|
|
621
|
+
let bullet;
|
|
622
|
+
if (checked !== undefined) {
|
|
623
|
+
const check = checked ? green("✓") : " ";
|
|
624
|
+
bullet = grey("[") + check + grey("]");
|
|
625
|
+
} else if (ordered) {
|
|
626
|
+
bullet = blueBright(`${orderedNum++}.`);
|
|
627
|
+
} else {
|
|
628
|
+
bullet = redBright("•");
|
|
629
|
+
}
|
|
630
|
+
const content = renderInline(item.children || []);
|
|
631
|
+
const wrapped = wrapText(content, ctx.width - 4);
|
|
632
|
+
const indented = wrapped.split(`
|
|
633
|
+
`).map((line, i) => i === 0 ? `${bullet} ${line}` : ` ${line}`).join(`
|
|
634
|
+
`);
|
|
635
|
+
return indented;
|
|
636
|
+
}).join(`
|
|
637
|
+
`);
|
|
638
|
+
}
|
|
639
|
+
function renderTable(token, ctx) {
|
|
640
|
+
const { headers, alignments, rows } = token.meta?.rows || { headers: [], alignments: [], rows: [] };
|
|
641
|
+
if (!headers.length)
|
|
642
|
+
return "";
|
|
643
|
+
const colWidths = headers.map((h, i) => {
|
|
644
|
+
const cellWidths = [h.length, ...rows.map((r) => (r[i] || "").length)];
|
|
645
|
+
return Math.max(...cellWidths);
|
|
646
|
+
});
|
|
647
|
+
const pad = (s, w, align) => {
|
|
648
|
+
const diff = w - s.length;
|
|
649
|
+
if (diff <= 0)
|
|
650
|
+
return s;
|
|
651
|
+
if (align === "center") {
|
|
652
|
+
const left = Math.floor(diff / 2);
|
|
653
|
+
return " ".repeat(left) + s + " ".repeat(diff - left);
|
|
654
|
+
}
|
|
655
|
+
if (align === "right")
|
|
656
|
+
return " ".repeat(diff) + s;
|
|
657
|
+
return s + " ".repeat(diff);
|
|
658
|
+
};
|
|
659
|
+
const renderRow = (cells, isHeader = false) => {
|
|
660
|
+
const styled = cells.map((cell, i) => {
|
|
661
|
+
const padded = pad(cell, colWidths[i], alignments[i] || "left");
|
|
662
|
+
return isHeader ? compose(bold, red)(padded) : padded;
|
|
663
|
+
});
|
|
664
|
+
return "│ " + styled.join(" │ ") + " │";
|
|
665
|
+
};
|
|
666
|
+
const separator = "├" + colWidths.map((w) => "─".repeat(w + 2)).join("┼") + "┤";
|
|
667
|
+
const top = "┌" + colWidths.map((w) => "─".repeat(w + 2)).join("┬") + "┐";
|
|
668
|
+
const bottom = "└" + colWidths.map((w) => "─".repeat(w + 2)).join("┴") + "┘";
|
|
669
|
+
const tableLines = [
|
|
670
|
+
top,
|
|
671
|
+
renderRow(headers, true),
|
|
672
|
+
separator,
|
|
673
|
+
...rows.map((r) => renderRow(r)),
|
|
674
|
+
bottom
|
|
675
|
+
];
|
|
676
|
+
return tableLines.join(`
|
|
677
|
+
`);
|
|
678
|
+
}
|
|
679
|
+
function wrapText(text, width) {
|
|
680
|
+
if (width <= 0)
|
|
681
|
+
return text;
|
|
682
|
+
const lines = [];
|
|
683
|
+
for (const paragraph of text.split(`
|
|
684
|
+
`)) {
|
|
685
|
+
if (stripAnsi(paragraph).length <= width) {
|
|
686
|
+
lines.push(paragraph);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
const words = paragraph.split(" ");
|
|
690
|
+
let current = "";
|
|
691
|
+
for (const word of words) {
|
|
692
|
+
const test = current ? current + " " + word : word;
|
|
693
|
+
if (stripAnsi(test).length <= width) {
|
|
694
|
+
current = test;
|
|
695
|
+
} else {
|
|
696
|
+
if (current)
|
|
697
|
+
lines.push(current);
|
|
698
|
+
current = word;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (current)
|
|
702
|
+
lines.push(current);
|
|
703
|
+
}
|
|
704
|
+
return lines.join(`
|
|
705
|
+
`);
|
|
706
|
+
}
|
|
707
|
+
function stripAnsi(s) {
|
|
708
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/index.ts
|
|
712
|
+
function markdown(input) {
|
|
713
|
+
const tokens = parse(input);
|
|
714
|
+
return render(tokens);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/cli.ts
|
|
718
|
+
import { readFileSync } from "fs";
|
|
719
|
+
async function main() {
|
|
720
|
+
const args = process.argv.slice(2);
|
|
721
|
+
let input;
|
|
722
|
+
if (args.length > 0) {
|
|
723
|
+
try {
|
|
724
|
+
input = readFileSync(args[0], "utf-8");
|
|
725
|
+
} catch {
|
|
726
|
+
console.error(`File not found: ${args[0]}`);
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
const chunks = [];
|
|
731
|
+
for await (const chunk of process.stdin) {
|
|
732
|
+
chunks.push(Buffer.from(chunk));
|
|
733
|
+
}
|
|
734
|
+
input = Buffer.concat(chunks).toString("utf-8");
|
|
735
|
+
}
|
|
736
|
+
process.stdout.write(markdown(input));
|
|
737
|
+
}
|
|
738
|
+
main().catch((err) => {
|
|
739
|
+
console.error(err);
|
|
740
|
+
process.exit(1);
|
|
741
|
+
});
|