markdansi 0.3.0 → 0.3.2
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/dist/ast.d.ts +99 -0
- package/dist/ast.js +1 -0
- package/dist/cli.js +71 -44
- package/dist/parser.d.ts +1 -1
- package/dist/parser.js +203 -7
- package/dist/render.js +3 -3
- package/docs/spec.md +3 -2
- package/package.json +13 -23
package/dist/ast.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type Position = unknown;
|
|
2
|
+
type Node = {
|
|
3
|
+
type: string;
|
|
4
|
+
position?: Position;
|
|
5
|
+
};
|
|
6
|
+
export type Text = Node & {
|
|
7
|
+
type: "text";
|
|
8
|
+
value: string;
|
|
9
|
+
};
|
|
10
|
+
export type Emphasis = Node & {
|
|
11
|
+
type: "emphasis";
|
|
12
|
+
children: InlineNode[];
|
|
13
|
+
};
|
|
14
|
+
export type Strong = Node & {
|
|
15
|
+
type: "strong";
|
|
16
|
+
children: InlineNode[];
|
|
17
|
+
};
|
|
18
|
+
export type Delete = Node & {
|
|
19
|
+
type: "delete";
|
|
20
|
+
children: InlineNode[];
|
|
21
|
+
};
|
|
22
|
+
export type InlineCode = Node & {
|
|
23
|
+
type: "inlineCode";
|
|
24
|
+
value: string;
|
|
25
|
+
};
|
|
26
|
+
export type Link = Node & {
|
|
27
|
+
type: "link";
|
|
28
|
+
url: string;
|
|
29
|
+
title?: string | null | undefined;
|
|
30
|
+
children: InlineNode[];
|
|
31
|
+
};
|
|
32
|
+
export type Break = Node & {
|
|
33
|
+
type: "break";
|
|
34
|
+
};
|
|
35
|
+
export type Html = Node & {
|
|
36
|
+
type: "html";
|
|
37
|
+
value: string;
|
|
38
|
+
};
|
|
39
|
+
export type InlineNode = Text | Emphasis | Strong | Delete | InlineCode | Link | Break | Html;
|
|
40
|
+
export type Paragraph = Node & {
|
|
41
|
+
type: "paragraph";
|
|
42
|
+
children: InlineNode[];
|
|
43
|
+
};
|
|
44
|
+
export type Heading = Node & {
|
|
45
|
+
type: "heading";
|
|
46
|
+
depth: number;
|
|
47
|
+
children: InlineNode[];
|
|
48
|
+
};
|
|
49
|
+
export type ThematicBreak = Node & {
|
|
50
|
+
type: "thematicBreak";
|
|
51
|
+
};
|
|
52
|
+
export type Blockquote = Node & {
|
|
53
|
+
type: "blockquote";
|
|
54
|
+
children: BlockNode[];
|
|
55
|
+
};
|
|
56
|
+
export type List = Node & {
|
|
57
|
+
type: "list";
|
|
58
|
+
ordered?: boolean | undefined;
|
|
59
|
+
start?: number | null | undefined;
|
|
60
|
+
spread?: boolean | undefined;
|
|
61
|
+
children: ListItem[];
|
|
62
|
+
};
|
|
63
|
+
export type ListItem = Node & {
|
|
64
|
+
type: "listItem";
|
|
65
|
+
checked?: boolean | null | undefined;
|
|
66
|
+
spread?: boolean | undefined;
|
|
67
|
+
children: BlockNode[];
|
|
68
|
+
};
|
|
69
|
+
export type Code = Node & {
|
|
70
|
+
type: "code";
|
|
71
|
+
value: string;
|
|
72
|
+
lang?: string | null | undefined;
|
|
73
|
+
meta?: string | null | undefined;
|
|
74
|
+
};
|
|
75
|
+
export type TableCell = Node & {
|
|
76
|
+
type: "tableCell";
|
|
77
|
+
children: InlineNode[];
|
|
78
|
+
};
|
|
79
|
+
export type TableRow = Node & {
|
|
80
|
+
type: "tableRow";
|
|
81
|
+
children: TableCell[];
|
|
82
|
+
};
|
|
83
|
+
export type Table = Node & {
|
|
84
|
+
type: "table";
|
|
85
|
+
align?: Array<"left" | "right" | "center" | null> | undefined;
|
|
86
|
+
children: TableRow[];
|
|
87
|
+
};
|
|
88
|
+
export type Definition = Node & {
|
|
89
|
+
type: "definition";
|
|
90
|
+
identifier: string;
|
|
91
|
+
url?: string | undefined;
|
|
92
|
+
title?: string | null | undefined;
|
|
93
|
+
};
|
|
94
|
+
export type BlockNode = Paragraph | Heading | ThematicBreak | Blockquote | List | ListItem | Code | Table | Definition;
|
|
95
|
+
export type Root = Node & {
|
|
96
|
+
type: "root";
|
|
97
|
+
children: BlockNode[];
|
|
98
|
+
};
|
|
99
|
+
export {};
|
package/dist/ast.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,31 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { render } from "./index.js";
|
|
6
|
+
function readOptionValue(argv, index, option, allowLeadingDashes = false) {
|
|
7
|
+
const value = argv[index + 1];
|
|
8
|
+
if (value === undefined || (!allowLeadingDashes && value.startsWith("--"))) {
|
|
9
|
+
throw new Error(`${option} requires a value`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function parseIntegerOption(option, value, minimum) {
|
|
14
|
+
const parsed = Number(value);
|
|
15
|
+
if (!/^\d+$/u.test(value) || !Number.isSafeInteger(parsed) || parsed < minimum) {
|
|
16
|
+
const range = minimum === 0 ? "a non-negative integer" : "a positive integer";
|
|
17
|
+
throw new Error(`${option} must be ${range}`);
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
function parseTheme(value) {
|
|
22
|
+
if (value === "default" || value === "dim" || value === "bright")
|
|
23
|
+
return value;
|
|
24
|
+
throw new Error("--theme must be default, dim, or bright");
|
|
25
|
+
}
|
|
26
|
+
function parseTableBorder(value) {
|
|
27
|
+
if (value === "unicode" || value === "ascii" || value === "none")
|
|
28
|
+
return value;
|
|
29
|
+
throw new Error("--table-border must be unicode, ascii, or none");
|
|
30
|
+
}
|
|
6
31
|
/**
|
|
7
32
|
* Ignore EPIPE when downstream (e.g., `head`) closes early.
|
|
8
33
|
*/
|
|
@@ -32,80 +57,85 @@ export function parseArgs(argv) {
|
|
|
32
57
|
args.color = false;
|
|
33
58
|
else if (a === "--no-links")
|
|
34
59
|
args.hyperlinks = false;
|
|
60
|
+
else if (a === "--code-wrap")
|
|
61
|
+
args.codeWrap = true;
|
|
35
62
|
else if (a === "--code-wrap=false")
|
|
36
63
|
args.codeWrap = false;
|
|
37
64
|
else if (a === "--code-wrap=true")
|
|
38
65
|
args.codeWrap = true;
|
|
66
|
+
else if (a === "--code-box")
|
|
67
|
+
args.codeBox = true;
|
|
39
68
|
else if (a === "--code-box=false")
|
|
40
69
|
args.codeBox = false;
|
|
41
70
|
else if (a === "--code-box=true")
|
|
42
71
|
args.codeBox = true;
|
|
72
|
+
else if (a === "--code-gutter")
|
|
73
|
+
args.codeGutter = true;
|
|
43
74
|
else if (a === "--code-gutter=true")
|
|
44
75
|
args.codeGutter = true;
|
|
45
76
|
else if (a === "--code-gutter=false")
|
|
46
77
|
args.codeGutter = false;
|
|
47
78
|
else if (a.startsWith("--table-border=")) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
79
|
+
args.tableBorder = parseTableBorder(a.slice("--table-border=".length));
|
|
80
|
+
}
|
|
81
|
+
else if (a === "--table-border") {
|
|
82
|
+
args.tableBorder = parseTableBorder(readOptionValue(argv, i, a));
|
|
83
|
+
i += 1;
|
|
51
84
|
}
|
|
52
85
|
else if (a === "--table-dense")
|
|
53
86
|
args.tableDense = true;
|
|
87
|
+
else if (a === "--table-truncate")
|
|
88
|
+
args.tableTruncate = true;
|
|
54
89
|
else if (a === "--table-truncate=false")
|
|
55
90
|
args.tableTruncate = false;
|
|
56
91
|
else if (a === "--table-truncate=true")
|
|
57
92
|
args.tableTruncate = true;
|
|
58
93
|
else if (a === "--table-padding") {
|
|
59
|
-
const next = argv
|
|
60
|
-
|
|
61
|
-
args.tablePadding = Number(next);
|
|
94
|
+
const next = readOptionValue(argv, i, a);
|
|
95
|
+
args.tablePadding = parseIntegerOption(a, next, 0);
|
|
62
96
|
i += 1;
|
|
63
97
|
}
|
|
64
98
|
else if (a === "--table-ellipsis") {
|
|
65
|
-
|
|
66
|
-
if (next)
|
|
67
|
-
args.tableEllipsis = next;
|
|
99
|
+
args.tableEllipsis = readOptionValue(argv, i, a, true);
|
|
68
100
|
i += 1;
|
|
69
101
|
}
|
|
70
102
|
else if (a === "--in") {
|
|
71
|
-
|
|
72
|
-
if (next)
|
|
73
|
-
args.in = next;
|
|
103
|
+
args.in = readOptionValue(argv, i, a, true);
|
|
74
104
|
i += 1;
|
|
75
105
|
}
|
|
76
106
|
else if (a === "--out") {
|
|
77
|
-
|
|
78
|
-
if (next)
|
|
79
|
-
args.out = next;
|
|
107
|
+
args.out = readOptionValue(argv, i, a, true);
|
|
80
108
|
i += 1;
|
|
81
109
|
}
|
|
82
110
|
else if (a === "--width") {
|
|
83
|
-
const next = argv
|
|
84
|
-
|
|
85
|
-
args.width = Number(next);
|
|
111
|
+
const next = readOptionValue(argv, i, a);
|
|
112
|
+
args.width = parseIntegerOption(a, next, 1);
|
|
86
113
|
i += 1;
|
|
87
114
|
}
|
|
88
115
|
else if (a.startsWith("--theme=")) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
116
|
+
args.theme = parseTheme(a.slice("--theme=".length));
|
|
117
|
+
}
|
|
118
|
+
else if (a === "--theme") {
|
|
119
|
+
args.theme = parseTheme(readOptionValue(argv, i, a));
|
|
120
|
+
i += 1;
|
|
92
121
|
}
|
|
93
122
|
else if (a === "--list-indent") {
|
|
94
|
-
const next = argv
|
|
95
|
-
|
|
96
|
-
args.listIndent = Number(next);
|
|
123
|
+
const next = readOptionValue(argv, i, a);
|
|
124
|
+
args.listIndent = parseIntegerOption(a, next, 0);
|
|
97
125
|
i += 1;
|
|
98
126
|
}
|
|
99
127
|
else if (a === "--quote-prefix") {
|
|
100
|
-
|
|
101
|
-
if (next)
|
|
102
|
-
args.quotePrefix = next;
|
|
128
|
+
args.quotePrefix = readOptionValue(argv, i, a, true);
|
|
103
129
|
i += 1;
|
|
104
130
|
}
|
|
105
131
|
else if (a === "--help" || a === "-h")
|
|
106
132
|
args.help = true;
|
|
107
|
-
else if (
|
|
133
|
+
else if (a.startsWith("-"))
|
|
134
|
+
throw new Error(`unknown option: ${a}`);
|
|
135
|
+
else if (!args.in)
|
|
108
136
|
args.in = a;
|
|
137
|
+
else
|
|
138
|
+
throw new Error(`unexpected positional argument: ${a}`);
|
|
109
139
|
}
|
|
110
140
|
return args;
|
|
111
141
|
}
|
|
@@ -114,8 +144,8 @@ export function parseArgs(argv) {
|
|
|
114
144
|
*/
|
|
115
145
|
function main() {
|
|
116
146
|
handleStdoutEpipe();
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
147
|
+
const { in: inputPath, out: outputPath, help, ...renderOptions } = parseArgs(process.argv);
|
|
148
|
+
if (help) {
|
|
119
149
|
process.stdout.write(`markdansi [FILE] [options]
|
|
120
150
|
|
|
121
151
|
markdansi file.md Render file
|
|
@@ -143,21 +173,12 @@ Options:
|
|
|
143
173
|
`);
|
|
144
174
|
process.exit(0);
|
|
145
175
|
}
|
|
146
|
-
const input =
|
|
147
|
-
? fs.readFileSync(path.resolve(
|
|
176
|
+
const input = inputPath && inputPath !== "-"
|
|
177
|
+
? fs.readFileSync(path.resolve(inputPath), "utf8")
|
|
148
178
|
: fs.readFileSync(0, "utf8");
|
|
149
|
-
const renderOptions = {
|
|
150
|
-
...(args.wrap !== undefined ? { wrap: args.wrap } : {}),
|
|
151
|
-
...(args.width !== undefined ? { width: args.width } : {}),
|
|
152
|
-
...(args.color !== undefined ? { color: args.color } : {}),
|
|
153
|
-
...(args.hyperlinks !== undefined ? { hyperlinks: args.hyperlinks } : {}),
|
|
154
|
-
...(args.theme !== undefined ? { theme: args.theme } : {}),
|
|
155
|
-
...(args.listIndent !== undefined ? { listIndent: args.listIndent } : {}),
|
|
156
|
-
...(args.quotePrefix !== undefined ? { quotePrefix: args.quotePrefix } : {}),
|
|
157
|
-
};
|
|
158
179
|
const output = render(input, renderOptions);
|
|
159
|
-
if (
|
|
160
|
-
fs.writeFileSync(path.resolve(
|
|
180
|
+
if (outputPath) {
|
|
181
|
+
fs.writeFileSync(path.resolve(outputPath), output, "utf8");
|
|
161
182
|
}
|
|
162
183
|
else {
|
|
163
184
|
process.stdout.write(output);
|
|
@@ -177,5 +198,11 @@ export function isDirectCliInvocation(metaUrl, argv1) {
|
|
|
177
198
|
}
|
|
178
199
|
// Only run the CLI when executed directly, not when imported for tests.
|
|
179
200
|
if (isDirectCliInvocation(import.meta.url, process.argv[1])) {
|
|
180
|
-
|
|
201
|
+
try {
|
|
202
|
+
main();
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.error(`markdansi: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
}
|
|
181
208
|
}
|
package/dist/parser.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Root } from "
|
|
1
|
+
import type { Root } from "./ast.js";
|
|
2
2
|
export declare function parse(markdown: string): Root;
|
package/dist/parser.js
CHANGED
|
@@ -1,9 +1,205 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { decodeNamedCharacterReference } from "decode-named-character-reference";
|
|
2
|
+
import { marked } from "marked";
|
|
3
|
+
const CHARACTER_REFERENCE = /&(#\d+|#x[\da-f]+|[a-z][\da-z]+);/giu;
|
|
4
|
+
function decodeNumericReference(body, radix) {
|
|
5
|
+
const offset = radix === 16 ? 2 : 1;
|
|
6
|
+
const codePoint = Number.parseInt(body.slice(offset), radix);
|
|
7
|
+
const isDisallowedControl = codePoint <= 0x08 ||
|
|
8
|
+
codePoint === 0x0b ||
|
|
9
|
+
(codePoint >= 0x0e && codePoint <= 0x1f) ||
|
|
10
|
+
(codePoint >= 0x7f && codePoint <= 0x9f);
|
|
11
|
+
const isNonCharacter = (codePoint >= 0xfdd0 && codePoint <= 0xfdef) ||
|
|
12
|
+
(codePoint & 0xffff) === 0xfffe ||
|
|
13
|
+
(codePoint & 0xffff) === 0xffff;
|
|
14
|
+
if (!Number.isSafeInteger(codePoint) ||
|
|
15
|
+
codePoint <= 0 ||
|
|
16
|
+
codePoint > 0x10ffff ||
|
|
17
|
+
(codePoint >= 0xd800 && codePoint <= 0xdfff) ||
|
|
18
|
+
isDisallowedControl ||
|
|
19
|
+
isNonCharacter) {
|
|
20
|
+
return "\uFFFD";
|
|
21
|
+
}
|
|
22
|
+
return String.fromCodePoint(codePoint);
|
|
23
|
+
}
|
|
24
|
+
function decodeEntities(value) {
|
|
25
|
+
return value.replace(CHARACTER_REFERENCE, (reference, body) => {
|
|
26
|
+
if (body.startsWith("#x") || body.startsWith("#X")) {
|
|
27
|
+
return decodeNumericReference(body, 16);
|
|
28
|
+
}
|
|
29
|
+
if (body.startsWith("#")) {
|
|
30
|
+
return decodeNumericReference(body, 10);
|
|
31
|
+
}
|
|
32
|
+
return decodeNamedCharacterReference(body) || reference;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function convertInlineTokens(tokens) {
|
|
36
|
+
if (!tokens)
|
|
37
|
+
return [];
|
|
38
|
+
const nodes = [];
|
|
39
|
+
for (const token of tokens) {
|
|
40
|
+
switch (token.type) {
|
|
41
|
+
case "text": {
|
|
42
|
+
const text = token;
|
|
43
|
+
if (text.tokens?.length) {
|
|
44
|
+
nodes.push(...convertInlineTokens(text.tokens));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
nodes.push({ type: "text", value: decodeEntities(text.text) });
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "escape":
|
|
52
|
+
nodes.push({ type: "text", value: token.text });
|
|
53
|
+
break;
|
|
54
|
+
case "em":
|
|
55
|
+
nodes.push({
|
|
56
|
+
type: "emphasis",
|
|
57
|
+
children: convertInlineTokens(token.tokens),
|
|
58
|
+
});
|
|
59
|
+
break;
|
|
60
|
+
case "strong":
|
|
61
|
+
nodes.push({
|
|
62
|
+
type: "strong",
|
|
63
|
+
children: convertInlineTokens(token.tokens),
|
|
64
|
+
});
|
|
65
|
+
break;
|
|
66
|
+
case "del":
|
|
67
|
+
nodes.push({
|
|
68
|
+
type: "delete",
|
|
69
|
+
children: convertInlineTokens(token.tokens),
|
|
70
|
+
});
|
|
71
|
+
break;
|
|
72
|
+
case "codespan":
|
|
73
|
+
nodes.push({ type: "inlineCode", value: token.text });
|
|
74
|
+
break;
|
|
75
|
+
case "link": {
|
|
76
|
+
const link = token;
|
|
77
|
+
nodes.push({
|
|
78
|
+
type: "link",
|
|
79
|
+
url: decodeEntities(link.href),
|
|
80
|
+
title: link.title ? decodeEntities(link.title) : link.title,
|
|
81
|
+
children: convertInlineTokens(link.tokens),
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "br":
|
|
86
|
+
nodes.push({ type: "break" });
|
|
87
|
+
break;
|
|
88
|
+
case "html":
|
|
89
|
+
nodes.push({ type: "html", value: token.text });
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return nodes;
|
|
96
|
+
}
|
|
97
|
+
function convertCode(token) {
|
|
98
|
+
const info = token.lang?.trim() ?? "";
|
|
99
|
+
const separator = info.search(/\s/u);
|
|
100
|
+
const lang = separator >= 0 ? info.slice(0, separator) : info;
|
|
101
|
+
const meta = separator >= 0 ? info.slice(separator).trim() : "";
|
|
102
|
+
return {
|
|
103
|
+
type: "code",
|
|
104
|
+
value: token.text,
|
|
105
|
+
...(lang ? { lang } : {}),
|
|
106
|
+
...(meta ? { meta } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function convertListItem(item) {
|
|
110
|
+
return {
|
|
111
|
+
type: "listItem",
|
|
112
|
+
checked: item.task ? Boolean(item.checked) : null,
|
|
113
|
+
spread: item.loose,
|
|
114
|
+
children: convertBlockTokens(item.tokens),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function convertTableCell(cell) {
|
|
118
|
+
return {
|
|
119
|
+
type: "tableCell",
|
|
120
|
+
children: convertInlineTokens(cell.tokens),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function convertTableRow(cells) {
|
|
124
|
+
return {
|
|
125
|
+
type: "tableRow",
|
|
126
|
+
children: cells.map(convertTableCell),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function convertBlockToken(token) {
|
|
130
|
+
switch (token.type) {
|
|
131
|
+
case "paragraph":
|
|
132
|
+
return {
|
|
133
|
+
type: "paragraph",
|
|
134
|
+
children: convertInlineTokens(token.tokens),
|
|
135
|
+
};
|
|
136
|
+
case "text": {
|
|
137
|
+
const text = token;
|
|
138
|
+
return {
|
|
139
|
+
type: "paragraph",
|
|
140
|
+
children: text.tokens?.length
|
|
141
|
+
? convertInlineTokens(text.tokens)
|
|
142
|
+
: [{ type: "text", value: decodeEntities(text.text) }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
case "heading": {
|
|
146
|
+
const heading = token;
|
|
147
|
+
return {
|
|
148
|
+
type: "heading",
|
|
149
|
+
depth: heading.depth,
|
|
150
|
+
children: convertInlineTokens(heading.tokens),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
case "hr":
|
|
154
|
+
return { type: "thematicBreak" };
|
|
155
|
+
case "blockquote":
|
|
156
|
+
return {
|
|
157
|
+
type: "blockquote",
|
|
158
|
+
children: convertBlockTokens(token.tokens),
|
|
159
|
+
};
|
|
160
|
+
case "list": {
|
|
161
|
+
const list = token;
|
|
162
|
+
return {
|
|
163
|
+
type: "list",
|
|
164
|
+
ordered: list.ordered,
|
|
165
|
+
start: list.ordered && typeof list.start === "number" ? list.start : null,
|
|
166
|
+
spread: list.loose,
|
|
167
|
+
children: list.items.map(convertListItem),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
case "code":
|
|
171
|
+
return convertCode(token);
|
|
172
|
+
case "table": {
|
|
173
|
+
const table = token;
|
|
174
|
+
return {
|
|
175
|
+
type: "table",
|
|
176
|
+
align: table.align,
|
|
177
|
+
children: [convertTableRow(table.header), ...table.rows.map(convertTableRow)],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
case "def": {
|
|
181
|
+
const definition = token;
|
|
182
|
+
const title = decodeEntities((definition.title ?? "").replace(/\s+/gu, " ").trim());
|
|
183
|
+
return {
|
|
184
|
+
type: "definition",
|
|
185
|
+
identifier: definition.tag,
|
|
186
|
+
url: decodeEntities(definition.href),
|
|
187
|
+
title: title || null,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
default:
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function convertBlockTokens(tokens) {
|
|
195
|
+
return tokens.flatMap((token) => {
|
|
196
|
+
const node = convertBlockToken(token);
|
|
197
|
+
return node ? [node] : [];
|
|
8
198
|
});
|
|
9
199
|
}
|
|
200
|
+
export function parse(markdown) {
|
|
201
|
+
return {
|
|
202
|
+
type: "root",
|
|
203
|
+
children: convertBlockTokens(marked.lexer(markdown, { gfm: true })),
|
|
204
|
+
};
|
|
205
|
+
}
|
package/dist/render.js
CHANGED
|
@@ -468,9 +468,9 @@ function renderInline(children, ctx) {
|
|
|
468
468
|
case "break":
|
|
469
469
|
out += HARD_BREAK;
|
|
470
470
|
break;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
471
|
+
case "html":
|
|
472
|
+
out += node.value;
|
|
473
|
+
break;
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
476
|
return out;
|
package/docs/spec.md
CHANGED
|
@@ -4,7 +4,8 @@ Goal: Tiny, dependency‑light Markdown → ANSI renderer & CLI for Node ≥22,
|
|
|
4
4
|
|
|
5
5
|
## Core Dependencies (runtime)
|
|
6
6
|
|
|
7
|
-
- `
|
|
7
|
+
- `marked`: GFM parsing (tables, task lists, strikethrough, autolink literals).
|
|
8
|
+
- `decode-named-character-reference`: decode Markdown character references in text and URLs.
|
|
8
9
|
- `chalk`: small, ESM‑only color/style helper.
|
|
9
10
|
- `string-width`: correct visible width (emoji / wide chars).
|
|
10
11
|
- `strip-ansi`: strip codes for width/wrapping.
|
|
@@ -65,7 +66,7 @@ Each theme entry holds simple SGR intents (bold/italic/fg color names). `inlineC
|
|
|
65
66
|
|
|
66
67
|
## Rendering Pipeline
|
|
67
68
|
|
|
68
|
-
1. **Parse** via
|
|
69
|
+
1. **Parse** via Marked's GFM lexer → lightweight internal AST.
|
|
69
70
|
2. **Build light IR** (nodes: paragraph, heading, list, listItem, taskItem, table, tableRow, tableCell, code, inline text/emph/strong/del/code/link).
|
|
70
71
|
3. **Render** to ANSI:
|
|
71
72
|
- Style map from theme to SGR codes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markdansi",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Tiny dependency-light markdown to ANSI converter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ansi",
|
|
@@ -48,41 +48,31 @@
|
|
|
48
48
|
"lint": "rm -rf dist coverage && pnpm format:check && oxlint --deny-warnings src test",
|
|
49
49
|
"test": "vitest run",
|
|
50
50
|
"test:coverage": "vitest run --coverage",
|
|
51
|
-
"typecheck": "
|
|
52
|
-
"types": "
|
|
53
|
-
"compile": "
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
52
|
+
"types": "tsc -p tsconfig.json --emitDeclarationOnly",
|
|
53
|
+
"compile": "tsc -p tsconfig.json",
|
|
54
54
|
"prepare": "pnpm compile",
|
|
55
55
|
"markdansi": "tsx src/cli.ts"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"chalk": "^5.6.2",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"micromark": "^4.0.2",
|
|
62
|
-
"micromark-extension-gfm": "^3.0.0",
|
|
63
|
-
"micromark-util-combine-extensions": "^2.0.1",
|
|
59
|
+
"decode-named-character-reference": "^1.3.0",
|
|
60
|
+
"marked": "^18.0.5",
|
|
64
61
|
"slice-ansi": "^9.0.0",
|
|
65
62
|
"string-width": "^8.2.1",
|
|
66
63
|
"strip-ansi": "^7.2.0",
|
|
67
|
-
"supports-hyperlinks": "^4.
|
|
64
|
+
"supports-hyperlinks": "^4.5.0"
|
|
68
65
|
},
|
|
69
66
|
"devDependencies": {
|
|
70
|
-
"@types/
|
|
71
|
-
"@
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"oxlint": "^1.62.0",
|
|
76
|
-
"tsx": "^4.21.0",
|
|
67
|
+
"@types/node": "^26.0.1",
|
|
68
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
69
|
+
"oxfmt": "^0.57.0",
|
|
70
|
+
"oxlint": "^1.72.0",
|
|
71
|
+
"tsx": "^4.22.4",
|
|
77
72
|
"typescript": "^6.0.3",
|
|
78
|
-
"vitest": "^4.1.
|
|
73
|
+
"vitest": "^4.1.9"
|
|
79
74
|
},
|
|
80
75
|
"engines": {
|
|
81
76
|
"node": ">=22"
|
|
82
|
-
},
|
|
83
|
-
"pnpm": {
|
|
84
|
-
"onlyBuiltDependencies": [
|
|
85
|
-
"esbuild"
|
|
86
|
-
]
|
|
87
77
|
}
|
|
88
78
|
}
|