lilmd 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/dist/mdq.js ADDED
@@ -0,0 +1,735 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+
5
+ // src/cli.ts
6
+ import { parseArgs } from "node:util";
7
+ import { readFileSync } from "node:fs";
8
+
9
+ // src/scan.ts
10
+ function scan(src) {
11
+ const out = [];
12
+ const len = src.length;
13
+ let i = 0;
14
+ let lineNo = 0;
15
+ let inFence = false;
16
+ let fenceChar = 0;
17
+ let fenceLen = 0;
18
+ while (i <= len) {
19
+ const start = i;
20
+ while (i < len && src.charCodeAt(i) !== 10)
21
+ i++;
22
+ let line = src.slice(start, i);
23
+ if (line.length > 0 && line.charCodeAt(line.length - 1) === 13) {
24
+ line = line.slice(0, line.length - 1);
25
+ }
26
+ lineNo++;
27
+ const fence = matchFence(line);
28
+ if (fence) {
29
+ if (!inFence) {
30
+ inFence = true;
31
+ fenceChar = fence.char;
32
+ fenceLen = fence.len;
33
+ } else if (fence.char === fenceChar && fence.len >= fenceLen) {
34
+ inFence = false;
35
+ }
36
+ } else if (!inFence) {
37
+ const h = matchHeading(line, lineNo);
38
+ if (h)
39
+ out.push(h);
40
+ }
41
+ if (i >= len)
42
+ break;
43
+ i++;
44
+ }
45
+ return out;
46
+ }
47
+ function matchFence(line) {
48
+ let p = 0;
49
+ while (p < 3 && line.charCodeAt(p) === 32)
50
+ p++;
51
+ const ch = line.charCodeAt(p);
52
+ if (ch !== 96 && ch !== 126)
53
+ return null;
54
+ let run = 0;
55
+ while (line.charCodeAt(p + run) === ch)
56
+ run++;
57
+ if (run < 3)
58
+ return null;
59
+ return { char: ch, len: run };
60
+ }
61
+ function matchHeading(line, lineNo) {
62
+ let p = 0;
63
+ while (p < 3 && line.charCodeAt(p) === 32)
64
+ p++;
65
+ if (line.charCodeAt(p) !== 35)
66
+ return null;
67
+ let hashes = 0;
68
+ while (line.charCodeAt(p + hashes) === 35)
69
+ hashes++;
70
+ if (hashes < 1 || hashes > 6)
71
+ return null;
72
+ const after = p + hashes;
73
+ const afterCh = line.charCodeAt(after);
74
+ if (after < line.length && afterCh !== 32 && afterCh !== 9) {
75
+ return null;
76
+ }
77
+ let contentStart = after;
78
+ while (contentStart < line.length && (line.charCodeAt(contentStart) === 32 || line.charCodeAt(contentStart) === 9)) {
79
+ contentStart++;
80
+ }
81
+ let end = line.length;
82
+ while (end > contentStart && (line.charCodeAt(end - 1) === 32 || line.charCodeAt(end - 1) === 9)) {
83
+ end--;
84
+ }
85
+ let closing = end;
86
+ while (closing > contentStart && line.charCodeAt(closing - 1) === 35)
87
+ closing--;
88
+ if (closing < end && (closing === contentStart || line.charCodeAt(closing - 1) === 32 || line.charCodeAt(closing - 1) === 9)) {
89
+ end = closing;
90
+ while (end > contentStart && (line.charCodeAt(end - 1) === 32 || line.charCodeAt(end - 1) === 9)) {
91
+ end--;
92
+ }
93
+ }
94
+ const title = line.slice(contentStart, end);
95
+ return { level: hashes, title, line: lineNo };
96
+ }
97
+
98
+ // src/sections.ts
99
+ function buildSections(headings, totalLines) {
100
+ const out = [];
101
+ const stack = [];
102
+ for (const h of headings) {
103
+ while (stack.length > 0 && stack[stack.length - 1].level >= h.level) {
104
+ const closing = stack.pop();
105
+ closing.line_end = h.line - 1;
106
+ }
107
+ const parent = stack.length > 0 ? stack[stack.length - 1] : null;
108
+ const sec = {
109
+ level: h.level,
110
+ title: h.title,
111
+ line_start: h.line,
112
+ line_end: totalLines,
113
+ parent
114
+ };
115
+ out.push(sec);
116
+ stack.push(sec);
117
+ }
118
+ return out;
119
+ }
120
+ function pathOf(sec) {
121
+ const path = [];
122
+ let cur = sec.parent;
123
+ while (cur) {
124
+ path.push(cur.title);
125
+ cur = cur.parent;
126
+ }
127
+ return path.reverse();
128
+ }
129
+ function countLines(src) {
130
+ if (src.length === 0)
131
+ return 0;
132
+ let n = 1;
133
+ for (let i = 0;i < src.length; i++) {
134
+ if (src.charCodeAt(i) === 10)
135
+ n++;
136
+ }
137
+ if (src.charCodeAt(src.length - 1) === 10)
138
+ n--;
139
+ return n;
140
+ }
141
+
142
+ // src/select.ts
143
+ function parseSelector(input) {
144
+ const trimmed = input.trim();
145
+ if (trimmed.length === 0)
146
+ return [];
147
+ const rawSegments = [];
148
+ const ops = ["descendant"];
149
+ let cur = "";
150
+ let i = 0;
151
+ let inRegex = false;
152
+ let atSegmentStart = true;
153
+ while (i < trimmed.length) {
154
+ const ch = trimmed[i];
155
+ if (ch === "/" && (atSegmentStart || inRegex)) {
156
+ inRegex = !inRegex;
157
+ cur += ch;
158
+ atSegmentStart = false;
159
+ i++;
160
+ continue;
161
+ }
162
+ if (!inRegex && ch === ">") {
163
+ rawSegments.push(cur.trim());
164
+ cur = "";
165
+ atSegmentStart = true;
166
+ if (trimmed[i + 1] === ">") {
167
+ ops.push("child");
168
+ i += 2;
169
+ } else {
170
+ ops.push("descendant");
171
+ i += 1;
172
+ }
173
+ continue;
174
+ }
175
+ cur += ch;
176
+ if (ch !== " " && ch !== "\t")
177
+ atSegmentStart = false;
178
+ i++;
179
+ }
180
+ rawSegments.push(cur.trim());
181
+ return rawSegments.map((s, idx) => parseSegment(s, ops[idx] ?? "descendant"));
182
+ }
183
+ function parseSegment(raw, op) {
184
+ let s = raw;
185
+ let level = null;
186
+ const levelMatch = /^(#{1,6})(?!#)\s*(.*)$/.exec(s);
187
+ if (levelMatch) {
188
+ level = levelMatch[1].length;
189
+ s = levelMatch[2] ?? "";
190
+ }
191
+ const regexMatch = /^\/(.+)\/([gimsuy]*)$/.exec(s);
192
+ if (regexMatch) {
193
+ const pattern = regexMatch[1];
194
+ const flags = regexMatch[2] || "i";
195
+ return {
196
+ op,
197
+ level,
198
+ kind: "regex",
199
+ value: pattern,
200
+ regex: new RegExp(pattern, flags)
201
+ };
202
+ }
203
+ if (s.startsWith("=")) {
204
+ return { op, level, kind: "exact", value: s.slice(1).trim() };
205
+ }
206
+ return { op, level, kind: "fuzzy", value: s.trim() };
207
+ }
208
+ function match(sections, selector) {
209
+ if (selector.length === 0)
210
+ return [];
211
+ const out = [];
212
+ for (const sec of sections) {
213
+ if (matches(sec, selector))
214
+ out.push(sec);
215
+ }
216
+ return out;
217
+ }
218
+ function matches(sec, segs) {
219
+ const last = segs[segs.length - 1];
220
+ if (!last || !segmentMatchesSection(last, sec))
221
+ return false;
222
+ let cursor = sec.parent;
223
+ for (let i = segs.length - 2;i >= 0; i--) {
224
+ const op = segs[i + 1].op;
225
+ const seg = segs[i];
226
+ if (op === "child") {
227
+ if (!cursor || !segmentMatchesSection(seg, cursor))
228
+ return false;
229
+ cursor = cursor.parent;
230
+ } else {
231
+ let found = null;
232
+ while (cursor) {
233
+ if (segmentMatchesSection(seg, cursor)) {
234
+ found = cursor;
235
+ break;
236
+ }
237
+ cursor = cursor.parent;
238
+ }
239
+ if (!found)
240
+ return false;
241
+ cursor = found.parent;
242
+ }
243
+ }
244
+ return true;
245
+ }
246
+ function segmentMatchesSection(seg, sec) {
247
+ if (seg.level !== null && seg.level !== sec.level)
248
+ return false;
249
+ const title = sec.title;
250
+ switch (seg.kind) {
251
+ case "exact":
252
+ return title.toLowerCase() === seg.value.toLowerCase();
253
+ case "regex":
254
+ return seg.regex.test(title);
255
+ case "fuzzy":
256
+ return title.toLowerCase().includes(seg.value.toLowerCase());
257
+ }
258
+ }
259
+
260
+ // src/render.ts
261
+ function renderToc(file, src, sections, opts) {
262
+ const totalLines = countLines(src);
263
+ const headerCount = sections.length;
264
+ const headerRange = totalLines === 0 ? "L0" : `L1-${totalLines}`;
265
+ const plural = headerCount === 1 ? "heading" : "headings";
266
+ const out = [];
267
+ out.push(`${file} ${headerRange} ${headerCount} ${plural}`);
268
+ for (const sec of sections) {
269
+ if (opts.depth != null && sec.level > opts.depth)
270
+ continue;
271
+ const indent = opts.flat ? "" : " ".repeat(Math.max(0, sec.level - 1));
272
+ const hashes = "#".repeat(sec.level);
273
+ const range = `L${sec.line_start}-${sec.line_end}`;
274
+ out.push(`${indent}${hashes} ${sec.title} ${range}`);
275
+ }
276
+ return out.join(`
277
+ `);
278
+ }
279
+ function renderSection(file, srcLines, sec, opts) {
280
+ const start = sec.line_start;
281
+ let end = sec.line_end;
282
+ if (opts.bodyOnly && opts.allSections) {
283
+ const firstChild = findFirstChild(sec, opts.allSections);
284
+ if (firstChild)
285
+ end = firstChild.line_start - 1;
286
+ }
287
+ if (opts.noBody) {
288
+ end = start;
289
+ }
290
+ const clampedEnd = Math.min(end, srcLines.length);
291
+ let body = srcLines.slice(start - 1, clampedEnd).join(`
292
+ `);
293
+ if (opts.maxLines != null && opts.maxLines > 0) {
294
+ body = truncateBody(body, opts.maxLines);
295
+ }
296
+ if (opts.pretty) {
297
+ body = opts.pretty(body);
298
+ }
299
+ if (opts.raw)
300
+ return body;
301
+ const hashes = "#".repeat(sec.level);
302
+ const header = `── ${file} L${start}-${end} ${hashes} ${sec.title} ${"─".repeat(8)}`;
303
+ const footer = `── end ${"─".repeat(40)}`;
304
+ return `${header}
305
+ ${body}
306
+ ${footer}`;
307
+ }
308
+ function truncateBody(body, maxLines) {
309
+ if (maxLines <= 0)
310
+ return body;
311
+ const lines = body.split(`
312
+ `);
313
+ if (lines.length <= maxLines)
314
+ return body;
315
+ const kept = lines.slice(0, maxLines).join(`
316
+ `);
317
+ const remaining = lines.length - maxLines;
318
+ return `${kept}
319
+
320
+ … ${remaining} more lines (use --max-lines=0 for full)`;
321
+ }
322
+ function findFirstChild(sec, all) {
323
+ for (const candidate of all) {
324
+ if (candidate.parent === sec)
325
+ return candidate;
326
+ }
327
+ return null;
328
+ }
329
+
330
+ // src/pretty.ts
331
+ var formatterPromise = null;
332
+ function loadPrettyFormatter() {
333
+ return formatterPromise ??= buildFormatter();
334
+ }
335
+ async function buildFormatter() {
336
+ const [{ marked }, { markedTerminal }] = await Promise.all([
337
+ import("marked"),
338
+ import("marked-terminal")
339
+ ]);
340
+ marked.use(markedTerminal({
341
+ reflowText: false,
342
+ tab: 2,
343
+ hr: "─"
344
+ }));
345
+ return (md) => {
346
+ const originalError = console.error;
347
+ console.error = (...args) => {
348
+ if (typeof args[0] === "string" && /Could not find the language/i.test(args[0]))
349
+ return;
350
+ originalError.apply(console, args);
351
+ };
352
+ let rendered;
353
+ try {
354
+ rendered = marked.parse(md);
355
+ } finally {
356
+ console.error = originalError;
357
+ }
358
+ if (typeof rendered !== "string") {
359
+ throw new Error("mdq: pretty renderer returned a Promise unexpectedly");
360
+ }
361
+ return rendered.replace(/\n+$/, "");
362
+ };
363
+ }
364
+
365
+ // src/cli.ts
366
+ var HELP = `mdq — CLI for working with large Markdown files
367
+
368
+ Usage:
369
+ mdq show this help
370
+ mdq <file> print table of contents
371
+ mdq <file> <selector> alias for 'mdq read'
372
+ mdq read <file> <selector> print sections matching selector
373
+ mdq ls <file> <selector> list direct child headings
374
+ mdq grep <file> <pattern> regex-search section bodies
375
+
376
+ Selector grammar:
377
+ Install fuzzy, case-insensitive substring
378
+ =Install exact, case-insensitive equality
379
+ /^inst/i regex (JS syntax); flags default to 'i'
380
+ ##Install level filter (1..6 '#'s)
381
+ Guide > Install descendant, any depth under 'Guide'
382
+ Guide >> Install direct child of 'Guide'
383
+
384
+ Options:
385
+ --depth <n> TOC: max heading depth to show (0 = none)
386
+ --flat TOC: flat list, no indentation
387
+ --max-results <n> cap matches for read/ls (default 25)
388
+ --max-lines <n> truncate long bodies (0 = unlimited)
389
+ --body-only read: skip subsections
390
+ --no-body read: print headings only
391
+ --raw read: drop delimiter lines
392
+ --pretty read: render markdown with ANSI styling (for humans)
393
+ --json machine-readable JSON output
394
+
395
+ Use '-' as <file> to read from stdin. Exit code is 1 when no matches.
396
+ `;
397
+ function ok(s) {
398
+ return { code: 0, stdout: s, stderr: "" };
399
+ }
400
+ function noMatch(s) {
401
+ return { code: 1, stdout: s, stderr: "" };
402
+ }
403
+ function err(s, code = 1) {
404
+ return { code, stdout: "", stderr: s };
405
+ }
406
+ var OPTIONS = {
407
+ depth: { type: "string" },
408
+ flat: { type: "boolean" },
409
+ "max-results": { type: "string" },
410
+ "max-lines": { type: "string" },
411
+ "body-only": { type: "boolean" },
412
+ "no-body": { type: "boolean" },
413
+ raw: { type: "boolean" },
414
+ pretty: { type: "boolean" },
415
+ json: { type: "boolean" },
416
+ help: { type: "boolean", short: "h" }
417
+ };
418
+ async function run(argv) {
419
+ let parsed;
420
+ try {
421
+ parsed = parseArgs({
422
+ args: argv,
423
+ options: OPTIONS,
424
+ allowPositionals: true,
425
+ strict: true
426
+ });
427
+ } catch (e) {
428
+ return err(`mdq: ${e.message}
429
+ ${HELP}`, 2);
430
+ }
431
+ const { values, positionals } = parsed;
432
+ if (values.help || positionals.length === 0) {
433
+ return ok(HELP);
434
+ }
435
+ const head = positionals[0];
436
+ if (head === "read" || head === "ls" || head === "grep" || head === "toc") {
437
+ return dispatch(head, positionals.slice(1), values);
438
+ }
439
+ if (positionals.length === 1)
440
+ return dispatch("toc", positionals, values);
441
+ return dispatch("read", positionals, values);
442
+ }
443
+ async function dispatch(cmd, rest, values) {
444
+ switch (cmd) {
445
+ case "toc":
446
+ return cmdToc(rest, values);
447
+ case "read":
448
+ return cmdRead(rest, values);
449
+ case "ls":
450
+ return cmdLs(rest, values);
451
+ case "grep":
452
+ return cmdGrep(rest, values);
453
+ default:
454
+ return err(`mdq: unknown command '${cmd}'
455
+ ${HELP}`, 2);
456
+ }
457
+ }
458
+ function loadFile(file) {
459
+ try {
460
+ const src = file === "-" ? readFileSync(0, "utf8") : readFileSync(file, "utf8");
461
+ return { src };
462
+ } catch (e) {
463
+ const msg = e.code === "ENOENT" ? `mdq: cannot open '${file}': not found
464
+ ` : `mdq: cannot open '${file}': ${e.message}
465
+ `;
466
+ return err(msg, 2);
467
+ }
468
+ }
469
+ function parseIntOrNull(v) {
470
+ if (v == null)
471
+ return null;
472
+ const n = Number.parseInt(v, 10);
473
+ if (Number.isNaN(n) || String(n) !== v.trim())
474
+ return null;
475
+ return n;
476
+ }
477
+ function readFlag(v, name, fallback) {
478
+ const raw = v[name];
479
+ if (raw == null)
480
+ return { value: fallback };
481
+ const n = parseIntOrNull(raw);
482
+ if (n == null || n < 0) {
483
+ return err(`mdq: --${name} expects a non-negative integer, got '${raw}'
484
+ `, 2);
485
+ }
486
+ return { value: n };
487
+ }
488
+ function cmdToc(rest, v) {
489
+ const file = rest[0];
490
+ if (file == null)
491
+ return err(`mdq toc: missing <file>
492
+ `, 2);
493
+ const loaded = loadFile(file);
494
+ if ("code" in loaded)
495
+ return loaded;
496
+ const { src } = loaded;
497
+ const sections = buildSections(scan(src), countLines(src));
498
+ if (v.json) {
499
+ return ok(JSON.stringify({
500
+ file,
501
+ total_lines: countLines(src),
502
+ headings: sections.map(sectionToJSON)
503
+ }, null, 2));
504
+ }
505
+ const depth = readFlag(v, "depth", null);
506
+ if ("code" in depth)
507
+ return depth;
508
+ return ok(renderToc(file, src, sections, {
509
+ depth: depth.value ?? undefined,
510
+ flat: !!v.flat
511
+ }));
512
+ }
513
+ async function cmdRead(rest, v) {
514
+ const file = rest[0];
515
+ const selectorStr = rest[1];
516
+ if (file == null || selectorStr == null) {
517
+ return err(`mdq read: missing <file> or <selector>
518
+ `, 2);
519
+ }
520
+ if (v.pretty && v.json) {
521
+ return err(`mdq read: --pretty cannot be combined with --json
522
+ `, 2);
523
+ }
524
+ const loaded = loadFile(file);
525
+ if ("code" in loaded)
526
+ return loaded;
527
+ const { src } = loaded;
528
+ const sections = buildSections(scan(src), countLines(src));
529
+ const maxResults = readFlag(v, "max-results", 25);
530
+ if ("code" in maxResults)
531
+ return maxResults;
532
+ const maxLines = readFlag(v, "max-lines", 0);
533
+ if ("code" in maxLines)
534
+ return maxLines;
535
+ const selector = parseSelector(selectorStr);
536
+ const matches2 = match(sections, selector);
537
+ const srcLines = src.split(`
538
+ `);
539
+ if (v.json) {
540
+ return emitReadJson(file, srcLines, sections, matches2, maxResults.value ?? 25, maxLines.value ?? 0, v);
541
+ }
542
+ if (matches2.length === 0)
543
+ return noMatch(`(no match)
544
+ `);
545
+ let pretty;
546
+ if (v.pretty) {
547
+ try {
548
+ pretty = await loadPrettyFormatter();
549
+ } catch (e) {
550
+ return err(`mdq read: ${e.message}
551
+ `, 2);
552
+ }
553
+ }
554
+ const cap = maxResults.value ?? 25;
555
+ const toPrint = matches2.slice(0, cap);
556
+ const out = [];
557
+ if (matches2.length > cap) {
558
+ out.push(`${matches2.length} matches, showing first ${cap}. Use --max-results=N to raise the cap.`);
559
+ }
560
+ for (const sec of toPrint) {
561
+ out.push(renderSection(file, srcLines, sec, {
562
+ bodyOnly: !!v["body-only"],
563
+ noBody: !!v["no-body"],
564
+ raw: !!v.raw,
565
+ maxLines: maxLines.value ?? 0,
566
+ allSections: sections,
567
+ pretty
568
+ }));
569
+ }
570
+ return ok(out.join(`
571
+ `));
572
+ }
573
+ function emitReadJson(file, srcLines, all, matches2, maxResults, maxLines, v) {
574
+ const body = JSON.stringify({
575
+ file,
576
+ matches: matches2.slice(0, maxResults).map((s) => ({
577
+ ...sectionToJSON(s),
578
+ body: v["no-body"] ? "" : maybeTruncate(sliceBody(srcLines, s, all, !!v["body-only"]), maxLines)
579
+ })),
580
+ truncated: matches2.length > maxResults
581
+ }, null, 2);
582
+ return matches2.length === 0 ? { code: 1, stdout: body, stderr: "" } : ok(body);
583
+ }
584
+ function cmdLs(rest, v) {
585
+ const file = rest[0];
586
+ const selectorStr = rest[1];
587
+ if (file == null || selectorStr == null) {
588
+ return err(`mdq ls: missing <file> or <selector>
589
+ `, 2);
590
+ }
591
+ const loaded = loadFile(file);
592
+ if ("code" in loaded)
593
+ return loaded;
594
+ const { src } = loaded;
595
+ const sections = buildSections(scan(src), countLines(src));
596
+ const maxResults = readFlag(v, "max-results", 25);
597
+ if ("code" in maxResults)
598
+ return maxResults;
599
+ const cap = maxResults.value ?? 25;
600
+ const selector = parseSelector(selectorStr);
601
+ const matches2 = match(sections, selector).slice(0, cap);
602
+ const childrenOf = new Map;
603
+ for (const sec of sections) {
604
+ if (sec.parent) {
605
+ const list = childrenOf.get(sec.parent);
606
+ if (list)
607
+ list.push(sec);
608
+ else
609
+ childrenOf.set(sec.parent, [sec]);
610
+ }
611
+ }
612
+ if (v.json) {
613
+ const results = matches2.map((parent) => ({
614
+ parent: sectionToJSON(parent),
615
+ children: (childrenOf.get(parent) ?? []).map(sectionToJSON)
616
+ }));
617
+ const body = JSON.stringify({ file, results }, null, 2);
618
+ return matches2.length === 0 ? { code: 1, stdout: body, stderr: "" } : ok(body);
619
+ }
620
+ if (matches2.length === 0)
621
+ return noMatch(`(no match)
622
+ `);
623
+ const out = [];
624
+ for (const parent of matches2) {
625
+ const children = childrenOf.get(parent) ?? [];
626
+ out.push(`${"#".repeat(parent.level)} ${parent.title} L${parent.line_start}-${parent.line_end}`);
627
+ if (children.length === 0) {
628
+ out.push(" (no children)");
629
+ } else {
630
+ for (const c of children) {
631
+ out.push(` ${"#".repeat(c.level)} ${c.title} L${c.line_start}-${c.line_end}`);
632
+ }
633
+ }
634
+ }
635
+ return ok(out.join(`
636
+ `));
637
+ }
638
+ function cmdGrep(rest, v) {
639
+ const file = rest[0];
640
+ const pattern = rest[1];
641
+ if (file == null || pattern == null) {
642
+ return err(`mdq grep: missing <file> or <pattern>
643
+ `, 2);
644
+ }
645
+ const loaded = loadFile(file);
646
+ if ("code" in loaded)
647
+ return loaded;
648
+ const { src } = loaded;
649
+ const sections = buildSections(scan(src), countLines(src));
650
+ let re;
651
+ try {
652
+ re = new RegExp(pattern);
653
+ } catch (e) {
654
+ return err(`mdq grep: invalid regex: ${e.message}
655
+ `, 2);
656
+ }
657
+ const srcLines = src.split(`
658
+ `);
659
+ const hits = [];
660
+ let secIdx = -1;
661
+ for (let lineNo = 1;lineNo <= srcLines.length; lineNo++) {
662
+ while (secIdx + 1 < sections.length && sections[secIdx + 1].line_start <= lineNo) {
663
+ secIdx++;
664
+ }
665
+ const line = srcLines[lineNo - 1];
666
+ if (re.test(line)) {
667
+ const section = secIdx >= 0 ? sections[secIdx] ?? null : null;
668
+ hits.push({ section, line: lineNo, text: line });
669
+ }
670
+ }
671
+ if (v.json) {
672
+ const body = JSON.stringify(hits.map((h) => ({
673
+ file,
674
+ line: h.line,
675
+ text: h.text,
676
+ section: h.section ? sectionToJSON(h.section) : null
677
+ })), null, 2);
678
+ return hits.length === 0 ? { code: 1, stdout: body, stderr: "" } : ok(body);
679
+ }
680
+ if (hits.length === 0)
681
+ return noMatch(`(no match)
682
+ `);
683
+ const out = [];
684
+ let lastSection = undefined;
685
+ for (const hit of hits) {
686
+ if (hit.section !== lastSection) {
687
+ if (hit.section) {
688
+ const path = pathOf(hit.section).concat(hit.section.title).join(" > ");
689
+ out.push(`── ${path} L${hit.section.line_start}-${hit.section.line_end}`);
690
+ } else {
691
+ out.push(`── ${file} (no enclosing heading)`);
692
+ }
693
+ lastSection = hit.section;
694
+ }
695
+ out.push(` L${hit.line}: ${hit.text}`);
696
+ }
697
+ return ok(out.join(`
698
+ `));
699
+ }
700
+ function sliceBody(srcLines, sec, all, bodyOnly) {
701
+ let end = sec.line_end;
702
+ if (bodyOnly) {
703
+ const firstChild = all.find((s) => s.parent === sec);
704
+ if (firstChild)
705
+ end = firstChild.line_start - 1;
706
+ }
707
+ return srcLines.slice(sec.line_start - 1, end).join(`
708
+ `);
709
+ }
710
+ function maybeTruncate(body, maxLines) {
711
+ return maxLines > 0 ? truncateBody(body, maxLines) : body;
712
+ }
713
+ function sectionToJSON(sec) {
714
+ return {
715
+ level: sec.level,
716
+ title: sec.title,
717
+ line_start: sec.line_start,
718
+ line_end: sec.line_end,
719
+ path: pathOf(sec)
720
+ };
721
+ }
722
+
723
+ // bin/mdq.ts
724
+ var result = await run(process.argv.slice(2));
725
+ if (result.stdout) {
726
+ process.stdout.write(result.stdout.endsWith(`
727
+ `) ? result.stdout : result.stdout + `
728
+ `);
729
+ }
730
+ if (result.stderr)
731
+ process.stderr.write(result.stderr);
732
+ process.exit(result.code);
733
+
734
+ //# debugId=21A3DDC69C33B5A064756E2164756E21
735
+ //# sourceMappingURL=mdq.js.map