md4x 0.0.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/build/.gitkeep +0 -0
- package/build/md4x.darwin-arm64.node +0 -0
- package/build/md4x.darwin-x64.node +0 -0
- package/build/md4x.linux-arm64.node +0 -0
- package/build/md4x.linux-x64.node +0 -0
- package/build/md4x.wasm +0 -0
- package/build/md4x.win32-arm64.node +0 -0
- package/build/md4x.win32-x64.node +0 -0
- package/lib/cli.mjs +138 -0
- package/lib/napi.d.mts +7 -0
- package/lib/napi.mjs +27 -0
- package/lib/types.d.ts +18 -0
- package/lib/wasm.d.mts +8 -0
- package/lib/wasm.mjs +82 -0
- package/package.json +38 -0
package/build/.gitkeep
ADDED
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/build/md4x.wasm
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/cli.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { renderToHtml, renderToAnsi, renderToJson } from "./napi.mjs";
|
|
6
|
+
|
|
7
|
+
const { values, positionals } = parseArgs({
|
|
8
|
+
allowPositionals: true,
|
|
9
|
+
options: {
|
|
10
|
+
output: { type: "string", short: "o" },
|
|
11
|
+
format: {
|
|
12
|
+
type: "string",
|
|
13
|
+
short: "t",
|
|
14
|
+
default: process.stdout.isTTY ? "ansi" : "html",
|
|
15
|
+
},
|
|
16
|
+
"full-html": { type: "boolean", short: "f", default: false },
|
|
17
|
+
"html-title": { type: "string" },
|
|
18
|
+
"html-css": { type: "string" },
|
|
19
|
+
stat: { type: "boolean", short: "s", default: false },
|
|
20
|
+
help: { type: "boolean", short: "h", default: false },
|
|
21
|
+
version: { type: "boolean", short: "v", default: false },
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function usage() {
|
|
26
|
+
process.stderr.write(
|
|
27
|
+
`Usage: md4x [OPTION]... [FILE]
|
|
28
|
+
Convert input FILE (or standard input) in Markdown format.
|
|
29
|
+
|
|
30
|
+
General options:
|
|
31
|
+
-o --output=FILE Output file (default is standard output)
|
|
32
|
+
-t, --format=FORMAT Output format: html, json, ansi (default: ansi for TTY, html otherwise)
|
|
33
|
+
-s, --stat Measure time of input parsing
|
|
34
|
+
-h, --help Display this help and exit
|
|
35
|
+
-v, --version Display version and exit
|
|
36
|
+
|
|
37
|
+
HTML output options:
|
|
38
|
+
-f, --full-html Generate full HTML document, including header
|
|
39
|
+
--html-title=TITLE Sets the title of the document
|
|
40
|
+
--html-css=URL In full HTML mode add a css link
|
|
41
|
+
`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (values.help) {
|
|
46
|
+
usage();
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (values.version) {
|
|
51
|
+
const { version } = JSON.parse(
|
|
52
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
53
|
+
);
|
|
54
|
+
process.stdout.write(`${version}\n`);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const format = values.format;
|
|
59
|
+
if (!["html", "json", "ansi"].includes(format)) {
|
|
60
|
+
process.stderr.write(`Unknown format: ${format}\n`);
|
|
61
|
+
process.stderr.write("Supported formats: html, json, ansi\n");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const inputPath = positionals[0];
|
|
66
|
+
let input;
|
|
67
|
+
if (!inputPath || inputPath === "-") {
|
|
68
|
+
if (process.stdin.isTTY) {
|
|
69
|
+
usage();
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
input = readFileSync(0, "utf8");
|
|
74
|
+
} catch {
|
|
75
|
+
usage();
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
try {
|
|
80
|
+
input = readFileSync(inputPath, "utf8");
|
|
81
|
+
} catch {
|
|
82
|
+
process.stderr.write(`Cannot open ${inputPath}.\n`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const t0 = values.stat ? performance.now() : 0;
|
|
88
|
+
|
|
89
|
+
let output;
|
|
90
|
+
switch (format) {
|
|
91
|
+
case "html":
|
|
92
|
+
output = renderToHtml(input);
|
|
93
|
+
break;
|
|
94
|
+
case "json":
|
|
95
|
+
output = JSON.stringify(renderToJson(input));
|
|
96
|
+
break;
|
|
97
|
+
case "ansi":
|
|
98
|
+
output = renderToAnsi(input);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (values.stat) {
|
|
103
|
+
const elapsed = performance.now() - t0;
|
|
104
|
+
if (elapsed < 1000) {
|
|
105
|
+
process.stderr.write(`Time spent on parsing: ${elapsed.toFixed(2)} ms.\n`);
|
|
106
|
+
} else {
|
|
107
|
+
process.stderr.write(
|
|
108
|
+
`Time spent on parsing: ${(elapsed / 1000).toFixed(3)} s.\n`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let result = "";
|
|
114
|
+
if (values["full-html"] && format === "html") {
|
|
115
|
+
result += "<!DOCTYPE html>\n<html>\n<head>\n";
|
|
116
|
+
result += `<title>${values["html-title"] || ""}</title>\n`;
|
|
117
|
+
result += '<meta name="generator" content="md4x">\n';
|
|
118
|
+
result += '<meta charset="UTF-8">\n';
|
|
119
|
+
if (values["html-css"]) {
|
|
120
|
+
result += `<link rel="stylesheet" href="${values["html-css"]}">\n`;
|
|
121
|
+
}
|
|
122
|
+
result += "</head>\n<body>\n";
|
|
123
|
+
result += output;
|
|
124
|
+
result += "</body>\n</html>\n";
|
|
125
|
+
} else {
|
|
126
|
+
result = output;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (values.output && values.output !== "-") {
|
|
130
|
+
try {
|
|
131
|
+
writeFileSync(values.output, result);
|
|
132
|
+
} catch {
|
|
133
|
+
process.stderr.write(`Cannot open ${values.output}.\n`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
process.stdout.write(result);
|
|
138
|
+
}
|
package/lib/napi.d.mts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComarkTree } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export type { ComarkTree, ComarkNode, ComarkElement, ComarkText, ComarkElementAttributes } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export declare function renderToHtml(input: string): string;
|
|
6
|
+
export declare function renderToJson(input: string): ComarkTree;
|
|
7
|
+
export declare function renderToAnsi(input: string): string;
|
package/lib/napi.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { arch, platform } from "node:process";
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
function loadBinding() {
|
|
7
|
+
const name = `md4x.${platform}-${arch}`;
|
|
8
|
+
try {
|
|
9
|
+
return require(`../build/${name}.node`);
|
|
10
|
+
} catch {
|
|
11
|
+
return require("../build/md4x.node");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const binding = loadBinding();
|
|
16
|
+
|
|
17
|
+
export function renderToHtml(input) {
|
|
18
|
+
return binding.renderToHtml(input);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function renderToJson(input) {
|
|
22
|
+
return JSON.parse(binding.renderToJson(input));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function renderToAnsi(input) {
|
|
26
|
+
return binding.renderToAnsi(input);
|
|
27
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ComarkTree = {
|
|
2
|
+
type: "comark";
|
|
3
|
+
value: ComarkNode[];
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type ComarkNode = ComarkElement | ComarkText;
|
|
7
|
+
|
|
8
|
+
export type ComarkText = string;
|
|
9
|
+
|
|
10
|
+
export type ComarkElement = [
|
|
11
|
+
tag: string,
|
|
12
|
+
props: ComarkElementAttributes,
|
|
13
|
+
...children: ComarkNode[],
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export type ComarkElementAttributes = {
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
};
|
package/lib/wasm.d.mts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComarkTree } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export type { ComarkTree, ComarkNode, ComarkElement, ComarkText, ComarkElementAttributes } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export declare function initWasm(input?: ArrayBuffer | Uint8Array | WebAssembly.Module | Response | Promise<Response>): Promise<void>;
|
|
6
|
+
export declare function renderToHtml(input: string): string;
|
|
7
|
+
export declare function renderToJson(input: string): ComarkTree;
|
|
8
|
+
export declare function renderToAnsi(input: string): string;
|
package/lib/wasm.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
let _instance;
|
|
2
|
+
|
|
3
|
+
function getExports() {
|
|
4
|
+
if (!_instance) {
|
|
5
|
+
throw new Error("md4x: WASM not initialized. Call `await initWasm()` first.");
|
|
6
|
+
}
|
|
7
|
+
return _instance.exports;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function initWasm(input) {
|
|
11
|
+
if (_instance) return;
|
|
12
|
+
let bytes;
|
|
13
|
+
if (input instanceof ArrayBuffer || input instanceof Uint8Array) {
|
|
14
|
+
bytes = input;
|
|
15
|
+
} else if (input instanceof WebAssembly.Module) {
|
|
16
|
+
const { instance } = await WebAssembly.instantiate(input, {
|
|
17
|
+
wasi_snapshot_preview1: wasiStub,
|
|
18
|
+
});
|
|
19
|
+
_instance = instance;
|
|
20
|
+
return;
|
|
21
|
+
} else if (input instanceof Response || (typeof input === "object" && typeof input.then === "function")) {
|
|
22
|
+
const response = await input;
|
|
23
|
+
if (response instanceof Response) {
|
|
24
|
+
bytes = await response.arrayBuffer();
|
|
25
|
+
} else {
|
|
26
|
+
bytes = response;
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
const fsp = globalThis.process?.getBuiltinModule?.("fs/promises")
|
|
30
|
+
if (fsp) {
|
|
31
|
+
const wasmPath = new URL("../build/md4x.wasm", import.meta.url);
|
|
32
|
+
bytes = await fsp.readFile(wasmPath);
|
|
33
|
+
} else {
|
|
34
|
+
bytes = await fetch(await import("../build/md4x.wasm?url").then(m => m.default)).then(r => r.arrayBuffer())
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const { instance } = await WebAssembly.instantiate(bytes, {
|
|
38
|
+
wasi_snapshot_preview1: wasiStub,
|
|
39
|
+
});
|
|
40
|
+
_instance = instance;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function renderToHtml(input) {
|
|
44
|
+
return render(getExports(), getExports().md4x_to_html, input);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function renderToJson(input) {
|
|
48
|
+
return JSON.parse(render(getExports(), getExports().md4x_to_json, input));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function renderToAnsi(input) {
|
|
52
|
+
return render(getExports(), getExports().md4x_to_ansi, input);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --- internals ---
|
|
56
|
+
|
|
57
|
+
const wasiStub = {
|
|
58
|
+
fd_close: () => 0,
|
|
59
|
+
fd_seek: () => 0,
|
|
60
|
+
fd_write: () => 0,
|
|
61
|
+
proc_exit: () => {},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function render(exports, fn, input) {
|
|
65
|
+
const { memory, md4x_alloc, md4x_free, md4x_result_ptr, md4x_result_size } =
|
|
66
|
+
exports;
|
|
67
|
+
const encoded = new TextEncoder().encode(input);
|
|
68
|
+
const ptr = md4x_alloc(encoded.length);
|
|
69
|
+
new Uint8Array(memory.buffer).set(encoded, ptr);
|
|
70
|
+
const ret = fn(ptr, encoded.length);
|
|
71
|
+
md4x_free(ptr);
|
|
72
|
+
if (ret !== 0) {
|
|
73
|
+
throw new Error("md4x: render failed");
|
|
74
|
+
}
|
|
75
|
+
const outPtr = md4x_result_ptr();
|
|
76
|
+
const outSize = md4x_result_size();
|
|
77
|
+
const result = new TextDecoder().decode(
|
|
78
|
+
new Uint8Array(memory.buffer, outPtr, outSize),
|
|
79
|
+
);
|
|
80
|
+
md4x_free(outPtr);
|
|
81
|
+
return result;
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "md4x",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"md4x": "./lib/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/napi.d.mts",
|
|
12
|
+
"node": "./lib/napi.mjs",
|
|
13
|
+
"default": "./lib/wasm.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./wasm": {
|
|
16
|
+
"types": "./lib/wasm.d.mts",
|
|
17
|
+
"default": "./lib/wasm.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./napi": {
|
|
20
|
+
"types": "./lib/napi.d.mts",
|
|
21
|
+
"default": "./lib/napi.mjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"build",
|
|
26
|
+
"lib"
|
|
27
|
+
],
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@milkdown/crepe": "^7.18.0",
|
|
30
|
+
"ansi-html": "^0.0.9",
|
|
31
|
+
"markdown-it": "^14.1.1",
|
|
32
|
+
"md4w": "^0.2.7",
|
|
33
|
+
"mitata": "^1.0.34",
|
|
34
|
+
"shiki": "^3.22.0",
|
|
35
|
+
"vite": "^6.3.5",
|
|
36
|
+
"vitest": "^4.0.18"
|
|
37
|
+
}
|
|
38
|
+
}
|