mdq-cli 0.1.0

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/index.js ADDED
@@ -0,0 +1,635 @@
1
+ // src/utils/mdq/query.ts
2
+ import { marked } from "marked";
3
+
4
+ // src/utils/mdq/edit.ts
5
+ import YAML from "yaml";
6
+
7
+ class MarkdownEditor {
8
+ source;
9
+ constructor(source) {
10
+ this.source = source;
11
+ }
12
+ static splitFrontmatter(source) {
13
+ if (!source.startsWith("---"))
14
+ return { raw: "", body: source, offset: 0 };
15
+ const match = source.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/);
16
+ if (!match)
17
+ return { raw: "", body: source, offset: 0 };
18
+ return { raw: match[1], body: source.slice(match[0].length), offset: match[0].length };
19
+ }
20
+ static blockEnd(range) {
21
+ if (range.trailing)
22
+ return range.trailing.start + range.trailing.length;
23
+ return range.start + range.length;
24
+ }
25
+ static table(headers, rows, align) {
26
+ const widths = headers.map((header, index) => Math.max(header.length, 3, ...rows.map((row) => (row[index] || "").length)));
27
+ const line = (cells) => `| ${cells.map((cell, index) => (cell || "").padEnd(widths[index])).join(" | ")} |`;
28
+ const divider = `| ${widths.map((width, index) => MarkdownEditor.dashes(align[index], width)).join(" | ")} |`;
29
+ return `${[line(headers), divider, ...rows.map(line)].join(`
30
+ `)}
31
+ `;
32
+ }
33
+ static item(listRaw, text) {
34
+ const lines = listRaw.split(`
35
+ `).filter((line) => line.trim());
36
+ const last = lines[lines.length - 1] || "- x";
37
+ const ordered = last.match(/^(\s*)(\d+)([.)])\s/);
38
+ if (ordered)
39
+ return `${ordered[1]}${Number.parseInt(ordered[2], 10) + 1}${ordered[3]} ${text}`;
40
+ const bullet = last.match(/^(\s*)([-*+])\s/);
41
+ if (!bullet)
42
+ return `- ${text}`;
43
+ return `${bullet[1]}${bullet[2]} ${text}`;
44
+ }
45
+ static entries(text, key, value, isBlockquote) {
46
+ const lines = text.split(`
47
+ `).map((line) => line.trim()).filter(Boolean);
48
+ const index = lines.findIndex((line) => MarkdownEditor.entryKey(line) === key.toLowerCase());
49
+ if (index < 0 && value)
50
+ lines.push(`${key}: ${value}`);
51
+ if (index >= 0 && value)
52
+ lines[index] = `${key}: ${value}`;
53
+ if (index >= 0 && !value)
54
+ lines.splice(index, 1);
55
+ if (!isBlockquote)
56
+ return lines.join(`
57
+ `);
58
+ return lines.map((line) => `> ${line}`).join(`
59
+ `);
60
+ }
61
+ remove(ranges) {
62
+ const ordered = this.dedupe(ranges);
63
+ let result = this.source;
64
+ for (let i = ordered.length - 1;i >= 0; i--) {
65
+ const range = ordered[i];
66
+ const head = result.slice(0, range.start);
67
+ const tail = result.slice(MarkdownEditor.blockEnd(range));
68
+ if (tail) {
69
+ result = head + tail;
70
+ continue;
71
+ }
72
+ if (!head) {
73
+ result = "";
74
+ continue;
75
+ }
76
+ result = `${head.trimEnd()}
77
+ `;
78
+ }
79
+ return result;
80
+ }
81
+ replace(ranges, render) {
82
+ const ordered = this.dedupe(ranges);
83
+ const rendered = ordered.map(render);
84
+ let result = this.source;
85
+ for (let i = ordered.length - 1;i >= 0; i--) {
86
+ const range = ordered[i];
87
+ result = result.slice(0, range.start) + rendered[i] + result.slice(range.start + range.length);
88
+ }
89
+ return result;
90
+ }
91
+ insert(offsets, markdown) {
92
+ const ordered = [...offsets].sort((a, b) => a - b);
93
+ let result = this.source;
94
+ for (let i = ordered.length - 1;i >= 0; i--) {
95
+ result = MarkdownEditor.spliceIn(result, ordered[i], markdown);
96
+ }
97
+ return result;
98
+ }
99
+ frontmatter() {
100
+ const { raw } = MarkdownEditor.splitFrontmatter(this.source);
101
+ if (!raw)
102
+ return {};
103
+ return YAML.parseDocument(raw).toJS() || {};
104
+ }
105
+ setFrontmatter(key, value) {
106
+ const { raw, body, offset } = MarkdownEditor.splitFrontmatter(this.source);
107
+ let document = new YAML.Document({});
108
+ if (raw)
109
+ document = YAML.parseDocument(raw);
110
+ if (value === null)
111
+ document.delete(key);
112
+ if (value !== null)
113
+ document.set(key, value);
114
+ const rendered = document.toString().trimEnd();
115
+ if (!offset)
116
+ return `---
117
+ ${rendered}
118
+ ---
119
+
120
+ ${this.source}`;
121
+ return `---
122
+ ${rendered}
123
+ ---
124
+ ${body}`;
125
+ }
126
+ static spliceIn(source, offset, markdown) {
127
+ const block = `${markdown.trimEnd()}
128
+ `;
129
+ const before = source.slice(0, offset).replace(/\n+$/, "");
130
+ const after = source.slice(offset).replace(/^\n+/, "");
131
+ if (!before)
132
+ return `${block}
133
+ ${after}`;
134
+ if (!after)
135
+ return `${before}
136
+
137
+ ${block}`;
138
+ return `${before}
139
+
140
+ ${block}
141
+ ${after}`;
142
+ }
143
+ static dashes(alignment, width) {
144
+ if (alignment === "center")
145
+ return `:${"-".repeat(Math.max(width - 2, 1))}:`;
146
+ if (alignment === "left")
147
+ return `:${"-".repeat(Math.max(width - 1, 1))}`;
148
+ if (alignment === "right")
149
+ return `${"-".repeat(Math.max(width - 1, 1))}:`;
150
+ return "-".repeat(width);
151
+ }
152
+ static entryKey(line) {
153
+ const separator = line.indexOf(":");
154
+ if (separator < 1)
155
+ return null;
156
+ return line.slice(0, separator).trim().toLowerCase();
157
+ }
158
+ dedupe(ranges) {
159
+ const sorted = [...ranges].sort((a, b) => a.start - b.start);
160
+ const kept = [];
161
+ let lastEnd = -1;
162
+ for (const range of sorted) {
163
+ if (range.start < lastEnd)
164
+ continue;
165
+ kept.push(range);
166
+ lastEnd = range.start + range.length;
167
+ }
168
+ return kept;
169
+ }
170
+ }
171
+
172
+ // src/utils/mdq/query.ts
173
+ class MarkdownQuery {
174
+ source;
175
+ matches;
176
+ constructor(source, matches) {
177
+ this.source = source;
178
+ this.matches = matches || buildTokenIndex(source);
179
+ }
180
+ query(selector, matcher) {
181
+ const segments = parseQuery(selector);
182
+ if (matcher !== undefined && segments.length > 0)
183
+ segments[segments.length - 1].textMatch = this.valueMatcher(matcher);
184
+ return new MarkdownQuery(this.source, this.run(this.expandSections(this.matches), segments));
185
+ }
186
+ section(matcher, options) {
187
+ return this.query(`section${options?.depth || ""}`, matcher);
188
+ }
189
+ heading(matcher, options) {
190
+ if (options?.depth)
191
+ return this.query(`h${options.depth}`, matcher);
192
+ return this.query("heading", matcher);
193
+ }
194
+ paragraph(matcher) {
195
+ return this.query("paragraph", matcher);
196
+ }
197
+ table(matcher) {
198
+ return this.query("table", matcher);
199
+ }
200
+ list(matcher) {
201
+ return this.query("list", matcher);
202
+ }
203
+ item(matcher) {
204
+ return this.query("item", matcher);
205
+ }
206
+ code(matcher) {
207
+ return this.query("code", matcher);
208
+ }
209
+ blockquote(matcher) {
210
+ return this.query("blockquote", matcher);
211
+ }
212
+ comment(matcher) {
213
+ return this.query("comment", matcher);
214
+ }
215
+ html(matcher) {
216
+ return this.query("html", matcher);
217
+ }
218
+ hr() {
219
+ return this.query("hr");
220
+ }
221
+ text() {
222
+ return this.matches.map((range) => this.source.slice(range.start, range.start + range.length)).join("");
223
+ }
224
+ nodes() {
225
+ return this.matches.map((range) => {
226
+ const token = range.token;
227
+ if (token.type !== "heading")
228
+ return { type: token.type, depth: null, text: this.tokenText(range.token) };
229
+ return { type: token.type, depth: token.depth, text: this.tokenText(range.token) };
230
+ });
231
+ }
232
+ rows() {
233
+ const results = [];
234
+ for (const range of this.matches) {
235
+ if (range.token.type !== "table")
236
+ continue;
237
+ const table = range.token;
238
+ const headers = table.header.map((cell) => cell.text);
239
+ for (const row of table.rows) {
240
+ const entry = {};
241
+ for (let i = 0;i < headers.length; i++) {
242
+ entry[headers[i]] = row[i]?.text ?? "";
243
+ }
244
+ results.push(entry);
245
+ }
246
+ }
247
+ return results;
248
+ }
249
+ entries() {
250
+ const entries = {};
251
+ for (const range of this.matches) {
252
+ for (const line of this.tokenText(range.token).split(`
253
+ `)) {
254
+ const separator = line.indexOf(":");
255
+ if (separator < 1)
256
+ continue;
257
+ const value = line.slice(separator + 1).trim();
258
+ if (value)
259
+ entries[line.slice(0, separator).trim().toLowerCase()] = value;
260
+ }
261
+ }
262
+ return entries;
263
+ }
264
+ frontmatter() {
265
+ return this.editor().frontmatter();
266
+ }
267
+ count() {
268
+ return this.matches.length;
269
+ }
270
+ exists() {
271
+ return this.matches.length > 0;
272
+ }
273
+ at(index) {
274
+ let resolved = index;
275
+ if (resolved < 0)
276
+ resolved = this.matches.length + resolved;
277
+ if (resolved < 0 || resolved >= this.matches.length)
278
+ return new MarkdownQuery(this.source, []);
279
+ return new MarkdownQuery(this.source, [this.matches[resolved]]);
280
+ }
281
+ slice(from, to) {
282
+ return new MarkdownQuery(this.source, this.matches.slice(from, to));
283
+ }
284
+ first() {
285
+ return this.slice(0, 1);
286
+ }
287
+ last() {
288
+ return this.slice(-1);
289
+ }
290
+ each() {
291
+ return this.matches.map((range) => new MarkdownQuery(this.source, [range]));
292
+ }
293
+ preceding() {
294
+ if (this.matches.length === 0)
295
+ return new MarkdownQuery(this.source, []);
296
+ const cutoff = this.matches[0].start;
297
+ return new MarkdownQuery(this.source, buildTokenIndex(this.source).filter((range) => range.start + range.length <= cutoff));
298
+ }
299
+ following() {
300
+ if (this.matches.length === 0)
301
+ return new MarkdownQuery(this.source, []);
302
+ const last = this.matches[this.matches.length - 1];
303
+ const cutoff = last.start + last.length;
304
+ return new MarkdownQuery(this.source, buildTokenIndex(this.source).filter((range) => range.start >= cutoff));
305
+ }
306
+ replace(content) {
307
+ return this.replaceEach(() => content);
308
+ }
309
+ replaceEach(replacer) {
310
+ return new MarkdownQuery(this.editor().replace(this.matches, (range, index) => String(replacer(new MarkdownQuery(this.source, [range]), index))));
311
+ }
312
+ remove() {
313
+ return new MarkdownQuery(this.editor().remove(this.matches));
314
+ }
315
+ insertBefore(markdown) {
316
+ return this.insertAll((range) => range.start, markdown);
317
+ }
318
+ insertAfter(markdown) {
319
+ return this.insertAll((range) => MarkdownEditor.blockEnd(range), markdown);
320
+ }
321
+ prepend(markdown) {
322
+ return this.insertAll((range) => this.containerStart(range), markdown);
323
+ }
324
+ append(markdown) {
325
+ return this.insertAll((range) => this.containerEnd(range), markdown);
326
+ }
327
+ addRow(row) {
328
+ return new MarkdownQuery(this.editor().replace(this.matches, (range) => {
329
+ if (range.token.type !== "table")
330
+ throw new MdqOperationError(`addRow needs a table, got ${range.token.type}`);
331
+ const table = range.token;
332
+ const headers = table.header.map((cell) => cell.text);
333
+ const existing = table.rows.map((cells) => headers.map((_, index) => cells[index]?.text || ""));
334
+ return MarkdownEditor.table(headers, [...existing, headers.map((header) => row[header] || "")], table.align);
335
+ }));
336
+ }
337
+ addItem(text) {
338
+ return new MarkdownQuery(this.editor().replace(this.matches, (range) => {
339
+ if (range.token.type !== "list")
340
+ throw new MdqOperationError(`addItem needs a list, got ${range.token.type}`);
341
+ const raw = (range.token.raw || "").trimEnd();
342
+ return `${raw}
343
+ ${MarkdownEditor.item(raw, text)}
344
+ `;
345
+ }));
346
+ }
347
+ setEntry(key, value) {
348
+ return this.replaceEach((match) => {
349
+ const token = match.matches[0].token;
350
+ return MarkdownEditor.entries(this.tokenText(token), key, value, token.type === "blockquote");
351
+ });
352
+ }
353
+ setFrontmatter(key, value) {
354
+ return new MarkdownQuery(this.editor().setFrontmatter(key, value));
355
+ }
356
+ toString() {
357
+ return this.source;
358
+ }
359
+ valueOf() {
360
+ return this.source;
361
+ }
362
+ get() {
363
+ return this.text();
364
+ }
365
+ meta() {
366
+ return this.nodes();
367
+ }
368
+ toJson() {
369
+ return this.rows();
370
+ }
371
+ keyValue() {
372
+ return this.entries();
373
+ }
374
+ setKeyValue(key, value) {
375
+ return this.setEntry(key, value);
376
+ }
377
+ before() {
378
+ return this.preceding();
379
+ }
380
+ after() {
381
+ return this.following();
382
+ }
383
+ editor() {
384
+ return new MarkdownEditor(this.source);
385
+ }
386
+ run(candidates, segments) {
387
+ if (segments.length === 0)
388
+ return candidates;
389
+ const segment = segments[0];
390
+ const remaining = segments.slice(1);
391
+ if (/^section[1-6]?$/.test(segment.selector)) {
392
+ const sections = this.narrow(this.sectionsOf(candidates, segment), segment);
393
+ if (remaining.length === 0)
394
+ return sections;
395
+ const results = [];
396
+ for (const section of sections)
397
+ results.push(...this.run(section.innerTokens || [], remaining));
398
+ return results;
399
+ }
400
+ if (segment.selector === "comment") {
401
+ const comments = candidates.filter((range) => this.isComment(range.token));
402
+ return this.run(this.narrow(this.filterByText(comments, segment), segment), remaining);
403
+ }
404
+ if (segment.selector === "item") {
405
+ return this.run(this.narrow(this.filterByText(this.itemsOf(candidates), segment), segment), remaining);
406
+ }
407
+ const depth = segment.selector.match(/^h([1-6])$/);
408
+ let type = TOKEN_ALIASES[segment.selector] || segment.selector;
409
+ if (depth)
410
+ type = "heading";
411
+ let matches = candidates.filter((range) => range.token.type === type);
412
+ if (depth)
413
+ matches = matches.filter((range) => range.token.depth === Number.parseInt(depth[1], 10));
414
+ return this.run(this.narrow(this.filterByText(matches, segment), segment), remaining);
415
+ }
416
+ sectionsOf(candidates, segment) {
417
+ const wanted = segment.selector.match(/^section([1-6])$/);
418
+ const sections = [];
419
+ for (let i = 0;i < candidates.length; i++) {
420
+ const range = candidates[i];
421
+ if (range.token.type !== "heading")
422
+ continue;
423
+ const heading = range.token;
424
+ if (wanted && heading.depth !== Number.parseInt(wanted[1], 10))
425
+ continue;
426
+ if (segment.textMatch && !this.matchText(heading.text, segment.textMatch))
427
+ continue;
428
+ const innerTokens = [];
429
+ let end = range.start + range.length;
430
+ for (let j = i + 1;j < candidates.length; j++) {
431
+ const next = candidates[j];
432
+ if (next.token.type === "heading" && next.token.depth <= heading.depth)
433
+ break;
434
+ innerTokens.push(next);
435
+ end = MarkdownEditor.blockEnd(next);
436
+ }
437
+ sections.push({ token: range.token, start: range.start, length: end - range.start, innerTokens });
438
+ }
439
+ return sections;
440
+ }
441
+ itemsOf(candidates) {
442
+ const items = [];
443
+ for (const range of candidates) {
444
+ if (range.token.type !== "list")
445
+ continue;
446
+ const raw = range.token.raw;
447
+ let cursor = 0;
448
+ for (const item of range.token.items) {
449
+ const itemRaw = item.raw;
450
+ const at = raw.indexOf(itemRaw, cursor);
451
+ if (at === -1)
452
+ continue;
453
+ items.push({ token: item, start: range.start + at, length: itemRaw.length });
454
+ cursor = at + itemRaw.length;
455
+ }
456
+ }
457
+ return items;
458
+ }
459
+ expandSections(matches) {
460
+ if (!matches.some((range) => range.innerTokens))
461
+ return matches;
462
+ const expanded = [];
463
+ for (const range of matches) {
464
+ if (!range.innerTokens) {
465
+ expanded.push(range);
466
+ continue;
467
+ }
468
+ expanded.push({ token: range.token, start: range.start, length: (range.token.raw || "").length });
469
+ expanded.push(...range.innerTokens);
470
+ }
471
+ return expanded;
472
+ }
473
+ narrow(matches, segment) {
474
+ if (segment.index !== null) {
475
+ let index = segment.index;
476
+ if (index < 0)
477
+ index = matches.length + index;
478
+ if (index < 0 || index >= matches.length)
479
+ return [];
480
+ return [matches[index]];
481
+ }
482
+ if (segment.slice)
483
+ return matches.slice(segment.slice.from, segment.slice.to);
484
+ return matches;
485
+ }
486
+ filterByText(matches, segment) {
487
+ if (!segment.textMatch)
488
+ return matches;
489
+ return matches.filter((range) => this.matchText(this.tokenText(range.token), segment.textMatch));
490
+ }
491
+ matchText(text, matcher) {
492
+ let result = false;
493
+ if (matcher.mode === "exact")
494
+ result = text === matcher.value;
495
+ if (matcher.mode === "contains")
496
+ result = text.includes(matcher.value);
497
+ if (matcher.mode === "regex")
498
+ result = new RegExp(matcher.value, matcher.flags || "").test(text);
499
+ if (matcher.mode === "predicate")
500
+ result = matcher.predicate(text);
501
+ if (matcher.negated)
502
+ return !result;
503
+ return result;
504
+ }
505
+ valueMatcher(matcher) {
506
+ if (typeof matcher === "function")
507
+ return { mode: "predicate", value: "", negated: false, predicate: matcher };
508
+ if (matcher instanceof RegExp)
509
+ return { mode: "regex", value: matcher.source, negated: false, flags: matcher.flags };
510
+ return { mode: "exact", value: matcher, negated: false };
511
+ }
512
+ tokenText(token) {
513
+ const value = token;
514
+ if (this.isComment(token))
515
+ return value.raw.trim().replace(/^<!--/, "").replace(/-->$/, "").trim();
516
+ if (token.type === "html")
517
+ return value.raw || "";
518
+ if (token.type === "table")
519
+ return [...(value.header || []).map((cell) => cell.text), ...(value.rows || []).flatMap((row) => row.map((cell) => cell.text))].join(", ");
520
+ return value.text || "";
521
+ }
522
+ isComment(token) {
523
+ if (token.type !== "html")
524
+ return false;
525
+ return (token.raw || "").trimStart().startsWith("<!--");
526
+ }
527
+ insertAll(offsetOf, markdown) {
528
+ return new MarkdownQuery(this.editor().insert(this.matches.map(offsetOf), String(markdown)));
529
+ }
530
+ containerStart(range) {
531
+ if (!range.innerTokens)
532
+ throw new MdqOperationError(`prepend needs a section or list, got ${range.token.type}`);
533
+ return range.start + (range.token.raw || "").length;
534
+ }
535
+ containerEnd(range) {
536
+ if (!range.innerTokens)
537
+ throw new MdqOperationError(`append needs a section or list, got ${range.token.type}`);
538
+ const last = range.innerTokens[range.innerTokens.length - 1];
539
+ if (!last)
540
+ return this.containerStart(range);
541
+ return MarkdownEditor.blockEnd(last);
542
+ }
543
+ }
544
+
545
+ class MdqError extends Error {
546
+ }
547
+
548
+ class MdqSelectorError extends MdqError {
549
+ index;
550
+ constructor(message, index) {
551
+ super(message);
552
+ this.name = "MdqSelectorError";
553
+ this.index = index;
554
+ }
555
+ }
556
+
557
+ class MdqOperationError extends MdqError {
558
+ constructor(message) {
559
+ super(message);
560
+ this.name = "MdqOperationError";
561
+ }
562
+ }
563
+ function mdq(source) {
564
+ return new MarkdownQuery(String(source));
565
+ }
566
+ function parseQuery(input) {
567
+ const segments = [];
568
+ SEGMENT.lastIndex = 0;
569
+ while (SEGMENT.lastIndex < input.length) {
570
+ const at = SEGMENT.lastIndex;
571
+ const match = SEGMENT.exec(input);
572
+ if (!match)
573
+ throw new MdqSelectorError(`Unexpected character "${input[at]}" in selector`, at);
574
+ const [, prefix, name, negated, contains, quoted, pattern, flags, brackets] = match;
575
+ if (!SELECTOR_NAMES.has(name) && !/^(?:h|section)[1-6]$/.test(name))
576
+ throw new MdqSelectorError(`Unknown selector "${name}"`, at + prefix.length);
577
+ const segment = { selector: name, index: null, slice: null };
578
+ if (quoted !== undefined) {
579
+ let mode = "exact";
580
+ if (contains)
581
+ mode = "contains";
582
+ segment.textMatch = { mode, value: quoted.replace(/\\(.)/g, "$1"), negated: Boolean(negated) };
583
+ }
584
+ if (pattern !== undefined) {
585
+ segment.textMatch = { mode: "regex", value: pattern, negated: Boolean(negated), flags };
586
+ }
587
+ for (const [, bounds] of brackets.matchAll(/\[([^\]]*)\]/g)) {
588
+ if (!bounds || !/^-?\d*(?::-?\d*)?$/.test(bounds))
589
+ continue;
590
+ if (!bounds.includes(":")) {
591
+ segment.index = Number.parseInt(bounds, 10);
592
+ continue;
593
+ }
594
+ const [from, to] = bounds.split(":");
595
+ segment.slice = {};
596
+ if (from)
597
+ segment.slice.from = Number.parseInt(from, 10);
598
+ if (to)
599
+ segment.slice.to = Number.parseInt(to, 10);
600
+ }
601
+ segments.push(segment);
602
+ }
603
+ return segments;
604
+ }
605
+ function buildTokenIndex(source) {
606
+ const { body, offset } = MarkdownEditor.splitFrontmatter(source);
607
+ const ranges = [];
608
+ let cursor = offset;
609
+ for (const token of marked.lexer(body)) {
610
+ const raw = token.raw || "";
611
+ if (token.type === "space") {
612
+ const previous = ranges[ranges.length - 1];
613
+ if (previous)
614
+ previous.trailing = { start: cursor, length: raw.length };
615
+ cursor += raw.length;
616
+ continue;
617
+ }
618
+ ranges.push({ token, start: cursor, length: raw.length });
619
+ cursor += raw.length;
620
+ }
621
+ return ranges;
622
+ }
623
+ var SELECTOR_NAMES = new Set(["section", "heading", "paragraph", "table", "list", "item", "code", "blockquote", "hr", "html", "comment"]);
624
+ var TOKEN_ALIASES = { item: "list_item" };
625
+ var SEGMENT = /(\s*\.?)([A-Za-z]\w*)(?:\((!?)(~?)(?:"((?:[^"\\]|\\.)*)"|\/((?:[^/\\]|\\.)*)\/([a-z]*))\))?((?:\[[^\]]*\])*)\s*/y;
626
+ export {
627
+ parseQuery,
628
+ mdq,
629
+ buildTokenIndex,
630
+ MdqSelectorError,
631
+ MdqOperationError,
632
+ MdqError,
633
+ MarkdownQuery,
634
+ MarkdownEditor
635
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "mdq-cli",
3
+ "version": "0.1.0",
4
+ "description": "Query and edit markdown with a selector language - jq, for markdown",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./index.js",
8
+ "types": "./types/query.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./types/query.d.ts",
12
+ "import": "./index.js"
13
+ }
14
+ },
15
+ "bin": {
16
+ "mdq": "./bin/mdq.js"
17
+ },
18
+ "files": [
19
+ "index.js",
20
+ "types/",
21
+ "bin/",
22
+ "README.md"
23
+ ],
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "keywords": [
28
+ "markdown",
29
+ "query",
30
+ "selector",
31
+ "cli",
32
+ "jq",
33
+ "frontmatter",
34
+ "marked",
35
+ "edit"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/testomatio/explorbot",
40
+ "directory": "src/utils/mdq"
41
+ },
42
+ "dependencies": {
43
+ "marked": "^16.2.0",
44
+ "yaml": "^2.8.3",
45
+ "commander": "^14.0.1"
46
+ }
47
+ }
@@ -0,0 +1,24 @@
1
+ import type { MatchedRange } from './query.ts';
2
+ export declare class MarkdownEditor {
3
+ private source;
4
+ constructor(source: string);
5
+ static splitFrontmatter(source: string): FrontmatterSplit;
6
+ static blockEnd(range: MatchedRange): number;
7
+ static table(headers: string[], rows: string[][], align: (string | null)[]): string;
8
+ static item(listRaw: string, text: string): string;
9
+ static entries(text: string, key: string, value: string | null, isBlockquote: boolean): string;
10
+ remove(ranges: MatchedRange[]): string;
11
+ replace(ranges: MatchedRange[], render: (range: MatchedRange, index: number) => string): string;
12
+ insert(offsets: number[], markdown: string): string;
13
+ frontmatter(): Record<string, unknown>;
14
+ setFrontmatter(key: string, value: unknown): string;
15
+ private static spliceIn;
16
+ private static dashes;
17
+ private static entryKey;
18
+ private dedupe;
19
+ }
20
+ export interface FrontmatterSplit {
21
+ raw: string;
22
+ body: string;
23
+ offset: number;
24
+ }