mdrefine 0.1.0-beta.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.
Files changed (59) hide show
  1. package/CONTRIBUTING.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -0
  4. package/SECURITY.md +16 -0
  5. package/dist/cli/main.d.ts +2 -0
  6. package/dist/cli/main.js +158 -0
  7. package/dist/cli/main.js.map +1 -0
  8. package/dist/config/load.d.ts +9 -0
  9. package/dist/config/load.js +75 -0
  10. package/dist/config/load.js.map +1 -0
  11. package/dist/config/resolve.d.ts +93 -0
  12. package/dist/config/resolve.js +110 -0
  13. package/dist/config/resolve.js.map +1 -0
  14. package/dist/core/directives.d.ts +3 -0
  15. package/dist/core/directives.js +48 -0
  16. package/dist/core/directives.js.map +1 -0
  17. package/dist/core/engine.d.ts +8 -0
  18. package/dist/core/engine.js +192 -0
  19. package/dist/core/engine.js.map +1 -0
  20. package/dist/core/types.d.ts +92 -0
  21. package/dist/core/types.js +2 -0
  22. package/dist/core/types.js.map +1 -0
  23. package/dist/index.d.ts +7 -0
  24. package/dist/index.js +5 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/rules/dialects.d.ts +2 -0
  27. package/dist/rules/dialects.js +105 -0
  28. package/dist/rules/dialects.js.map +1 -0
  29. package/dist/rules/links.d.ts +2 -0
  30. package/dist/rules/links.js +238 -0
  31. package/dist/rules/links.js.map +1 -0
  32. package/dist/rules/style.d.ts +12 -0
  33. package/dist/rules/style.js +244 -0
  34. package/dist/rules/style.js.map +1 -0
  35. package/dist/syntax/obsidian.d.ts +33 -0
  36. package/dist/syntax/obsidian.js +126 -0
  37. package/dist/syntax/obsidian.js.map +1 -0
  38. package/dist/syntax/parse.d.ts +5 -0
  39. package/dist/syntax/parse.js +45 -0
  40. package/dist/syntax/parse.js.map +1 -0
  41. package/dist/workspace/files.d.ts +8 -0
  42. package/dist/workspace/files.js +98 -0
  43. package/dist/workspace/files.js.map +1 -0
  44. package/dist/workspace/index.d.ts +10 -0
  45. package/dist/workspace/index.js +100 -0
  46. package/dist/workspace/index.js.map +1 -0
  47. package/docs/cli.md +71 -0
  48. package/docs/configuration.md +171 -0
  49. package/docs/contributing.md +103 -0
  50. package/docs/design.md +79 -0
  51. package/docs/index.md +48 -0
  52. package/docs/obsidian.md +69 -0
  53. package/docs/plugins.md +114 -0
  54. package/docs/quick-start.md +101 -0
  55. package/docs/releases/0.1.0-beta.1.md +39 -0
  56. package/docs/releases.md +90 -0
  57. package/examples/github.jsonc +7 -0
  58. package/examples/obsidian.jsonc +8 -0
  59. package/package.json +101 -0
