markdansi 0.3.0 → 0.3.1

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 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/parser.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { Root } from "mdast";
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 { fromMarkdown } from "mdast-util-from-markdown";
2
- import { gfmFromMarkdown } from "mdast-util-gfm";
3
- import { gfm as gfmSyntax } from "micromark-extension-gfm";
4
- export function parse(markdown) {
5
- return fromMarkdown(markdown, {
6
- extensions: [gfmSyntax()],
7
- mdastExtensions: [gfmFromMarkdown()],
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
- default:
472
- if ("value" in node && typeof node.value === "string")
473
- out += node.value;
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
- - `micromark`, `micromark-extension-gfm`, `micromark-util-combine-extensions`: GFM parsing (tables, task lists, strikethrough, autolink literals).
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 micromark with combined GFM extensions → AST events.
69
+ 1. **Parse** via Marked's GFM lexerlightweight 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.0",
3
+ "version": "0.3.1",
4
4
  "description": "Tiny dependency-light markdown to ANSI converter.",
5
5
  "keywords": [
6
6
  "ansi",
@@ -48,28 +48,23 @@
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": "tsgo -p tsconfig.json --noEmit",
52
- "types": "tsgo -p tsconfig.json --emitDeclarationOnly",
53
- "compile": "tsgo -p tsconfig.json",
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
- "mdast-util-from-markdown": "^2.0.3",
60
- "mdast-util-gfm": "^3.1.0",
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
64
  "supports-hyperlinks": "^4.4.0"
68
65
  },
69
66
  "devDependencies": {
70
- "@types/mdast": "^4.0.4",
71
67
  "@types/node": "^25.6.0",
72
- "@typescript/native-preview": "7.0.0-dev.20260503.1",
73
68
  "@vitest/coverage-v8": "^4.1.5",
74
69
  "oxfmt": "^0.47.0",
75
70
  "oxlint": "^1.62.0",
@@ -79,10 +74,5 @@
79
74
  },
80
75
  "engines": {
81
76
  "node": ">=22"
82
- },
83
- "pnpm": {
84
- "onlyBuiltDependencies": [
85
- "esbuild"
86
- ]
87
77
  }
88
78
  }