@p-buddy/parkdown 0.0.1 → 0.0.3

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/cli.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { Command as t } from "@commander-js/extra-typings";
3
+ import { populateMarkdownInclusions as r, depopulateMarkdownInclusions as l } from "@p-buddy/parkdown";
4
+ const p = "0.0.3", f = new t().version(p).option("--nw, --no-write", "Do NOT write result to file (defaults to false)", !1).option("--ni, --no-inclusions", "Do NOT process file inclusions (defaults to false)", !1).option("-d, --depopulate", "Remove populated inclusions from the file", !1).option("-f, --file <flag>", "The file(s) to process", (e, o) => (o.push(e), o), new Array()).option("-r, --remap-imports", "Remap import specifiers in code blocks from one destination to another").parse(), { inclusions: a, depopulate: c, file: n, write: i } = f.opts();
5
+ n.length === 0 && n.push("README.md");
6
+ const u = [
7
+ [r, !a],
8
+ [l, c]
9
+ ];
10
+ for (const [e] of u.filter(([o, s]) => s))
11
+ for (const o of n) {
12
+ const s = e(o, !i);
13
+ i && console.log(s);
14
+ }
@@ -0,0 +1,7 @@
1
+ export declare const depopulateMarkdownInclusions: (file: string, writeFile?: boolean) => string;
2
+
3
+ export declare const populateMarkdownInclusions: (file: string, writeFile?: boolean) => string;
4
+
5
+ export declare const remapImportSpecifiers: (file: string, writeFile?: boolean) => void;
6
+
7
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,396 @@
1
+ import { writeFileSync as q, readFileSync as te } from "node:fs";
2
+ import { basename as ne, dirname as _, join as C, resolve as U } from "node:path";
3
+ import { URLSearchParams as oe } from "node:url";
4
+ import { unified as se } from "unified";
5
+ import re from "remark-parse";
6
+ import { visit as ie } from "unist-util-visit";
7
+ import ce from "stable-hash";
8
+ import { dedent as k } from "ts-dedent";
9
+ import le from "extract-comments";
10
+ const g = (e, n) => e.position.start.offset - n.position.start.offset;
11
+ g.reverse = (e, n) => g(n, e);
12
+ const T = (e) => e.position !== void 0 && e.position.start.offset !== void 0 && e.position.end.offset !== void 0, ae = se().use(re), I = {
13
+ md: (e) => ae.parse(e)
14
+ }, y = (e, n) => {
15
+ const t = [];
16
+ return ie(e, (s, o, r) => {
17
+ if (s.type !== "root") {
18
+ if (n && s.type !== n) return;
19
+ if (T(s)) {
20
+ const i = ce(r), c = ((r == null ? void 0 : r.children.length) ?? 0) - 1;
21
+ t.push({ ...s, parentID: i, siblingIndex: o, siblingCount: c });
22
+ }
23
+ }
24
+ }), t;
25
+ }, pe = (e) => e.children.length === 0, G = (e, n, ...t) => {
26
+ if (t.length === 0) throw new Error("No nodes to replace content from");
27
+ t.sort(g);
28
+ const s = t.at(0), o = t.at(-1);
29
+ return e.slice(0, s.position.start.offset) + n + e.slice(o.position.end.offset);
30
+ }, me = (e, n, t) => {
31
+ const s = Math.min(n.position.end.offset, t.position.end.offset), o = Math.max(n.position.start.offset, t.position.start.offset);
32
+ return e.slice(s, o);
33
+ }, L = (...e) => e.join(" "), ue = (...e) => e.join(`
34
+ `), he = (e) => {
35
+ const n = e.split("?");
36
+ return n.length > 1 ? [n.slice(0, -1).join("?"), n.at(-1)] : [e, ""];
37
+ }, ge = (e) => he(e)[0];
38
+ class d {
39
+ constructor() {
40
+ this.intervals = [];
41
+ }
42
+ push(n, t) {
43
+ this.intervals.push([Math.min(n, t), Math.max(n, t)]);
44
+ }
45
+ combine(n) {
46
+ this.intervals.push(...n.intervals);
47
+ }
48
+ collapse() {
49
+ const { intervals: n } = this;
50
+ if (!n.length) return this.intervals = [];
51
+ n.sort((r, i) => r[0] - i[0]);
52
+ const t = [];
53
+ let [s, o] = n[0];
54
+ for (let r = 1; r < n.length; r++) {
55
+ const [i, c] = n[r];
56
+ i <= o ? o = Math.max(o, c) : (t.push([s, o]), s = i, o = c);
57
+ }
58
+ return t.push([s, o]), this.intervals = t;
59
+ }
60
+ subtract(n) {
61
+ const { intervals: t } = this, { intervals: s } = n;
62
+ if (!t.length || !s.length) return t;
63
+ let o = [...t];
64
+ for (const [r, i] of s) {
65
+ const c = [];
66
+ for (const [l, a] of o) {
67
+ if (i <= l || r >= a) {
68
+ c.push([l, a]);
69
+ continue;
70
+ }
71
+ r > l && c.push([l, r]), i < a && c.push([i, a]);
72
+ }
73
+ o = c;
74
+ }
75
+ return this.intervals = o;
76
+ }
77
+ }
78
+ const P = /,\s*(?![^()]*\))/, fe = [
79
+ [/'''/g, '"'],
80
+ [/''/g, "'"],
81
+ [/parkdown:\s+/g, ""],
82
+ [/p▼:\s+/g, ""]
83
+ ], de = (e, n = "-") => {
84
+ const t = fe.reduce((s, [o, r]) => s.replaceAll(o, r), e);
85
+ return n ? t.replaceAll(n, " ") : t;
86
+ }, we = ["string", "number", "boolean"], ve = (e) => we.includes(e), $e = /^([a-zA-Z0-9_-]+)(?:\(([^)]*)\))?$/, Ce = (e) => {
87
+ const n = e.match($e);
88
+ if (!n) throw new Error(`Invalid invocation: ${e}`);
89
+ const [, t, s = ""] = n;
90
+ if (!s.trim()) return { name: t, parameters: [] };
91
+ const o = Ee(s);
92
+ return { name: t, parameters: o };
93
+ }, ye = (e, n) => {
94
+ for (let t = n + 1; t < e.length; t++)
95
+ if (e[t] !== void 0) return !1;
96
+ return !0;
97
+ }, Ee = (e) => {
98
+ const n = [];
99
+ let t = "", s = !1;
100
+ for (let o = 0; o < e.length; o++) {
101
+ const r = e[o], i = r === "'";
102
+ if (i && e.at(o + 1) === "'") {
103
+ const c = e.at(o + 2) === "'";
104
+ s = !s, t += c ? "'''" : "''", o += c ? 2 : 1;
105
+ } else i ? (s = !s, t += r) : r === "," && !s ? (n.push(t.trim()), t = "") : t += r;
106
+ }
107
+ return n.push(t.trim()), n.map((o) => o === "" ? void 0 : o ? Se(o) : void 0).filter((o, r, i) => o !== void 0 || !ye(i, r));
108
+ }, Se = (e) => {
109
+ const n = e.length >= 2 && e[0] === "'" && e.at(-1) === "'", t = n && e.length >= 4 && e[1] === "'" && e.at(-2) === "'";
110
+ return !n || t ? e : e.slice(1, -1);
111
+ }, Ne = (e) => {
112
+ const n = /^(\w+)(?:\(([^)]*)\))?/, t = e.match(n);
113
+ if (!t) return { name: e };
114
+ const [, s, o] = t;
115
+ if (!o) return { name: s };
116
+ const r = /(\w+)(\?)?:\s*([^,]+)/g, i = [];
117
+ let c;
118
+ for (; (c = r.exec(o)) !== null; ) {
119
+ const [, l, a, p] = c, m = p.trim();
120
+ if (!ve(m)) throw new Error(`Unsupported type: ${m}`);
121
+ i.push({ name: l, optional: a === "?", type: m });
122
+ }
123
+ return { name: s, parameters: i };
124
+ }, H = (e) => {
125
+ const n = e.map(Ne).reduce(
126
+ (t, { name: s, parameters: o }) => t.set(s, o),
127
+ /* @__PURE__ */ new Map()
128
+ );
129
+ return (t) => {
130
+ const { name: s, parameters: o } = Ce(t);
131
+ if (!n.has(s))
132
+ throw new Error(`Unknown method: ${s}`);
133
+ const r = n.get(s);
134
+ if (r === void 0)
135
+ return { name: s };
136
+ if (o.length > r.length) {
137
+ const i = r.filter(({ optional: l }) => !l).length, c = i === r.length ? i.toString() : `${i} - ${r.length}`;
138
+ throw new Error(`Too many parameters: ${o.length} for method '${s}' (expected: ${c})`);
139
+ }
140
+ return r.reduce((i, { name: c, optional: l, type: a }, p) => {
141
+ const m = o[p];
142
+ if (m === void 0) {
143
+ if (l) return i;
144
+ throw new Error(`Missing required parameter: ${c} for method '${s}'`);
145
+ }
146
+ switch (a) {
147
+ case "string":
148
+ i[c] = m;
149
+ break;
150
+ case "number":
151
+ case "boolean":
152
+ i[c] = JSON.parse(m);
153
+ break;
154
+ }
155
+ if (typeof i[c] !== a)
156
+ throw new Error(`Invalid type: ${c} must be ${a}, got ${typeof i[c]} for method '${s}'`);
157
+ return i;
158
+ }, { name: s });
159
+ };
160
+ }, R = (e) => Object.entries(e).filter(([n]) => n !== "name" && !isNaN(Number(n))).map(([n, t]) => t), xe = [
161
+ /**
162
+ * Wraps the content in a markdown-formatted code block.
163
+ * @param lang The language of the code block (defaults to the file extension).
164
+ * @param meta Additional metadata to include in the top line of the code block (i.e. to the right of the `lang`).
165
+ * @example [](<url>?wrap=code)
166
+ * @example [](<url>?wrap=code())
167
+ * @example [](<url>?wrap=code(ts))
168
+ * @example [](<url>?wrap=code(,some-meta))
169
+ */
170
+ "code(lang?: string, meta?: string)",
171
+ /**
172
+ * Wraps the content in a markdown-formatted blockquote
173
+ * (using the `>` character if the content is a single line,
174
+ * or the `<blockquote>` tag if the content is a multi-line block).
175
+ * @example [](<url>?wrap=quote)
176
+ * @example [](<url>?wrap=quote())
177
+ * @example [](<url>?wrap=quote(,))
178
+ */
179
+ "quote()",
180
+ /**
181
+ * Wraps the content in a markdown-formatted dropdown (using the `<details>` and `<summary>` tags).
182
+ * @param summary The summary text of the dropdown.
183
+ * @param open Whether the dropdown should be open by default.
184
+ * @param space The space character to use between words in the summary (defaults to `-`).
185
+ * @example [](<url>?wrap=dropdown(hello-world))
186
+ * @example [](<url>?wrap=dropdown('hello,-world',true))
187
+ * @example [](<url>?wrap=dropdown(hello_world,,_))
188
+ */
189
+ "dropdown(summary: string, open?: boolean, space?: string)"
190
+ ], Me = "-", be = H(xe), W = (e, n, t) => {
191
+ const s = be(n), o = (t == null ? void 0 : t.inline) && !e.includes(`
192
+
193
+ `);
194
+ switch (s.name) {
195
+ case "code":
196
+ const r = s.lang ?? (t == null ? void 0 : t.extension) ?? "", i = s.meta ? ` ${s.meta}` : "";
197
+ return `\`\`\`${r}${i}
198
+ ${e}
199
+ \`\`\``;
200
+ case "quote":
201
+ return o ? `> ${e}` : `<blockquote>
202
+
203
+ ${e}
204
+
205
+ </blockquote>
206
+ `;
207
+ case "dropdown":
208
+ const c = `<details${s.open ? " open" : ""}>`, l = `<summary>${s.summary.split(s.space ?? Me).join(" ")}</summary>`;
209
+ return ["", c, l, "", e, "</details>", ""].join(`
210
+ `);
211
+ }
212
+ }, _e = [
213
+ "extract(id: string, 0?: string, 1?: string, 2?: string)",
214
+ "remove(id: string, 0?: string, 1?: string, 2?: string)",
215
+ "replace(id: string, with?: string, space?: string)"
216
+ ], ke = H(_e), D = (e) => le(e), B = (e, n) => D(e).filter(({ value: t }) => t.includes(n)).sort((t, s) => t.range[0] - s.range[0]), Te = (e, ...n) => {
217
+ if (n.length === 0) return e;
218
+ const t = ([i, c]) => e.slice(i, c), s = D(e), o = new d(), r = new d();
219
+ for (const i of n) {
220
+ const c = s.filter(({ value: l }) => l.includes(i)).sort((l, a) => l.range[0] - a.range[0]);
221
+ for (let l = 0; l < c.length - 1; l += 2) {
222
+ const a = c[l], p = c[l + 1];
223
+ o.push(a.range[1], p.range[0]);
224
+ const [m, ...f] = t([a.range[1], p.range[0]]), S = f[f.length - 1];
225
+ r.push(a.range[0], a.range[1] + (m.trim() ? 0 : 1)), r.push(p.range[0], p.range[1] + (S.trim() ? 0 : 1));
226
+ }
227
+ }
228
+ return o.collapse(), r.collapse(), k(
229
+ o.subtract(r).map(t).filter(Boolean).join("")
230
+ ).trim();
231
+ }, Ie = (e, ...n) => {
232
+ if (n.length === 0) return e;
233
+ const t = ([i, c]) => e.slice(i, c), s = D(e), o = new d();
234
+ for (const i of n) {
235
+ const c = s.filter(({ value: l }) => l.includes(i)).sort((l, a) => l.range[0] - a.range[0]);
236
+ for (let l = 0; l < c.length - 1; l += 2) {
237
+ const a = c[l], p = c[l + 1], m = t([p.range[1], p.range[1] + 1]).at(-1);
238
+ o.push(a.range[0], m === `
239
+ ` ? p.range[1] + 1 : p.range[1]);
240
+ }
241
+ }
242
+ o.collapse();
243
+ const r = new d();
244
+ return r.push(0, e.length), r.subtract(o), k(
245
+ r.collapse().map(t).filter(Boolean).join("")
246
+ ).trim();
247
+ }, De = (e, n, t, s) => {
248
+ if (!n) return e;
249
+ const o = B(e, n);
250
+ if (o.length < 2) return e;
251
+ let r = "", i = 0;
252
+ for (let l = 0; l < o.length - 1; l += 2) {
253
+ const a = o[l], p = o[l + 1];
254
+ r += e.slice(i, a.range[1]), r += de(t ?? a.value, s), i = p.range[0];
255
+ }
256
+ r += e.slice(i);
257
+ const c = new d();
258
+ return c.push(0, r.length), c.subtract(
259
+ B(r, n).reduce((l, { range: a }) => (l.push(...a), l), new d())
260
+ ), k(
261
+ c.collapse().map(([l, a]) => r.slice(l, a)).filter(Boolean).join("")
262
+ ).trim();
263
+ }, je = (e, n) => {
264
+ const t = ke(n);
265
+ switch (t.name) {
266
+ case "extract":
267
+ return Te(e, t.id, ...R(t));
268
+ case "remove":
269
+ return Ie(e, t.id, ...R(t));
270
+ case "replace":
271
+ return De(e, t.id, t.with, t.space);
272
+ }
273
+ }, Oe = ["http", "./", "../"], Ae = ({ url: e }) => Oe.some((n) => e.startsWith(n)), Le = (e) => T(e) && pe(e) && Ae(e), b = ({ url: e }, n) => `[](${n ? C(n, e) : e})`, h = {
274
+ _open: "<!--",
275
+ _close: "-->",
276
+ _flag: "p▼",
277
+ get begin() {
278
+ return L(h._open, h._flag, "BEGIN", h._close);
279
+ },
280
+ get end() {
281
+ return L(h._open, h._flag, "END", h._close);
282
+ }
283
+ }, F = (e) => (n) => T(n) && n.value === h[e], Pe = ({ position: e, url: n, siblingCount: t }, s) => ({ position: e, url: n, headingDepth: s, inline: t >= 1 }), Re = ({ position: { start: e }, url: n, siblingCount: t }, { position: { end: s } }, o) => ({ position: { start: e, end: s }, url: n, headingDepth: o, inline: t >= 1 }), We = (e, n, t) => e.inline ? `${b(e, t)} ${h.begin} ${n} ${h.end}` : ue(b(e, t), h.begin, n, h.end), Be = (e) => {
284
+ const n = y(e, "heading").reduce((t, { position: s, depth: o }) => t.set(s.start.line, o), /* @__PURE__ */ new Map());
285
+ return (t) => {
286
+ for (let s = t.position.start.line; s >= 1; s--) {
287
+ const o = n.get(s);
288
+ if (o) return o;
289
+ }
290
+ return 0;
291
+ };
292
+ }, E = {
293
+ openingCommentDoesNotFollowLink: ({ position: { start: e } }) => new Error(`Opening comment (@${e.line}:${e.column}) does not follow link`),
294
+ closingCommentNotMatchedToOpening: ({ position: { start: e } }) => new Error(`Closing comment (@${e.line}:${e.column}) does not match to opening comment`),
295
+ openingCommentNotClosed: ({ position: { start: e } }) => new Error(`Opening comment (@${e.line}:${e.column}) is not followed by a closing comment`)
296
+ }, Fe = (e, n) => {
297
+ const t = [], s = [
298
+ ...e.map((r) => ({ node: r, type: "open" })),
299
+ ...n.map((r) => ({ node: r, type: "close" }))
300
+ ].sort((r, i) => g(r.node, i.node)), o = [];
301
+ for (const r of s)
302
+ if (r.type === "open") o.push(r);
303
+ else {
304
+ const i = r.node;
305
+ if (o.length === 0)
306
+ throw E.closingCommentNotMatchedToOpening(i);
307
+ const c = o.pop().node;
308
+ if (o.length > 0) continue;
309
+ t.push({ open: c, close: i });
310
+ }
311
+ if (o.length > 0)
312
+ throw E.openingCommentNotClosed(o[0].node);
313
+ return t;
314
+ }, qe = (e, n, t) => {
315
+ const s = [...n].sort(g), o = [];
316
+ return [...t].sort((r, i) => g.reverse(r.open, i.open)).forEach((r) => {
317
+ for (; s.length > 0; ) {
318
+ const i = s.pop();
319
+ if (i.position.start.offset < r.open.position.start.offset) {
320
+ if (me(e, i, r.open).trim() !== "")
321
+ throw E.openingCommentDoesNotFollowLink(r.open);
322
+ return o.push([i, r]);
323
+ }
324
+ o.push(i);
325
+ }
326
+ throw E.openingCommentDoesNotFollowLink(r.open);
327
+ }), o.push(...s.reverse()), o.reverse();
328
+ }, Q = (e, n) => {
329
+ n ?? (n = I.md(e));
330
+ const t = Be(n), s = y(n, "link").filter(Le), o = y(n, "html").sort(g), r = o.filter(F("begin")), i = o.filter(F("end")), c = Fe(r, i);
331
+ return qe(e, s, c).map((a) => Array.isArray(a) ? Re(a[0], a[1].close, t(a[0])) : Pe(a, t(a)));
332
+ }, Ue = (e, { url: n }) => (t) => e(C(_(n), t)), z = (...e) => {
333
+ const n = e.reduce((t, s) => t + s, 0);
334
+ return Math.min(Math.max(n, 1), 6);
335
+ }, Ge = (e, n, t) => {
336
+ if (n === 0) return e;
337
+ t ?? (t = I.md(e));
338
+ const s = y(t, "heading"), o = e.split(`
339
+ `);
340
+ for (const r of s) {
341
+ const { depth: i, position: { start: c, end: l } } = r, a = z(i, n), p = o[c.line - 1].slice(i, l.column), m = "#".repeat(a) + p;
342
+ o[c.line - 1] = m, r.depth = a;
343
+ }
344
+ return o.join(`
345
+ `);
346
+ }, X = (e) => Q(e).reverse().sort(g.reverse).reduce((n, t) => G(n, b(t), t), e), J = (e, n, t, s) => {
347
+ e = X(e), e = Ge(e, n);
348
+ const o = I.md(e);
349
+ return Q(e, o).sort(g).reverse().map((r) => {
350
+ var O, A;
351
+ const { url: i, headingDepth: c } = r, [l, ...a] = ne(i).split("?"), p = l.split(".").pop() ?? "", m = a.join("?"), f = _(i), S = C(f, l);
352
+ if (i.startsWith("./") || i.startsWith("../")) {
353
+ let u = t(S);
354
+ const w = new oe(m), N = (O = w.get("region")) == null ? void 0 : O.split(P);
355
+ u = (N == null ? void 0 : N.reduce((v, $) => je(v, $), u)) ?? u;
356
+ const Z = w.has("skip"), K = w.get("heading") ?? 0, Y = w.has("inline");
357
+ let { inline: x } = r;
358
+ if (Y && (x = !0), !Z)
359
+ if (p === "md") {
360
+ const v = Ue(t, r), $ = s ? C(s, f) : f, ee = z(c, Number(K));
361
+ u = J(
362
+ u,
363
+ /** p▼: ... */
364
+ ee,
365
+ v,
366
+ $
367
+ /** p▼: ... */
368
+ );
369
+ } else /^(js|ts)x?|svelte$/i.test(p) && (u = W(
370
+ u,
371
+ "code",
372
+ /** p▼: ... */
373
+ { extension: p, inline: x }
374
+ /** p▼: ... */
375
+ ));
376
+ const M = (A = w.get("wrap")) == null ? void 0 : A.split(P);
377
+ return u = (M == null ? void 0 : M.reduce((v, $) => W(v, $, { extension: p, inline: x }), u)) ?? u, { target: r, content: We(r, u, s) };
378
+ } else throw i.startsWith("http") ? new Error("External web links are not implemented yet") : new Error(`Unsupported link type: ${i}`);
379
+ }).reduce((r, { target: i, content: c }) => G(r, c, i), e);
380
+ }, V = (e) => te(e, "utf-8"), j = (e) => {
381
+ const n = U(e), t = _(n);
382
+ return { markdown: V(n), dir: t, path: n };
383
+ }, et = (e, n = !0) => {
384
+ const { dir: t, path: s, markdown: o } = j(e), i = J(o, 0, (c) => V(U(t, ge(c))));
385
+ return n && q(s, i), i;
386
+ }, tt = (e, n = !0) => {
387
+ const { path: t, markdown: s } = j(e), o = X(s);
388
+ return n && q(t, o), o;
389
+ }, nt = (e, n = !0) => {
390
+ j(e);
391
+ };
392
+ export {
393
+ tt as depopulateMarkdownInclusions,
394
+ et as populateMarkdownInclusions,
395
+ nt as remapImportSpecifiers
396
+ };
@@ -0,0 +1,15 @@
1
+ (function(u,w){typeof exports=="object"&&typeof module<"u"?w(exports,require("node:fs"),require("node:path"),require("node:url"),require("unified"),require("remark-parse"),require("unist-util-visit"),require("stable-hash"),require("ts-dedent"),require("extract-comments")):typeof define=="function"&&define.amd?define(["exports","node:fs","node:path","node:url","unified","remark-parse","unist-util-visit","stable-hash","ts-dedent","extract-comments"],w):(u=typeof globalThis<"u"?globalThis:u||self,w(u.index={},u.node_fs,u.node_path,u.node_url,u.unified,u.remarkParse,u.unistUtilVisit,u.hash,u.tsDedent,u._extractComments))})(this,function(u,w,f,J,Z,K,Y,ee,x,te){"use strict";const g=(e,n)=>e.position.start.offset-n.position.start.offset;g.reverse=(e,n)=>g(n,e);const N=e=>e.position!==void 0&&e.position.start.offset!==void 0&&e.position.end.offset!==void 0,ne=Z.unified().use(K),k={md:e=>ne.parse(e)},E=(e,n)=>{const t=[];return Y.visit(e,(o,s,r)=>{if(o.type!=="root"){if(n&&o.type!==n)return;if(N(o)){const i=ee(r),c=((r==null?void 0:r.children.length)??0)-1;t.push({...o,parentID:i,siblingIndex:s,siblingCount:c})}}}),t},se=e=>e.children.length===0,P=(e,n,...t)=>{if(t.length===0)throw new Error("No nodes to replace content from");t.sort(g);const o=t.at(0),s=t.at(-1);return e.slice(0,o.position.start.offset)+n+e.slice(s.position.end.offset)},oe=(e,n,t)=>{const o=Math.min(n.position.end.offset,t.position.end.offset),s=Math.max(n.position.start.offset,t.position.start.offset);return e.slice(o,s)},A=(...e)=>e.join(" "),re=(...e)=>e.join(`
2
+ `),ie=e=>{const n=e.split("?");return n.length>1?[n.slice(0,-1).join("?"),n.at(-1)]:[e,""]},ce=e=>ie(e)[0];class v{constructor(){this.intervals=[]}push(n,t){this.intervals.push([Math.min(n,t),Math.max(n,t)])}combine(n){this.intervals.push(...n.intervals)}collapse(){const{intervals:n}=this;if(!n.length)return this.intervals=[];n.sort((r,i)=>r[0]-i[0]);const t=[];let[o,s]=n[0];for(let r=1;r<n.length;r++){const[i,c]=n[r];i<=s?s=Math.max(s,c):(t.push([o,s]),o=i,s=c)}return t.push([o,s]),this.intervals=t}subtract(n){const{intervals:t}=this,{intervals:o}=n;if(!t.length||!o.length)return t;let s=[...t];for(const[r,i]of o){const c=[];for(const[l,a]of s){if(i<=l||r>=a){c.push([l,a]);continue}r>l&&c.push([l,r]),i<a&&c.push([i,a])}s=c}return this.intervals=s}}const L=/,\s*(?![^()]*\))/,le=[[/'''/g,'"'],[/''/g,"'"],[/parkdown:\s+/g,""],[/p▼:\s+/g,""]],ae=(e,n="-")=>{const t=le.reduce((o,[s,r])=>o.replaceAll(s,r),e);return n?t.replaceAll(n," "):t},pe=["string","number","boolean"],ue=e=>pe.includes(e),me=/^([a-zA-Z0-9_-]+)(?:\(([^)]*)\))?$/,de=e=>{const n=e.match(me);if(!n)throw new Error(`Invalid invocation: ${e}`);const[,t,o=""]=n;if(!o.trim())return{name:t,parameters:[]};const s=fe(o);return{name:t,parameters:s}},he=(e,n)=>{for(let t=n+1;t<e.length;t++)if(e[t]!==void 0)return!1;return!0},fe=e=>{const n=[];let t="",o=!1;for(let s=0;s<e.length;s++){const r=e[s],i=r==="'";if(i&&e.at(s+1)==="'"){const c=e.at(s+2)==="'";o=!o,t+=c?"'''":"''",s+=c?2:1}else i?(o=!o,t+=r):r===","&&!o?(n.push(t.trim()),t=""):t+=r}return n.push(t.trim()),n.map(s=>s===""?void 0:s?ge(s):void 0).filter((s,r,i)=>s!==void 0||!he(i,r))},ge=e=>{const n=e.length>=2&&e[0]==="'"&&e.at(-1)==="'",t=n&&e.length>=4&&e[1]==="'"&&e.at(-2)==="'";return!n||t?e:e.slice(1,-1)},we=e=>{const n=/^(\w+)(?:\(([^)]*)\))?/,t=e.match(n);if(!t)return{name:e};const[,o,s]=t;if(!s)return{name:o};const r=/(\w+)(\?)?:\s*([^,]+)/g,i=[];let c;for(;(c=r.exec(s))!==null;){const[,l,a,p]=c,m=p.trim();if(!ue(m))throw new Error(`Unsupported type: ${m}`);i.push({name:l,optional:a==="?",type:m})}return{name:o,parameters:i}},R=e=>{const n=e.map(we).reduce((t,{name:o,parameters:s})=>t.set(o,s),new Map);return t=>{const{name:o,parameters:s}=de(t);if(!n.has(o))throw new Error(`Unknown method: ${o}`);const r=n.get(o);if(r===void 0)return{name:o};if(s.length>r.length){const i=r.filter(({optional:l})=>!l).length,c=i===r.length?i.toString():`${i} - ${r.length}`;throw new Error(`Too many parameters: ${s.length} for method '${o}' (expected: ${c})`)}return r.reduce((i,{name:c,optional:l,type:a},p)=>{const m=s[p];if(m===void 0){if(l)return i;throw new Error(`Missing required parameter: ${c} for method '${o}'`)}switch(a){case"string":i[c]=m;break;case"number":case"boolean":i[c]=JSON.parse(m);break}if(typeof i[c]!==a)throw new Error(`Invalid type: ${c} must be ${a}, got ${typeof i[c]} for method '${o}'`);return i},{name:o})}},_=e=>Object.entries(e).filter(([n])=>n!=="name"&&!isNaN(Number(n))).map(([n,t])=>t),ve=["code(lang?: string, meta?: string)","quote()","dropdown(summary: string, open?: boolean, space?: string)"],$e="-",Ce=R(ve),F=(e,n,t)=>{const o=Ce(n),s=(t==null?void 0:t.inline)&&!e.includes(`
3
+
4
+ `);switch(o.name){case"code":const r=o.lang??(t==null?void 0:t.extension)??"",i=o.meta?` ${o.meta}`:"";return`\`\`\`${r}${i}
5
+ ${e}
6
+ \`\`\``;case"quote":return s?`> ${e}`:`<blockquote>
7
+
8
+ ${e}
9
+
10
+ </blockquote>
11
+ `;case"dropdown":const c=`<details${o.open?" open":""}>`,l=`<summary>${o.summary.split(o.space??$e).join(" ")}</summary>`;return["",c,l,"",e,"</details>",""].join(`
12
+ `)}},ye=R(["extract(id: string, 0?: string, 1?: string, 2?: string)","remove(id: string, 0?: string, 1?: string, 2?: string)","replace(id: string, with?: string, space?: string)"]),T=e=>te(e),W=(e,n)=>T(e).filter(({value:t})=>t.includes(n)).sort((t,o)=>t.range[0]-o.range[0]),Se=(e,...n)=>{if(n.length===0)return e;const t=([i,c])=>e.slice(i,c),o=T(e),s=new v,r=new v;for(const i of n){const c=o.filter(({value:l})=>l.includes(i)).sort((l,a)=>l.range[0]-a.range[0]);for(let l=0;l<c.length-1;l+=2){const a=c[l],p=c[l+1];s.push(a.range[1],p.range[0]);const[m,...$]=t([a.range[1],p.range[0]]),b=$[$.length-1];r.push(a.range[0],a.range[1]+(m.trim()?0:1)),r.push(p.range[0],p.range[1]+(b.trim()?0:1))}}return s.collapse(),r.collapse(),x.dedent(s.subtract(r).map(t).filter(Boolean).join("")).trim()},Ee=(e,...n)=>{if(n.length===0)return e;const t=([i,c])=>e.slice(i,c),o=T(e),s=new v;for(const i of n){const c=o.filter(({value:l})=>l.includes(i)).sort((l,a)=>l.range[0]-a.range[0]);for(let l=0;l<c.length-1;l+=2){const a=c[l],p=c[l+1],m=t([p.range[1],p.range[1]+1]).at(-1);s.push(a.range[0],m===`
13
+ `?p.range[1]+1:p.range[1])}}s.collapse();const r=new v;return r.push(0,e.length),r.subtract(s),x.dedent(r.collapse().map(t).filter(Boolean).join("")).trim()},Me=(e,n,t,o)=>{if(!n)return e;const s=W(e,n);if(s.length<2)return e;let r="",i=0;for(let l=0;l<s.length-1;l+=2){const a=s[l],p=s[l+1];r+=e.slice(i,a.range[1]),r+=ae(t??a.value,o),i=p.range[0]}r+=e.slice(i);const c=new v;return c.push(0,r.length),c.subtract(W(r,n).reduce((l,{range:a})=>(l.push(...a),l),new v)),x.dedent(c.collapse().map(([l,a])=>r.slice(l,a)).filter(Boolean).join("")).trim()},xe=(e,n)=>{const t=ye(n);switch(t.name){case"extract":return Se(e,t.id,..._(t));case"remove":return Ee(e,t.id,..._(t));case"replace":return Me(e,t.id,t.with,t.space)}},Ne=["http","./","../"],ke=({url:e})=>Ne.some(n=>e.startsWith(n)),Te=e=>N(e)&&se(e)&&ke(e),I=({url:e},n)=>`[](${n?f.join(n,e):e})`,d={_open:"<!--",_close:"-->",_flag:"p▼",get begin(){return A(d._open,d._flag,"BEGIN",d._close)},get end(){return A(d._open,d._flag,"END",d._close)}},B=e=>n=>N(n)&&n.value===d[e],Ie=({position:e,url:n,siblingCount:t},o)=>({position:e,url:n,headingDepth:o,inline:t>=1}),je=({position:{start:e},url:n,siblingCount:t},{position:{end:o}},s)=>({position:{start:e,end:o},url:n,headingDepth:s,inline:t>=1}),be=(e,n,t)=>e.inline?`${I(e,t)} ${d.begin} ${n} ${d.end}`:re(I(e,t),d.begin,n,d.end),qe=e=>{const n=E(e,"heading").reduce((t,{position:o,depth:s})=>t.set(o.start.line,s),new Map);return t=>{for(let o=t.position.start.line;o>=1;o--){const s=n.get(o);if(s)return s}return 0}},M={openingCommentDoesNotFollowLink:({position:{start:e}})=>new Error(`Opening comment (@${e.line}:${e.column}) does not follow link`),closingCommentNotMatchedToOpening:({position:{start:e}})=>new Error(`Closing comment (@${e.line}:${e.column}) does not match to opening comment`),openingCommentNotClosed:({position:{start:e}})=>new Error(`Opening comment (@${e.line}:${e.column}) is not followed by a closing comment`)},De=(e,n)=>{const t=[],o=[...e.map(r=>({node:r,type:"open"})),...n.map(r=>({node:r,type:"close"}))].sort((r,i)=>g(r.node,i.node)),s=[];for(const r of o)if(r.type==="open")s.push(r);else{const i=r.node;if(s.length===0)throw M.closingCommentNotMatchedToOpening(i);const c=s.pop().node;if(s.length>0)continue;t.push({open:c,close:i})}if(s.length>0)throw M.openingCommentNotClosed(s[0].node);return t},Oe=(e,n,t)=>{const o=[...n].sort(g),s=[];return[...t].sort((r,i)=>g.reverse(r.open,i.open)).forEach(r=>{for(;o.length>0;){const i=o.pop();if(i.position.start.offset<r.open.position.start.offset){if(oe(e,i,r.open).trim()!=="")throw M.openingCommentDoesNotFollowLink(r.open);return s.push([i,r])}s.push(i)}throw M.openingCommentDoesNotFollowLink(r.open)}),s.push(...o.reverse()),s.reverse()},U=(e,n)=>{n??(n=k.md(e));const t=qe(n),o=E(n,"link").filter(Te),s=E(n,"html").sort(g),r=s.filter(B("begin")),i=s.filter(B("end")),c=De(r,i);return Oe(e,o,c).map(a=>Array.isArray(a)?je(a[0],a[1].close,t(a[0])):Ie(a,t(a)))},Pe=(e,{url:n})=>t=>e(f.join(f.dirname(n),t)),G=(...e)=>{const n=e.reduce((t,o)=>t+o,0);return Math.min(Math.max(n,1),6)},Ae=(e,n,t)=>{if(n===0)return e;t??(t=k.md(e));const o=E(t,"heading"),s=e.split(`
14
+ `);for(const r of o){const{depth:i,position:{start:c,end:l}}=r,a=G(i,n),p=s[c.line-1].slice(i,l.column),m="#".repeat(a)+p;s[c.line-1]=m,r.depth=a}return s.join(`
15
+ `)},H=e=>U(e).reverse().sort(g.reverse).reduce((n,t)=>P(n,I(t),t),e),Q=(e,n,t,o)=>{e=H(e),e=Ae(e,n);const s=k.md(e);return U(e,s).sort(g).reverse().map(r=>{var X,V;const{url:i,headingDepth:c}=r,[l,...a]=f.basename(i).split("?"),p=l.split(".").pop()??"",m=a.join("?"),$=f.dirname(i),b=f.join($,l);if(i.startsWith("./")||i.startsWith("../")){let h=t(b);const C=new J.URLSearchParams(m),q=(X=C.get("region"))==null?void 0:X.split(L);h=(q==null?void 0:q.reduce((y,S)=>xe(y,S),h))??h;const Fe=C.has("skip"),We=C.get("heading")??0,Be=C.has("inline");let{inline:D}=r;if(Be&&(D=!0),!Fe)if(p==="md"){const y=Pe(t,r),S=o?f.join(o,$):$,Ue=G(c,Number(We));h=Q(h,Ue,y,S)}else/^(js|ts)x?|svelte$/i.test(p)&&(h=F(h,"code",{extension:p,inline:D}));const O=(V=C.get("wrap"))==null?void 0:V.split(L);return h=(O==null?void 0:O.reduce((y,S)=>F(y,S,{extension:p,inline:D}),h))??h,{target:r,content:be(r,h,o)}}else throw i.startsWith("http")?new Error("External web links are not implemented yet"):new Error(`Unsupported link type: ${i}`)}).reduce((r,{target:i,content:c})=>P(r,c,i),e)},z=e=>w.readFileSync(e,"utf-8"),j=e=>{const n=f.resolve(e),t=f.dirname(n);return{markdown:z(n),dir:t,path:n}},Le=(e,n=!0)=>{const{dir:t,path:o,markdown:s}=j(e),i=Q(s,0,c=>z(f.resolve(t,ce(c))));return n&&w.writeFileSync(o,i),i},Re=(e,n=!0)=>{const{path:t,markdown:o}=j(e),s=H(o);return n&&w.writeFileSync(t,s),s},_e=(e,n=!0)=>{j(e)};u.depopulateMarkdownInclusions=Re,u.populateMarkdownInclusions=Le,u.remapImportSpecifiers=_e,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -2,9 +2,17 @@
2
2
  "name": "@p-buddy/parkdown",
3
3
  "type": "module",
4
4
  "private": false,
5
- "version": "0.0.1",
5
+ "version": "0.0.3",
6
6
  "main": "dist/index.js",
7
7
  "bin": "dist/cli.js",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
8
16
  "devDependencies": {
9
17
  "@types/node": "^22.13.10",
10
18
  "@types/unist": "^3.0.3",
@@ -1,3 +0,0 @@
1
- **_NOTE ON API USAGE:_** As you can see from the included examples, each _invocation_ of an API method looks like a less strict (more quirky) version of a typical javascript function invocation.
2
-
3
- Please see the [full explanation](#query-parameters-with-function-like-apis) to learn more and/or if the below is confusing.
package/.assets/api.md DELETED
@@ -1,34 +0,0 @@
1
- ## Query Parameters with Function-like APIs
2
-
3
- Some query parameters have more complex APIs, which are defined by a collection of typescript function singatures (limited to only `string`, `boolean`, and `number` arguments), like:
4
-
5
- ```ts
6
- const definitions = [
7
- "method(arg1: string, arg2: boolean, arg3?: number)",
8
- "otherMethod(arg1: string, arg2?: boolean, arg3?: number)"
9
- ]
10
- ```
11
-
12
- These APIs are utilized when setting the query parameter with a _function-like_ invocation syntax, such as:
13
-
14
- ```md
15
- [](<url>?example=method(hello-world,true))
16
- ```
17
-
18
- As you can maybe tell from the example above, we're relaxing some constraints on typical function invocations (like the need to wrap string arguments in quotes), while also imposing some additional constraints (like not using spaces) to ensure the links are valid markdown and the URLs are [safe](https://support.exactonline.com/community/s/knowledge-base?language=en_GB#All-All-DNO-Content-urlcharacters).
19
-
20
- The goal is to make it as painless as possible to author links that are valid markdown, valid URLs, and easy to read and write.
21
-
22
- Please note the following:
23
-
24
- - Methods that take no arguments can be invoked without parentheses (e.g. `[](<url>?example=method)`).
25
- - String arguments do not need to be wrapped in quotes (e.g. `[](<url>?example=method(some-string))`), and they should **NOT** be wrapped in double quotes (see more below).
26
- - You cannot use spaces within a string argument or anywhere else in the query (as this would violate the markdown link syntax). For arguments that reasonably could include spaces, there should be an optional `space` argument that defaults to `-`, so that any usage of the space character will be converted to a space (e.g. `hello-world` becomes `hello world`).
27
- - If a method takes a string argument, and you want to include a comma within that argument, you must wrap it in one or more single quotes (`'`).
28
- - String arguments wrapped in single set of single quotes will automatically have their single quotes removed when the query is parsed (e.g. the argument included in `[](<url>?example=method('hello,world'))` will parse to `hello,world`).
29
- - If you want single quotes preserved in the parsed output, use two single quotes in a row (e.g. `[](<url>?example=method(''single-quoted''))`).
30
- - You cannot use double quotes within a string argument (as they are not a [URL safe character](https://support.exactonline.com/community/s/knowledge-base#All-All-DNO-Content-urlcharacters)). To include a double-quote in the parsed output, use three single quotes in a row (e.g. `[](<url>?example=method'''double-quoted'''))`).
31
- - Optional arguments can be completely ommitted (for example if a `method` took 3 optional arguments, and you only wanted to provide the third, you could do the following: `[](<url>?example=method(,,your-third-argument))`).
32
- - Overall, text meant to be displayed to a human will be _sanitized_ in the following manner (unless otherwise noted):
33
-
34
- [](../src/utils.ts?region=extract(sanitize))
@@ -1,69 +0,0 @@
1
- # Authoring
2
-
3
- You author inclusions in your markdown files using a link with no text i.e. `[](<url>)`, where `<url>` points to some local or remote text resource (e.g.`./other.md`, `https://example.com/remote.md`).
4
-
5
- These links can be rendered either [inline](#inline) or [block](#block), depending on how you author them.
6
-
7
- ## Inline
8
-
9
- Inline inclusions occur when your _text-less_ link has 1 or more siblings (meaning it's **not** the only node in a [paragraph](https://www.markdownguide.org/basic-syntax/#paragraphs-1)).
10
-
11
- There are two equivalent ways to author inline inclusions, [single-line](#single-line) or [multi-line](#multi-line), and which you choose depends solely on how you want your raw markdown to look (it will **not** affect the rendered output).
12
-
13
- ### Single-line
14
-
15
- What you write:
16
-
17
- [](./unpopulated/inline.single.md?wrap=code)
18
-
19
- What is rendered (**_before_** processing, same as [Option B](#option-b-multi-line)):
20
-
21
- [](./unpopulated/inline.single.md?wrap=quote&inline)
22
-
23
- What your markdown file contains (**_after_** processing):
24
-
25
- [](./populated/inline.single.md?wrap=code)
26
-
27
- What is rendered (**_after_** processing, same as [Option B](#option-b-multi-line)):
28
-
29
- [](./populated/inline.single.md?wrap=quote&inline)
30
-
31
- ### Multi-line
32
-
33
- What you write:
34
-
35
- [](./unpopulated/inline.multi.md?wrap=code)
36
-
37
- What is rendered (**_before_** processing, same as [Option A](#option-a-single-line)):
38
-
39
- [](./unpopulated/inline.multi.md?wrap=quote&inline)
40
-
41
- What your markdown file contains (**_after_** processing):
42
-
43
- [](./populated/inline.multi.md?wrap=code)
44
-
45
- What is rendered (**_after_** processing, same as [Option A](#option-a-single-line)):
46
-
47
- [](./populated/inline.multi.md?wrap=quote&inline)
48
-
49
- ## Block
50
-
51
- Block inclusions occur when your "empty" link is the **only** node in a [paragraph](https://www.markdownguide.org/basic-syntax/#paragraphs-1) (at least before being populated). This is likely the most common way to author inclusions.
52
-
53
- What you write:
54
-
55
- [](./unpopulated/block.md?wrap=code)
56
-
57
- What is rendered (**_before_** processing):
58
-
59
- [](./unpopulated/block.md?wrap=quote)
60
-
61
- What your markdown file contains (**_after_** processing):
62
-
63
- [](./populated/block.md?wrap=code)
64
-
65
- What is rendered (**_after_** processing):
66
-
67
- [](./populated/block.md?wrap=quote)
68
-
69
- [](./query.md?heading=-1)
@@ -1,6 +0,0 @@
1
- import { depopulateMarkdownInclusions } from /** p▼: pkg */ "../../src" /** p▼: pkg */;
2
-
3
- const file = "README.md";
4
- const writeFile = true;
5
-
6
- depopulateMarkdownInclusions(file, writeFile);
@@ -1,6 +0,0 @@
1
- import { populateMarkdownInclusions } from /** p▼: pkg */ "../../src" /** p▼: pkg */;
2
-
3
- const file = "README.md";
4
- const writeFile = true;
5
-
6
- populateMarkdownInclusions(file, writeFile);
@@ -1,25 +0,0 @@
1
- # Removing populated inclusions
2
-
3
- Sometimes you may want to remove populated inclusions from your markdown file, since they can make things more difficult to read during authoring. You can do this either using the [cli](#cli-removing-populated-inclusions) or via the `removePopulatedInclusions` [export](#depopulateMarkdownIncludes-export):
4
-
5
- ## CLI (removing populated inclusions)
6
-
7
- The following commands are all equivalent:
8
-
9
- ```bash
10
- npx parkdown --file ./README.md --depopulate --no-inclusions
11
- npx parkdown -f README.md -d --ni # Notice the double-dash (--) on 'ni'
12
- npx parkdown -d --ni # defaults to processing the 'README.md' file of the current working directory
13
- ```
14
-
15
- The following commands will lead to the same result, but since `--no-inclusions` (`--ni`) is not specified, there will be some wasted work as inclusions will be processed and then removed.
16
-
17
- ```bash
18
- npx parkdown --file ./README.md --depopulate
19
- npx parkdown -f README.md -d
20
- npx parkdown -d # defaults to processing the 'README.md' file of the current working directory
21
- ```
22
-
23
- ## `depopulateMarkdownIncludes` export
24
-
25
- [](./code/depopulate.ts?region=replace(pkg,'''parkdown'''))
@@ -1,16 +0,0 @@
1
- # Invocation
2
-
3
- Invoke [parkdown's]() functionality with either the [cli](#cli-inclusions) or via the `processMarkdownIncludes` [export](#populateMarkdownIncludes-export):
4
-
5
- ## CLI
6
-
7
- The following commands are all equivalent:
8
- ```bash
9
- npx parkdown --file ./README.md
10
- npx parkdown -f README.md
11
- npx parkdown # defaults to processing inclusions in the 'README.md' file of the current working directory
12
- ```
13
-
14
- ## `populateMarkdownIncludes` export
15
-
16
- [](./code/inclusions.ts?region=replace(pkg,'''parkdown'''))
@@ -1,9 +0,0 @@
1
- Before...
2
-
3
- [](<url>)
4
- <!-- p▼ Begin -->
5
- ...Included Content...
6
- ...Included Content...
7
- <!-- p▼ End -->
8
-
9
- ...After
@@ -1,5 +0,0 @@
1
- Before...
2
- [](<url>) <!-- p▼ Begin -->
3
- ...Included Content...
4
- ...Included Content... <!-- p▼ End -->
5
- ...After