@@ -0,0 +1,244 @@
1
+ import { visit } from "unist-util-visit";
2
+ import stringWidth from "string-width";
3
+ import { range } from "../syntax/parse.js";
4
+ export function optionsSchema(properties) {
5
+ return { type: "object", properties, additionalProperties: false };
6
+ }
7
+ function markerRule(type, fallback) {
8
+ return {
9
+ description: `Choose ${type} delimiters without reprinting their contents.`,
10
+ kind: "style",
11
+ phase: "inline",
12
+ schema: optionsSchema({ marker: { enum: ["*", "_"] } }),
13
+ check({ document, options }) {
14
+ const findings = [];
15
+ visit(document.tree, type, (node) => {
16
+ const [start, end] = range(node);
17
+ const size = type === "strong" ? 2 : 1;
18
+ const marker = String(options.marker ?? fallback).repeat(size);
19
+ const original = document.source.slice(start, end);
20
+ if (original.startsWith(marker))
21
+ return;
22
+ // Underscores cannot delimit intraword emphasis. Preserve these valid forms.
23
+ if (marker.startsWith("_") &&
24
+ (/\p{L}|\p{N}/u.test(document.source[start - 1] ?? "") ||
25
+ /\p{L}|\p{N}/u.test(document.source[end] ?? "")))
26
+ return;
27
+ for (const [a, b] of [
28
+ [start, start + size],
29
+ [end - size, end],
30
+ ]) {
31
+ findings.push({
32
+ start: a,
33
+ end: b,
34
+ message: `Use ${JSON.stringify(marker)} for ${type}.`,
35
+ edit: { start: a, end: b, text: marker },
36
+ });
37
+ }
38
+ });
39
+ return findings;
40
+ },
41
+ };
42
+ }
43
+ const wrap = {
44
+ description: "Reflow paragraphs using display-column width and protected inline atoms.",
45
+ kind: "style",
46
+ phase: "block",
47
+ schema: optionsSchema({ width: { type: "integer", minimum: 20, maximum: 500 } }),
48
+ check({ document, options, workspace }) {
49
+ if (document.dialect === "obsidian" && workspace?.strictLineBreaks !== true)
50
+ return [];
51
+ const findings = [];
52
+ const width = Number(options.width ?? 80);
53
+ visit(document.tree, "paragraph", (node) => {
54
+ const [start, end] = range(node);
55
+ const original = document.source.slice(start, end);
56
+ // Explicit breaks, callout headers, block IDs, multiline opaque syntax, and HTML are protected.
57
+ if (node.children.some((child) => child.type === "break" || child.type === "html") ||
58
+ /^\[![^\]]+\]/.test(original) ||
59
+ /(?:^|\s)\^[\w-]+\s*$/.test(original))
60
+ return;
61
+ const lineStart = document.source.lastIndexOf("\n", start - 1) + 1;
62
+ const prefix = document.source.slice(lineStart, start);
63
+ if (!/^[\s>\-*+\d.[\]xX)]*$/.test(prefix))
64
+ return;
65
+ const continuation = prefix.replace(/(?:[-+*]|\d+[.)]|\[[xX ]\])(?=\s)/g, (value) => " ".repeat(value.length));
66
+ const words = [];
67
+ let unsupported = false;
68
+ for (const child of node.children) {
69
+ const [a, b] = range(child);
70
+ const raw = document.source.slice(a, b);
71
+ const lines = raw.split(/\r?\n/);
72
+ for (let i = 1; i < lines.length; i++) {
73
+ const line = lines[i];
74
+ if (continuation && line.startsWith(continuation))
75
+ lines[i] = line.slice(continuation.length);
76
+ else if (continuation && /^[\s>]/.test(line))
77
+ unsupported = true;
78
+ }
79
+ const value = lines.join(" ");
80
+ if (child.type === "text") {
81
+ // Preserve escaping and entities. Split only ordinary source whitespace.
82
+ words.push(...value.split(/([ \t]+)/).filter(Boolean));
83
+ }
84
+ else {
85
+ if (raw.includes("\n"))
86
+ unsupported = true;
87
+ words.push(value);
88
+ }
89
+ }
90
+ if (unsupported)
91
+ return;
92
+ // Adjacent inline nodes without source whitespace form a single wrapping atom.
93
+ const atoms = [];
94
+ let current = "";
95
+ for (const word of words) {
96
+ if (/^[ \t]+$/.test(word)) {
97
+ if (current)
98
+ atoms.push(current);
99
+ current = "";
100
+ }
101
+ else
102
+ current += word;
103
+ }
104
+ if (current)
105
+ atoms.push(current);
106
+ const output = [];
107
+ let line = "";
108
+ let available = width - stringWidth(prefix);
109
+ for (const atom of atoms) {
110
+ const candidate = line ? `${line} ${atom}` : atom;
111
+ // Never introduce a Markdown block by placing its marker at a physical line start.
112
+ const marker = /^(?:[-+*]|\d+[.)]|#{1,6}|>|[-*_]{3,})$/.test(atom);
113
+ if (line && stringWidth(candidate) > available && !marker) {
114
+ output.push(line);
115
+ line = atom;
116
+ available = width - stringWidth(continuation);
117
+ }
118
+ else
119
+ line = candidate;
120
+ }
121
+ if (line)
122
+ output.push(line);
123
+ const newline = document.source.includes("\r\n") ? "\r\n" : "\n";
124
+ const replacement = output.join(newline + continuation);
125
+ if (replacement !== original)
126
+ findings.push({
127
+ start,
128
+ end,
129
+ message: `Reflow paragraph to ${width} columns.`,
130
+ edit: { start, end, text: replacement },
131
+ });
132
+ });
133
+ return findings;
134
+ },
135
+ };
136
+ const table = {
137
+ description: "Align GFM table cells while preserving their inline source.",
138
+ kind: "style",
139
+ phase: "block",
140
+ schema: optionsSchema({}),
141
+ check({ document }) {
142
+ const findings = [];
143
+ visit(document.tree, "table", (node) => {
144
+ const [start, end] = range(node);
145
+ // Nested tables retain container prefixes until a dedicated container printer handles them.
146
+ if (node.position.start.column !== 1)
147
+ return;
148
+ const rows = node.children.map((row) => row.children.map((cell) => {
149
+ if (!cell.children.length)
150
+ return "";
151
+ const first = range(cell.children[0])[0];
152
+ const last = range(cell.children.at(-1))[1];
153
+ return document.source.slice(first, last);
154
+ }));
155
+ const count = node.align?.length ?? rows[0]?.length ?? 0;
156
+ if (rows.some((row) => row.length > count))
157
+ return;
158
+ const widths = Array.from({ length: count }, (_, i) => Math.max(3, ...rows.map((row) => stringWidth(row[i] ?? ""))));
159
+ const rendered = rows.map((row) => `| ${widths.map((width, i) => (row[i] ?? "") + " ".repeat(width - stringWidth(row[i] ?? ""))).join(" | ")} |`);
160
+ rendered.splice(1, 0, `| ${widths
161
+ .map((width, i) => {
162
+ const align = node.align?.[i];
163
+ return ((align === "left" || align === "center" ? ":" : "-") +
164
+ "-".repeat(width - 2) +
165
+ (align === "right" || align === "center" ? ":" : "-"));
166
+ })
167
+ .join(" | ")} |`);
168
+ const replacement = rendered.join(document.source.includes("\r\n") ? "\r\n" : "\n");
169
+ if (replacement !== document.source.slice(start, end))
170
+ findings.push({
171
+ start,
172
+ end,
173
+ message: "Align table cells.",
174
+ edit: { start, end, text: replacement },
175
+ });
176
+ });
177
+ return findings;
178
+ },
179
+ };
180
+ export const styleRules = {
181
+ "style/wrap": wrap,
182
+ "style/emphasis": markerRule("emphasis", "_"),
183
+ "style/strong": markerRule("strong", "*"),
184
+ "style/table": table,
185
+ "style/final-newline": {
186
+ description: "End nonempty documents with a newline.",
187
+ kind: "style",
188
+ phase: "document",
189
+ schema: optionsSchema({}),
190
+ check({ document: { source } }) {
191
+ if (!source || source.endsWith("\n"))
192
+ return [];
193
+ return [
194
+ {
195
+ start: source.length,
196
+ message: "Add a final newline.",
197
+ edit: {
198
+ start: source.length,
199
+ end: source.length,
200
+ text: source.includes("\r\n") ? "\r\n" : "\n",
201
+ },
202
+ },
203
+ ];
204
+ },
205
+ },
206
+ "style/heading": {
207
+ description: "Use ATX headings.",
208
+ kind: "style",
209
+ phase: "block",
210
+ schema: optionsSchema({}),
211
+ check({ document }) {
212
+ const findings = [];
213
+ visit(document.tree, "heading", (node) => {
214
+ const [start, end] = range(node);
215
+ if (document.source[start] === "#" || node.position.start.column !== 1)
216
+ return;
217
+ const first = node.children[0];
218
+ const last = node.children.at(-1);
219
+ if (!first || !last)
220
+ return;
221
+ const text = document.source.slice(range(first)[0], range(last)[1]);
222
+ if (text.includes("\n"))
223
+ return;
224
+ findings.push({
225
+ start,
226
+ end,
227
+ message: "Use an ATX heading.",
228
+ edit: { start, end, text: `${"#".repeat(node.depth)} ${text}` },
229
+ });
230
+ });
231
+ return findings;
232
+ },
233
+ },
234
+ };
235
+ /** Collect source-positioned nodes for rule/plugin consumers. */
236
+ export function nodesOfType(document, type) {
237
+ const result = [];
238
+ visit(document.tree, (node) => {
239
+ if (node.type === type)
240
+ result.push(node);
241
+ });
242
+ return result;
243
+ }
244
+ //# sourceMappingURL=style.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/rules/style.ts"],"names":[],"mappings":"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","sourcesContent":["import { visit } from \"unist-util-visit\";\nimport stringWidth from \"string-width\";\nimport type { Nodes } from \"mdast\";\nimport type { Finding, Rule } from \"../core/types.js\";\nimport { range } from \"../syntax/parse.js\";\n\nexport function optionsSchema(properties: Record<string, unknown>) {\n return { type: \"object\", properties, additionalProperties: false };\n}\nfunction markerRule(type: \"emphasis\" | \"strong\", fallback: string): Rule {\n return {\n description: `Choose ${type} delimiters without reprinting their contents.`,\n kind: \"style\",\n phase: \"inline\",\n schema: optionsSchema({ marker: { enum: [\"*\", \"_\"] } }),\n check({ document, options }) {\n const findings: Finding[] = [];\n visit(document.tree, type, (node) => {\n const [start, end] = range(node);\n const size = type === \"strong\" ? 2 : 1;\n const marker = String(options.marker ?? fallback).repeat(size);\n const original = document.source.slice(start, end);\n if (original.startsWith(marker)) return;\n // Underscores cannot delimit intraword emphasis. Preserve these valid forms.\n if (\n marker.startsWith(\"_\") &&\n (/\\p{L}|\\p{N}/u.test(document.source[start - 1] ?? \"\") ||\n /\\p{L}|\\p{N}/u.test(document.source[end] ?? \"\"))\n )\n return;\n for (const [a, b] of [\n [start, start + size],\n [end - size, end],\n ] as const) {\n findings.push({\n start: a,\n end: b,\n message: `Use ${JSON.stringify(marker)} for ${type}.`,\n edit: { start: a, end: b, text: marker },\n });\n }\n });\n return findings;\n },\n };\n}\n\nconst wrap: Rule = {\n description: \"Reflow paragraphs using display-column width and protected inline atoms.\",\n kind: \"style\",\n phase: \"block\",\n schema: optionsSchema({ width: { type: \"integer\", minimum: 20, maximum: 500 } }),\n check({ document, options, workspace }) {\n if (document.dialect === \"obsidian\" && workspace?.strictLineBreaks !== true) return [];\n const findings: Finding[] = [];\n const width = Number(options.width ?? 80);\n visit(document.tree, \"paragraph\", (node) => {\n const [start, end] = range(node);\n const original = document.source.slice(start, end);\n // Explicit breaks, callout headers, block IDs, multiline opaque syntax, and HTML are protected.\n if (\n node.children.some((child) => child.type === \"break\" || child.type === \"html\") ||\n /^\\[![^\\]]+\\]/.test(original) ||\n /(?:^|\\s)\\^[\\w-]+\\s*$/.test(original)\n )\n return;\n const lineStart = document.source.lastIndexOf(\"\\n\", start - 1) + 1;\n const prefix = document.source.slice(lineStart, start);\n if (!/^[\\s>\\-*+\\d.[\\]xX)]*$/.test(prefix)) return;\n const continuation = prefix.replace(/(?:[-+*]|\\d+[.)]|\\[[xX ]\\])(?=\\s)/g, (value) =>\n \" \".repeat(value.length),\n );\n const words: string[] = [];\n let unsupported = false;\n for (const child of node.children) {\n const [a, b] = range(child);\n const raw = document.source.slice(a, b);\n const lines = raw.split(/\\r?\\n/);\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i]!;\n if (continuation && line.startsWith(continuation))\n lines[i] = line.slice(continuation.length);\n else if (continuation && /^[\\s>]/.test(line)) unsupported = true;\n }\n const value = lines.join(\" \");\n if (child.type === \"text\") {\n // Preserve escaping and entities. Split only ordinary source whitespace.\n words.push(...value.split(/([ \\t]+)/).filter(Boolean));\n } else {\n if (raw.includes(\"\\n\")) unsupported = true;\n words.push(value);\n }\n }\n if (unsupported) return;\n // Adjacent inline nodes without source whitespace form a single wrapping atom.\n const atoms: string[] = [];\n let current = \"\";\n for (const word of words) {\n if (/^[ \\t]+$/.test(word)) {\n if (current) atoms.push(current);\n current = \"\";\n } else current += word;\n }\n if (current) atoms.push(current);\n const output: string[] = [];\n let line = \"\";\n let available = width - stringWidth(prefix);\n for (const atom of atoms) {\n const candidate = line ? `${line} ${atom}` : atom;\n // Never introduce a Markdown block by placing its marker at a physical line start.\n const marker = /^(?:[-+*]|\\d+[.)]|#{1,6}|>|[-*_]{3,})$/.test(atom);\n if (line && stringWidth(candidate) > available && !marker) {\n output.push(line);\n line = atom;\n available = width - stringWidth(continuation);\n } else line = candidate;\n }\n if (line) output.push(line);\n const newline = document.source.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n const replacement = output.join(newline + continuation);\n if (replacement !== original)\n findings.push({\n start,\n end,\n message: `Reflow paragraph to ${width} columns.`,\n edit: { start, end, text: replacement },\n });\n });\n return findings;\n },\n};\n\nconst table: Rule = {\n description: \"Align GFM table cells while preserving their inline source.\",\n kind: \"style\",\n phase: \"block\",\n schema: optionsSchema({}),\n check({ document }) {\n const findings: Finding[] = [];\n visit(document.tree, \"table\", (node) => {\n const [start, end] = range(node);\n // Nested tables retain container prefixes until a dedicated container printer handles them.\n if (node.position!.start.column !== 1) return;\n const rows = node.children.map((row) =>\n row.children.map((cell) => {\n if (!cell.children.length) return \"\";\n const first = range(cell.children[0]!)[0];\n const last = range(cell.children.at(-1)!)[1];\n return document.source.slice(first, last);\n }),\n );\n const count = node.align?.length ?? rows[0]?.length ?? 0;\n if (rows.some((row) => row.length > count)) return;\n const widths = Array.from({ length: count }, (_, i) =>\n Math.max(3, ...rows.map((row) => stringWidth(row[i] ?? \"\"))),\n );\n const rendered = rows.map(\n (row) =>\n `| ${widths.map((width, i) => (row[i] ?? \"\") + \" \".repeat(width - stringWidth(row[i] ?? \"\"))).join(\" | \")} |`,\n );\n rendered.splice(\n 1,\n 0,\n `| ${widths\n .map((width, i) => {\n const align = node.align?.[i];\n return (\n (align === \"left\" || align === \"center\" ? \":\" : \"-\") +\n \"-\".repeat(width - 2) +\n (align === \"right\" || align === \"center\" ? \":\" : \"-\")\n );\n })\n .join(\" | \")} |`,\n );\n const replacement = rendered.join(document.source.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\");\n if (replacement !== document.source.slice(start, end))\n findings.push({\n start,\n end,\n message: \"Align table cells.\",\n edit: { start, end, text: replacement },\n });\n });\n return findings;\n },\n};\n\nexport const styleRules: Record<string, Rule> = {\n \"style/wrap\": wrap,\n \"style/emphasis\": markerRule(\"emphasis\", \"_\"),\n \"style/strong\": markerRule(\"strong\", \"*\"),\n \"style/table\": table,\n \"style/final-newline\": {\n description: \"End nonempty documents with a newline.\",\n kind: \"style\",\n phase: \"document\",\n schema: optionsSchema({}),\n check({ document: { source } }) {\n if (!source || source.endsWith(\"\\n\")) return [];\n return [\n {\n start: source.length,\n message: \"Add a final newline.\",\n edit: {\n start: source.length,\n end: source.length,\n text: source.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\",\n },\n },\n ];\n },\n },\n \"style/heading\": {\n description: \"Use ATX headings.\",\n kind: \"style\",\n phase: \"block\",\n schema: optionsSchema({}),\n check({ document }) {\n const findings: Finding[] = [];\n visit(document.tree, \"heading\", (node) => {\n const [start, end] = range(node);\n if (document.source[start] === \"#\" || node.position!.start.column !== 1) return;\n const first = node.children[0];\n const last = node.children.at(-1);\n if (!first || !last) return;\n const text = document.source.slice(range(first)[0], range(last)[1]);\n if (text.includes(\"\\n\")) return;\n findings.push({\n start,\n end,\n message: \"Use an ATX heading.\",\n edit: { start, end, text: `${\"#\".repeat(node.depth)} ${text}` },\n });\n });\n return findings;\n },\n },\n};\n\n/** Collect source-positioned nodes for rule/plugin consumers. */\nexport function nodesOfType(\n document: { tree: import(\"mdast\").Root },\n type: Nodes[\"type\"],\n): Nodes[] {\n const result: Nodes[] = [];\n visit(document.tree, (node) => {\n if (node.type === type) result.push(node);\n });\n return result;\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import type { Literal } from "mdast";
2
+ import type { Extension } from "micromark-util-types";
3
+ import type { Extension as TreeExtension } from "mdast-util-from-markdown";
4
+ export interface ObsidianLiteral extends Literal {
5
+ type: "wikiLink" | "obsidianComment" | "obsidianHighlight";
6
+ embed?: boolean;
7
+ target?: string;
8
+ label?: string;
9
+ }
10
+ declare module "mdast" {
11
+ interface RootContentMap {
12
+ wikiLink: ObsidianLiteral;
13
+ obsidianComment: ObsidianLiteral;
14
+ obsidianHighlight: ObsidianLiteral;
15
+ }
16
+ interface PhrasingContentMap {
17
+ wikiLink: ObsidianLiteral;
18
+ obsidianComment: ObsidianLiteral;
19
+ obsidianHighlight: ObsidianLiteral;
20
+ }
21
+ interface BlockContentMap {
22
+ obsidianComment: ObsidianLiteral;
23
+ }
24
+ }
25
+ declare module "micromark-util-types" {
26
+ interface TokenTypeMap {
27
+ obsidianLiteral: "obsidianLiteral";
28
+ obsidianBlockComment: "obsidianBlockComment";
29
+ obsidianCommentData: "obsidianCommentData";
30
+ }
31
+ }
32
+ export declare const obsidianSyntax: Extension;
33
+ export declare const obsidianTree: TreeExtension;
@@ -0,0 +1,126 @@
1
+ /** A tokenizer, not a pre-parse replacement: code, escapes, and fenced blocks stay opaque. */
2
+ const tokenize = function (effects, ok, nok) {
3
+ let opening = "";
4
+ let closing = "";
5
+ let index = 0;
6
+ let previous = null;
7
+ let count = 0;
8
+ const start = (code) => {
9
+ opening = code === 33 ? "![[" : code === 91 ? "[[" : code === 37 ? "%%" : "==";
10
+ closing = opening.endsWith("[[") ? "]]" : opening;
11
+ effects.enter("obsidianLiteral");
12
+ return open(code);
13
+ };
14
+ const open = (code) => {
15
+ if (code !== opening.charCodeAt(index))
16
+ return nok(code);
17
+ effects.consume(code);
18
+ index++;
19
+ return index === opening.length ? body : open;
20
+ };
21
+ const body = (code) => {
22
+ if (code === null || (opening !== "%%" && code < 0 && code !== -1 && code !== -2))
23
+ return nok(code);
24
+ effects.consume(code);
25
+ count++;
26
+ if (code === closing.charCodeAt(1) && previous === closing.charCodeAt(0)) {
27
+ if (count <= 2)
28
+ return nok(code);
29
+ effects.exit("obsidianLiteral");
30
+ return ok;
31
+ }
32
+ previous = code;
33
+ return body;
34
+ };
35
+ return start;
36
+ };
37
+ // Standalone comment blocks may cross blank lines. Keep their complete source opaque.
38
+ const tokenizeBlockComment = function (effects, ok, nok) {
39
+ let opened = 0;
40
+ let previous = null;
41
+ let closed = false;
42
+ const start = (code) => {
43
+ if (code !== 37)
44
+ return nok(code);
45
+ if (opened === 0) {
46
+ effects.enter("obsidianBlockComment");
47
+ effects.enter("obsidianCommentData");
48
+ }
49
+ effects.consume(code);
50
+ opened++;
51
+ return opened === 2 ? body : start;
52
+ };
53
+ const body = (code) => {
54
+ if (code === null || (closed && code < -2)) {
55
+ effects.exit("obsidianCommentData");
56
+ effects.exit("obsidianBlockComment");
57
+ return ok(code);
58
+ }
59
+ if (code < -2) {
60
+ effects.exit("obsidianCommentData");
61
+ effects.enter("lineEnding");
62
+ effects.consume(code);
63
+ effects.exit("lineEnding");
64
+ previous = null;
65
+ return before;
66
+ }
67
+ effects.consume(code);
68
+ if (code === 37 && previous === 37)
69
+ closed = true;
70
+ previous = code;
71
+ return body;
72
+ };
73
+ const before = (code) => {
74
+ if (code === null) {
75
+ effects.exit("obsidianBlockComment");
76
+ return ok(code);
77
+ }
78
+ if (code < -2) {
79
+ effects.enter("lineEnding");
80
+ effects.consume(code);
81
+ effects.exit("lineEnding");
82
+ return before;
83
+ }
84
+ effects.enter("obsidianCommentData");
85
+ return body(code);
86
+ };
87
+ return start;
88
+ };
89
+ export const obsidianSyntax = {
90
+ flow: { 37: { name: "obsidianBlockComment", tokenize: tokenizeBlockComment, concrete: true } },
91
+ text: Object.fromEntries([33, 91, 37, 61].map((code) => [code, { name: "obsidianLiteral", tokenize }])),
92
+ };
93
+ export const obsidianTree = {
94
+ enter: {
95
+ obsidianBlockComment(token) {
96
+ this.enter({ type: "obsidianComment", value: this.sliceSerialize(token) }, token);
97
+ },
98
+ obsidianLiteral(token) {
99
+ const raw = this.sliceSerialize(token);
100
+ const wiki = raw.startsWith("[[") || raw.startsWith("![[");
101
+ const embed = raw.startsWith("!");
102
+ const value = raw.slice(wiki && embed ? 3 : 2, -2).replaceAll("\\|", "|");
103
+ const divider = value.indexOf("|");
104
+ const node = {
105
+ type: wiki ? "wikiLink" : raw.startsWith("%%") ? "obsidianComment" : "obsidianHighlight",
106
+ value,
107
+ };
108
+ if (wiki) {
109
+ node.embed = embed;
110
+ node.target = divider < 0 ? value : value.slice(0, divider);
111
+ if (divider >= 0)
112
+ node.label = value.slice(divider + 1);
113
+ }
114
+ this.enter(node, token);
115
+ },
116
+ },
117
+ exit: {
118
+ obsidianBlockComment(token) {
119
+ this.exit(token);
120
+ },
121
+ obsidianLiteral(token) {
122
+ this.exit(token);
123
+ },
124
+ },
125
+ };
126
+ //# sourceMappingURL=obsidian.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"obsidian.js","sourceRoot":"","sources":["../../src/syntax/obsidian.ts"],"names":[],"mappings":"AAiCA,8FAA8F;AAC9F,MAAM,QAAQ,GAAc,UAAU,OAAO,EAAE,EAAE,EAAE,GAAG;IACpD,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAkB,IAAI,CAAC;IACnC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,KAAK,GAAU,CAAC,IAAI,EAAE,EAAE;QAC5B,OAAO,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/E,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC;IACF,MAAM,IAAI,GAAU,CAAC,IAAI,EAAE,EAAE;QAC3B,IAAI,IAAI,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;QACzD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtB,KAAK,EAAE,CAAC;QACR,OAAO,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,CAAC,CAAC;IACF,MAAM,IAAI,GAAU,CAAC,IAAI,EAAE,EAAE;QAC3B,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;YAC/E,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtB,KAAK,EAAE,CAAC;QACR,IAAI,IAAI,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,QAAQ,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YACzE,IAAI,KAAK,IAAI,CAAC;gBAAE,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAChC,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,sFAAsF;AACtF,MAAM,oBAAoB,GAAc,UAAU,OAAO,EAAE,EAAE,EAAE,GAAG;IAChE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,QAAQ,GAAkB,IAAI,CAAC;IACnC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,KAAK,GAAU,CAAC,IAAI,EAAE,EAAE;QAC5B,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;YACtC,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,EAAE,CAAC;QACT,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IACrC,CAAC,CAAC;IACF,MAAM,IAAI,GAAU,CAAC,IAAI,EAAE,EAAE;QAC3B,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;YACrC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACpC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5B,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC3B,QAAQ,GAAG,IAAI,CAAC;YAChB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,EAAE,IAAI,QAAQ,KAAK,EAAE;YAAE,MAAM,GAAG,IAAI,CAAC;QAClD,QAAQ,GAAG,IAAI,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,MAAM,MAAM,GAAU,CAAC,IAAI,EAAE,EAAE;QAC7B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;YACrC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC5B,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC3B,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAc;IACvC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAC9F,IAAI,EAAE,MAAM,CAAC,WAAW,CACtB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC,CAAC,CAC9E;CACF,CAAC;AACF,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,KAAK,EAAE;QACL,oBAAoB,CAAC,KAAK;YACxB,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACpF,CAAC;QACD,eAAe,CAAC,KAAK;YACnB,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC3D,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAC1E,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,IAAI,GAAoB;gBAC5B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,mBAAmB;gBACxF,KAAK;aACN,CAAC;YACF,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;gBACnB,IAAI,CAAC,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBAC5D,IAAI,OAAO,IAAI,CAAC;oBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;YAC1D,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;KACF;IACD,IAAI,EAAE;QACJ,oBAAoB,CAAC,KAAK;YACxB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;QACD,eAAe,CAAC,KAAK;YACnB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;KACF;CACF,CAAC","sourcesContent":["import type { Literal } from \"mdast\";\nimport type { Extension, State, Tokenizer } from \"micromark-util-types\";\nimport type { Extension as TreeExtension } from \"mdast-util-from-markdown\";\n\nexport interface ObsidianLiteral extends Literal {\n type: \"wikiLink\" | \"obsidianComment\" | \"obsidianHighlight\";\n embed?: boolean;\n target?: string;\n label?: string;\n}\ndeclare module \"mdast\" {\n interface RootContentMap {\n wikiLink: ObsidianLiteral;\n obsidianComment: ObsidianLiteral;\n obsidianHighlight: ObsidianLiteral;\n }\n interface PhrasingContentMap {\n wikiLink: ObsidianLiteral;\n obsidianComment: ObsidianLiteral;\n obsidianHighlight: ObsidianLiteral;\n }\n interface BlockContentMap {\n obsidianComment: ObsidianLiteral;\n }\n}\ndeclare module \"micromark-util-types\" {\n interface TokenTypeMap {\n obsidianLiteral: \"obsidianLiteral\";\n obsidianBlockComment: \"obsidianBlockComment\";\n obsidianCommentData: \"obsidianCommentData\";\n }\n}\n\n/** A tokenizer, not a pre-parse replacement: code, escapes, and fenced blocks stay opaque. */\nconst tokenize: Tokenizer = function (effects, ok, nok) {\n let opening = \"\";\n let closing = \"\";\n let index = 0;\n let previous: number | null = null;\n let count = 0;\n const start: State = (code) => {\n opening = code === 33 ? \"![[\" : code === 91 ? \"[[\" : code === 37 ? \"%%\" : \"==\";\n closing = opening.endsWith(\"[[\") ? \"]]\" : opening;\n effects.enter(\"obsidianLiteral\");\n return open(code);\n };\n const open: State = (code) => {\n if (code !== opening.charCodeAt(index)) return nok(code);\n effects.consume(code);\n index++;\n return index === opening.length ? body : open;\n };\n const body: State = (code) => {\n if (code === null || (opening !== \"%%\" && code < 0 && code !== -1 && code !== -2))\n return nok(code);\n effects.consume(code);\n count++;\n if (code === closing.charCodeAt(1) && previous === closing.charCodeAt(0)) {\n if (count <= 2) return nok(code);\n effects.exit(\"obsidianLiteral\");\n return ok;\n }\n previous = code;\n return body;\n };\n return start;\n};\n\n// Standalone comment blocks may cross blank lines. Keep their complete source opaque.\nconst tokenizeBlockComment: Tokenizer = function (effects, ok, nok) {\n let opened = 0;\n let previous: number | null = null;\n let closed = false;\n const start: State = (code) => {\n if (code !== 37) return nok(code);\n if (opened === 0) {\n effects.enter(\"obsidianBlockComment\");\n effects.enter(\"obsidianCommentData\");\n }\n effects.consume(code);\n opened++;\n return opened === 2 ? body : start;\n };\n const body: State = (code) => {\n if (code === null || (closed && code < -2)) {\n effects.exit(\"obsidianCommentData\");\n effects.exit(\"obsidianBlockComment\");\n return ok(code);\n }\n if (code < -2) {\n effects.exit(\"obsidianCommentData\");\n effects.enter(\"lineEnding\");\n effects.consume(code);\n effects.exit(\"lineEnding\");\n previous = null;\n return before;\n }\n effects.consume(code);\n if (code === 37 && previous === 37) closed = true;\n previous = code;\n return body;\n };\n const before: State = (code) => {\n if (code === null) {\n effects.exit(\"obsidianBlockComment\");\n return ok(code);\n }\n if (code < -2) {\n effects.enter(\"lineEnding\");\n effects.consume(code);\n effects.exit(\"lineEnding\");\n return before;\n }\n effects.enter(\"obsidianCommentData\");\n return body(code);\n };\n return start;\n};\n\nexport const obsidianSyntax: Extension = {\n flow: { 37: { name: \"obsidianBlockComment\", tokenize: tokenizeBlockComment, concrete: true } },\n text: Object.fromEntries(\n [33, 91, 37, 61].map((code) => [code, { name: \"obsidianLiteral\", tokenize }]),\n ),\n};\nexport const obsidianTree: TreeExtension = {\n enter: {\n obsidianBlockComment(token) {\n this.enter({ type: \"obsidianComment\", value: this.sliceSerialize(token) }, token);\n },\n obsidianLiteral(token) {\n const raw = this.sliceSerialize(token);\n const wiki = raw.startsWith(\"[[\") || raw.startsWith(\"![[\");\n const embed = raw.startsWith(\"!\");\n const value = raw.slice(wiki && embed ? 3 : 2, -2).replaceAll(\"\\\\|\", \"|\");\n const divider = value.indexOf(\"|\");\n const node: ObsidianLiteral = {\n type: wiki ? \"wikiLink\" : raw.startsWith(\"%%\") ? \"obsidianComment\" : \"obsidianHighlight\",\n value,\n };\n if (wiki) {\n node.embed = embed;\n node.target = divider < 0 ? value : value.slice(0, divider);\n if (divider >= 0) node.label = value.slice(divider + 1);\n }\n this.enter(node, token);\n },\n },\n exit: {\n obsidianBlockComment(token) {\n this.exit(token);\n },\n obsidianLiteral(token) {\n this.exit(token);\n },\n },\n};\n"]}
@@ -0,0 +1,5 @@
1
+ import type { Nodes } from "mdast";
2
+ import type { Dialect, Document, Plugin } from "../core/types.js";
3
+ export declare function parse(source: string, dialect: Dialect, path?: string, plugins?: Plugin[]): Document;
4
+ export declare function range(node: Nodes): [number, number];
5
+ export declare function textContent(node: Nodes): string;
@@ -0,0 +1,45 @@
1
+ import { fromMarkdown } from "mdast-util-from-markdown";
2
+ import { gfm } from "micromark-extension-gfm";
3
+ import { gfmFromMarkdown } from "mdast-util-gfm";
4
+ import { frontmatter } from "micromark-extension-frontmatter";
5
+ import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
6
+ import { math } from "micromark-extension-math";
7
+ import { mathFromMarkdown } from "mdast-util-math";
8
+ import { obsidianSyntax, obsidianTree } from "./obsidian.js";
9
+ export function parse(source, dialect, path = "document.md", plugins = []) {
10
+ const extensions = [frontmatter(["yaml", "toml"])];
11
+ const mdastExtensions = [frontmatterFromMarkdown(["yaml", "toml"])];
12
+ if (dialect !== "commonmark") {
13
+ extensions.push(gfm(), math());
14
+ mdastExtensions.push(...gfmFromMarkdown(), mathFromMarkdown());
15
+ }
16
+ if (dialect === "obsidian") {
17
+ extensions.push(obsidianSyntax);
18
+ mdastExtensions.push(obsidianTree);
19
+ }
20
+ for (const plugin of plugins) {
21
+ if (plugin.syntax) {
22
+ extensions.push(plugin.syntax.micromark);
23
+ mdastExtensions.push(plugin.syntax.mdast);
24
+ }
25
+ }
26
+ return { source, dialect, path, tree: fromMarkdown(source, { extensions, mdastExtensions }) };
27
+ }
28
+ export function range(node) {
29
+ if (node.position?.start.offset === undefined || node.position.end.offset === undefined) {
30
+ throw new Error(`Missing source position for ${node.type}`);
31
+ }
32
+ return [node.position.start.offset, node.position.end.offset];
33
+ }
34
+ export function textContent(node) {
35
+ if (node.type === "wikiLink")
36
+ return node.label ?? node.target ?? node.value;
37
+ if (node.type === "obsidianComment")
38
+ return "";
39
+ if ("value" in node)
40
+ return node.value;
41
+ if ("children" in node)
42
+ return node.children.map(textContent).join("");
43
+ return "";
44
+ }
45
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/syntax/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7D,MAAM,UAAU,KAAK,CACnB,MAAc,EACd,OAAgB,EAChB,IAAI,GAAG,aAAa,EACpB,UAAoB,EAAE;IAEtB,MAAM,UAAU,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACnD,MAAM,eAAe,GAAG,CAAC,uBAAuB,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACpE,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,eAAe,CAAC,IAAI,CAAC,GAAG,eAAe,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAChC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACrC,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACzC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;AAChG,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,IAAW;IAC/B,IAAI,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACxF,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAChE,CAAC;AACD,MAAM,UAAU,WAAW,CAAC,IAAW;IACrC,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC;IAC7E,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB;QAAE,OAAO,EAAE,CAAC;IAC/C,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC;IACvC,IAAI,UAAU,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvE,OAAO,EAAE,CAAC;AACZ,CAAC","sourcesContent":["import { fromMarkdown } from \"mdast-util-from-markdown\";\nimport { gfm } from \"micromark-extension-gfm\";\nimport { gfmFromMarkdown } from \"mdast-util-gfm\";\nimport { frontmatter } from \"micromark-extension-frontmatter\";\nimport { frontmatterFromMarkdown } from \"mdast-util-frontmatter\";\nimport { math } from \"micromark-extension-math\";\nimport { mathFromMarkdown } from \"mdast-util-math\";\nimport type { Nodes } from \"mdast\";\nimport type { Dialect, Document, Plugin } from \"../core/types.js\";\nimport { obsidianSyntax, obsidianTree } from \"./obsidian.js\";\n\nexport function parse(\n source: string,\n dialect: Dialect,\n path = \"document.md\",\n plugins: Plugin[] = [],\n): Document {\n const extensions = [frontmatter([\"yaml\", \"toml\"])];\n const mdastExtensions = [frontmatterFromMarkdown([\"yaml\", \"toml\"])];\n if (dialect !== \"commonmark\") {\n extensions.push(gfm(), math());\n mdastExtensions.push(...gfmFromMarkdown(), mathFromMarkdown());\n }\n if (dialect === \"obsidian\") {\n extensions.push(obsidianSyntax);\n mdastExtensions.push(obsidianTree);\n }\n for (const plugin of plugins) {\n if (plugin.syntax) {\n extensions.push(plugin.syntax.micromark);\n mdastExtensions.push(plugin.syntax.mdast);\n }\n }\n return { source, dialect, path, tree: fromMarkdown(source, { extensions, mdastExtensions }) };\n}\n\nexport function range(node: Nodes): [number, number] {\n if (node.position?.start.offset === undefined || node.position.end.offset === undefined) {\n throw new Error(`Missing source position for ${node.type}`);\n }\n return [node.position.start.offset, node.position.end.offset];\n}\nexport function textContent(node: Nodes): string {\n if (node.type === \"wikiLink\") return node.label ?? node.target ?? node.value;\n if (node.type === \"obsidianComment\") return \"\";\n if (\"value\" in node) return node.value;\n if (\"children\" in node) return node.children.map(textContent).join(\"\");\n return \"\";\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export interface FileSet {
2
+ root: string;
3
+ files: Record<string, string | null>;
4
+ selected: string[];
5
+ strictLineBreaks?: boolean;
6
+ }
7
+ export declare function discover(rootPath: string, supplied: string[], excluded: string[]): Promise<FileSet>;
8
+ export declare function writeAtomic(file: string, before: string, after: string): Promise<void>;
@@ -0,0 +1,98 @@
1
+ import { lstat, readdir, readFile, realpath, open, rename, unlink } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import ignore from "ignore";
5
+ import { minimatch } from "minimatch";
6
+ import { exists } from "../config/load.js";
7
+ export async function discover(rootPath, supplied, excluded) {
8
+ const root = await realpath(rootPath);
9
+ const files = {};
10
+ const selected = [];
11
+ const requests = [];
12
+ for (const input of supplied.length ? supplied : [root]) {
13
+ const absolute = path.resolve(input);
14
+ if ((await lstat(absolute)).isSymbolicLink())
15
+ throw new Error(`Refusing symbolic-link input: ${input}`);
16
+ const resolved = await realpath(absolute);
17
+ const relative = path.relative(root, resolved).split(path.sep).join("/");
18
+ if (relative.startsWith("../") || relative === ".." || path.isAbsolute(relative))
19
+ throw new Error(`Input is outside the workspace: ${input}`);
20
+ requests.push(relative);
21
+ }
22
+ async function walk(relative, inherited) {
23
+ const directory = path.join(root, relative);
24
+ const layers = [...inherited];
25
+ if (await exists(path.join(directory, ".gitignore")))
26
+ layers.push({
27
+ base: relative,
28
+ matcher: ignore().add(await readFile(path.join(directory, ".gitignore"), "utf8")),
29
+ });
30
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
31
+ if ([".git", ".obsidian", "node_modules", ".npm-cache", "dist", "coverage"].includes(entry.name) ||
32
+ entry.isSymbolicLink())
33
+ continue;
34
+ const name = relative ? `${relative}/${entry.name}` : entry.name;
35
+ if (layers.some((layer) => layer.matcher.ignores(name.slice(layer.base ? layer.base.length + 1 : 0) + (entry.isDirectory() ? "/" : ""))))
36
+ continue;
37
+ const full = path.join(root, name);
38
+ if (entry.isDirectory()) {
39
+ if (!(await exists(path.join(full, ".git"))))
40
+ await walk(name, layers);
41
+ }
42
+ else if (entry.isFile()) {
43
+ const markdown = /\.md$/i.test(name);
44
+ files[name] = markdown ? await readFile(full, "utf8") : null;
45
+ if (markdown &&
46
+ requests.some((request) => !request || name === request || name.startsWith(`${request}/`)) &&
47
+ !excluded.some((pattern) => minimatch(name, pattern, { dot: true })))
48
+ selected.push(name);
49
+ }
50
+ }
51
+ }
52
+ await walk("", []);
53
+ let strictLineBreaks;
54
+ const app = path.join(root, ".obsidian", "app.json");
55
+ if (await exists(app)) {
56
+ const settings = JSON.parse(await readFile(app, "utf8"));
57
+ strictLineBreaks =
58
+ typeof settings === "object" &&
59
+ settings !== null &&
60
+ "strictLineBreaks" in settings &&
61
+ settings.strictLineBreaks === true;
62
+ }
63
+ return {
64
+ root,
65
+ files,
66
+ selected: selected.sort(),
67
+ ...(strictLineBreaks !== undefined ? { strictLineBreaks } : {}),
68
+ };
69
+ }
70
+ export async function writeAtomic(file, before, after) {
71
+ const info = await lstat(file);
72
+ if (!info.isFile() || info.isSymbolicLink())
73
+ throw new Error(`Refusing non-regular file: ${file}`);
74
+ if ((await readFile(file, "utf8")) !== before)
75
+ throw new Error(`File changed during formatting: ${file}`);
76
+ const temporary = path.join(path.dirname(file), `.mdtools-${randomUUID()}.tmp`);
77
+ const handle = await open(temporary, "wx", info.mode);
78
+ try {
79
+ await handle.writeFile(after, "utf8");
80
+ await handle.chmod(info.mode);
81
+ await handle.sync();
82
+ await handle.close();
83
+ const current = await lstat(file);
84
+ if (current.isSymbolicLink() ||
85
+ current.ino !== info.ino ||
86
+ (await readFile(file, "utf8")) !== before)
87
+ throw new Error(`File changed before replacement: ${file}`);
88
+ await rename(temporary, file);
89
+ }
90
+ finally {
91
+ await handle.close();
92
+ await unlink(temporary).catch((error) => {
93
+ if (error.code !== "ENOENT")
94
+ throw error;
95
+ });
96
+ }
97
+ }
98
+ //# sourceMappingURL=files.js.map