@hawkeyexl/manni 2.9.0 → 2.10.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/README.md CHANGED
@@ -98,7 +98,7 @@ actually reads rather than hand-written:
98
98
  ```yaml
99
99
  repos:
100
100
  - repo: https://github.com/hawkeyexl/manni
101
- rev: v2.9.0
101
+ rev: v2.10.0
102
102
  hooks:
103
103
  - id: manni-meta
104
104
  ```
@@ -75,7 +75,7 @@ import {
75
75
  withValueAt,
76
76
  writeEncryptionKey,
77
77
  writeFileAtomic
78
- } from "./chunk-5A3XPOTD.js";
78
+ } from "./chunk-ROAPSWWB.js";
79
79
 
80
80
  // src/meta/core/reencrypt.ts
81
81
  import { extname } from "node:path";
@@ -835,11 +835,34 @@ function nextLineStart(content, offset) {
835
835
  if (offset === 0 || content.charCodeAt(offset - 1) === 10) return offset;
836
836
  return lineEnd(content, offset) + 1;
837
837
  }
838
+ var MAX_IDS_PER_MARKER = 25;
838
839
  function payloadOf(payload) {
839
840
  if (payload === "") return { kind: "bad", reason: "empty payload" };
840
- if (payload.startsWith("{")) return { kind: "bad", reason: "a JSON payload", json: true };
841
- if (ID.test(payload)) return { kind: "ref", id: payload };
842
- return { kind: "bad", reason: "payload is not an id" };
841
+ if (/[\r\n]/.test(payload)) {
842
+ return { kind: "bad", reason: "a marker is one line; write two markers" };
843
+ }
844
+ const [head, ...rest] = payload.split(" ").filter((word) => word !== "");
845
+ if (head === void 0) return { kind: "bad", reason: "empty payload" };
846
+ const words = [head, ...rest];
847
+ if (words.some((word) => word.startsWith("{"))) {
848
+ return { kind: "bad", reason: "a JSON payload", json: true };
849
+ }
850
+ if (words.length > MAX_IDS_PER_MARKER) {
851
+ return {
852
+ kind: "bad",
853
+ reason: `more than ${String(MAX_IDS_PER_MARKER)} ids in one marker (${String(words.length)}); write a second marker`
854
+ };
855
+ }
856
+ const bad = words.find((word) => !ID.test(word));
857
+ if (bad !== void 0) return { kind: "bad", reason: `"${bad}" is not an id` };
858
+ const seen = /* @__PURE__ */ new Set();
859
+ for (const word of words) {
860
+ if (seen.has(word)) {
861
+ return { kind: "bad", reason: `"${word}" is named twice in one marker` };
862
+ }
863
+ seen.add(word);
864
+ }
865
+ return { kind: "ref", ids: words };
843
866
  }
844
867
  function isCite(inner) {
845
868
  return inner === "cite" || inner.startsWith("cite") && /\s/.test(inner.charAt(4));
@@ -1020,7 +1043,22 @@ function writtenForm(format) {
1020
1043
  function formatStatement(format, payload) {
1021
1044
  const form = writtenForm(format);
1022
1045
  const pad = form.open.endsWith("(") ? "" : " ";
1023
- return `${form.open}${pad}cite ${payload.id}${pad}${form.close}`;
1046
+ return `${form.open}${pad}cite ${payload.ids.join(" ")}${pad}${form.close}`;
1047
+ }
1048
+ function respellEdit(content, statement, ids) {
1049
+ const text = content.slice(statement.start, statement.end);
1050
+ const at2 = text.indexOf(statement.raw);
1051
+ if (at2 === -1) return void 0;
1052
+ return {
1053
+ start: statement.start + at2,
1054
+ end: statement.start + at2 + statement.raw.length,
1055
+ text: `cite ${ids.join(" ")}`
1056
+ };
1057
+ }
1058
+ function respellStatement(content, statement, ids) {
1059
+ const edit = respellEdit(content, statement, ids);
1060
+ if (edit === void 0) return content;
1061
+ return content.slice(0, edit.start) + edit.text + content.slice(edit.end);
1024
1062
  }
1025
1063
  function nextTextLine(text, offset, format) {
1026
1064
  let pos = nextLineStart(text, offset);
@@ -1411,34 +1449,58 @@ function readPage(file, content, opts) {
1411
1449
  );
1412
1450
  continue;
1413
1451
  }
1414
- const target = byId.get(payload.id);
1415
- if (target === void 0) {
1416
- findings.push(
1417
- finding("marker-orphan", `no entry has id "${payload.id}"`, {
1418
- line: statement.line,
1419
- id: payload.id
1420
- })
1421
- );
1422
- continue;
1423
- }
1424
- const first = markedAt.get(target);
1425
- if (first !== void 0) {
1452
+ const anchored = [];
1453
+ for (const id of payload.ids) {
1454
+ const target = byId.get(id);
1455
+ if (target === void 0) {
1456
+ findings.push(
1457
+ finding("marker-orphan", `no entry has id "${id}"`, {
1458
+ line: statement.line,
1459
+ id
1460
+ })
1461
+ );
1462
+ continue;
1463
+ }
1464
+ const first = markedAt.get(target);
1465
+ if (first !== void 0) {
1466
+ findings.push(
1467
+ finding(
1468
+ "marker-repeated",
1469
+ `${id} is named by markers at lines ${String(first)} and ${String(statement.line)}; the first anchors it.`,
1470
+ {
1471
+ line: statement.line,
1472
+ id,
1473
+ index: target.origin.index,
1474
+ src: spellSource(target.citation.source)
1475
+ }
1476
+ )
1477
+ );
1478
+ continue;
1479
+ }
1480
+ markedAt.set(target, statement.line);
1481
+ target.marker = statement;
1482
+ anchored.push(target);
1483
+ }
1484
+ const quoted2 = anchored.find((entry) => entry.citation.quote === true);
1485
+ const plain = anchored.find((entry) => entry.citation.quote !== true);
1486
+ if (quoted2 !== void 0 && plain !== void 0) {
1487
+ const mixed = {
1488
+ line: statement.line,
1489
+ index: quoted2.origin.index,
1490
+ src: spellSource(quoted2.citation.source)
1491
+ };
1492
+ if (quoted2.citation.id !== void 0) mixed.id = quoted2.citation.id;
1426
1493
  findings.push(
1427
1494
  finding(
1428
- "marker-repeated",
1429
- `${payload.id} is named by markers at lines ${String(first)} and ${String(statement.line)}; the first anchors it.`,
1430
- {
1431
- line: statement.line,
1432
- id: payload.id,
1433
- index: target.origin.index,
1434
- src: spellSource(target.citation.source)
1435
- }
1495
+ "anchor-invalid",
1496
+ named(
1497
+ quoted2.citation.id,
1498
+ "a quote entry shares a marker with a non-quote entry. Give the quote its own marker."
1499
+ ),
1500
+ mixed
1436
1501
  )
1437
1502
  );
1438
- continue;
1439
1503
  }
1440
- markedAt.set(target, statement.line);
1441
- target.marker = statement;
1442
1504
  }
1443
1505
  for (const entry of citations) {
1444
1506
  const { citation, origin, marker } = entry;
@@ -1998,7 +2060,7 @@ function readThen(input, text) {
1998
2060
  const id = input.entry.citation.id;
1999
2061
  if (id === void 0) return void 0;
2000
2062
  const marker = then.statements.find(
2001
- (statement) => statement.payload.kind === "ref" && statement.payload.id === id
2063
+ (statement) => statement.payload.kind === "ref" && statement.payload.ids.includes(id)
2002
2064
  );
2003
2065
  if (marker === void 0) return void 0;
2004
2066
  return { page: then, lines, marker };
@@ -2605,15 +2667,48 @@ function removeFrontmatterCitations(content, format, drop, filePath) {
2605
2667
  throw e;
2606
2668
  }
2607
2669
  }
2608
- function removeLine(content, line) {
2609
- const start = offsetOfLine(content, line);
2610
- const nl = content.indexOf("\n", start);
2611
- if (nl === -1) {
2612
- let above = start > 0 ? content.lastIndexOf("\n", start - 1) : -1;
2670
+ function applyEdits(content, edits) {
2671
+ const sorted = [...edits].sort((a, b) => a.start - b.start);
2672
+ let out = "";
2673
+ let at2 = 0;
2674
+ for (const edit of sorted) {
2675
+ if (edit.end < edit.start) {
2676
+ throw new Error(`cite: backwards edit [${String(edit.start)}, ${String(edit.end)})`);
2677
+ }
2678
+ if (edit.start < at2) {
2679
+ throw new Error(`cite: overlapping edits at ${String(edit.start)}`);
2680
+ }
2681
+ out += content.slice(at2, edit.start) + edit.text;
2682
+ at2 = edit.end;
2683
+ }
2684
+ return out + content.slice(at2);
2685
+ }
2686
+ function lineRemovals(content, lines) {
2687
+ const sorted = [...new Set(lines)].sort((a, b) => a - b);
2688
+ const out = [];
2689
+ let run3;
2690
+ for (const line of sorted) {
2691
+ if (run3 !== void 0 && line === run3.last + 1) run3.last = line;
2692
+ else {
2693
+ if (run3 !== void 0) out.push(runRemoval(content, run3.first, run3.last));
2694
+ run3 = { first: line, last: line };
2695
+ }
2696
+ }
2697
+ if (run3 !== void 0) out.push(runRemoval(content, run3.first, run3.last));
2698
+ return out;
2699
+ }
2700
+ function runRemoval(content, first, last) {
2701
+ let start = offsetOfLine(content, first);
2702
+ const end = offsetOfLine(content, last + 1);
2703
+ if (end === content.length && !content.endsWith("\n") && start > 0) {
2704
+ let above = content.lastIndexOf("\n", start - 1);
2613
2705
  if (above > 0 && content.charAt(above - 1) === "\r") above--;
2614
- return content.slice(0, above === -1 ? 0 : above);
2706
+ start = above === -1 ? 0 : above;
2615
2707
  }
2616
- return content.slice(0, start) + content.slice(nl + 1);
2708
+ return { start, end, text: "" };
2709
+ }
2710
+ function removeLine(content, line) {
2711
+ return applyEdits(content, lineRemovals(content, [line]));
2617
2712
  }
2618
2713
  function entryObject(citation) {
2619
2714
  const source = { file: citation.source.file };
@@ -3936,7 +4031,7 @@ async function runAdd(opts) {
3936
4031
  }
3937
4032
  if (marker && opts.id !== void 0) {
3938
4033
  const already = page.statements.find(
3939
- (s) => s.payload.kind === "ref" && s.payload.id === opts.id
4034
+ (s) => s.payload.kind === "ref" && s.payload.ids.includes(opts.id ?? "")
3940
4035
  );
3941
4036
  if (already !== void 0) {
3942
4037
  throw new CiteError(
@@ -3989,11 +4084,17 @@ async function runAdd(opts) {
3989
4084
  const sourceIndex = await sourceIndexFor(root, client);
3990
4085
  let body = content;
3991
4086
  let markerAt2;
4087
+ let markerJoined = false;
3992
4088
  let claimSpan;
3993
4089
  let claim;
3994
4090
  let elsewhere = [];
3995
4091
  let shifted = { frontmatter: [], manifest: /* @__PURE__ */ new Map() };
3996
4092
  if (marker && pageLines !== void 0) {
4093
+ if (isMarkerLine(lines[pageLines.start - 1] ?? "", format)) {
4094
+ throw new CiteError(
4095
+ `${label}:${String(pageLines.start)} is a marker line. A marker there would change its pin.`
4096
+ );
4097
+ }
3997
4098
  const holding = unitHolding(content, pageLines.start, format, {
3998
4099
  offset: page.bodyOffset,
3999
4100
  line: page.bodyLine
@@ -4012,18 +4113,30 @@ async function runAdd(opts) {
4012
4113
  `${at2} is in a ${holding.kind} at ${spellAt(holding)} that ${unitWide}. A marker anchors the whole ${holding.kind}.`
4013
4114
  );
4014
4115
  }
4015
- shifted = shiftedEntries({
4016
- citations: page.citations,
4017
- at: [holding.start],
4018
- delta: 1,
4019
- bodyLine: page.bodyLine,
4020
- label
4021
- });
4022
- const statement = markerIndent(content, holding.start, format, page.bodyLine) + formatStatement(format, { kind: "ref", id: opts.id ?? "" });
4023
- const insertAt = offsetOfLine(content, holding.start);
4024
- body = insertStatementBefore(content, insertAt, statement);
4025
- markerAt2 = holding.start;
4026
- const unit = anchoredLines(body, insertAt + statement.length, format, quote);
4116
+ const above = page.statements.filter((s) => s.anchorLine === holding.start);
4117
+ const nearest = above[above.length - 1];
4118
+ const held = nearest !== void 0 && nearest.payload.kind === "ref" && nearest.payload.ids.length < MAX_IDS_PER_MARKER && !/[\r\n]/.test(nearest.raw) ? nearest.payload.ids : void 0;
4119
+ let after2;
4120
+ if (nearest !== void 0 && held !== void 0) {
4121
+ body = respellStatement(content, nearest, [...held, opts.id ?? ""]);
4122
+ after2 = nearest.end + (body.length - content.length);
4123
+ markerAt2 = nearest.line;
4124
+ markerJoined = true;
4125
+ } else {
4126
+ shifted = shiftedEntries({
4127
+ citations: page.citations,
4128
+ at: [holding.start],
4129
+ delta: 1,
4130
+ bodyLine: page.bodyLine,
4131
+ label
4132
+ });
4133
+ const statement = markerIndent(content, holding.start, format, page.bodyLine) + formatStatement(format, { kind: "ref", ids: [opts.id ?? ""] });
4134
+ const insertAt = offsetOfLine(content, holding.start);
4135
+ body = insertStatementBefore(content, insertAt, statement);
4136
+ after2 = insertAt + statement.length;
4137
+ markerAt2 = holding.start;
4138
+ }
4139
+ const unit = anchoredLines(body, after2, format, quote);
4027
4140
  const pin = unit === void 0 ? void 0 : pinOfLines(splitLines(body), unit);
4028
4141
  if (unit === void 0 || pin === void 0) {
4029
4142
  throw new CiteError(`${at2} has no paragraph or block for a marker to anchor.`);
@@ -4121,6 +4234,7 @@ async function runAdd(opts) {
4121
4234
  ...placed === void 0 ? {} : { manifest: placed }
4122
4235
  };
4123
4236
  if (markerAt2 !== void 0) result.markerLine = markerAt2 + shift;
4237
+ if (markerJoined) result.markerJoined = true;
4124
4238
  if (claimSpan !== void 0) {
4125
4239
  result.claimLines = { start: claimSpan.start + shift, end: claimSpan.end + shift };
4126
4240
  }
@@ -4207,7 +4321,15 @@ async function runRemove(opts) {
4207
4321
  }
4208
4322
  return [];
4209
4323
  };
4210
- const markersFor = (id) => id === void 0 ? [] : page.statements.filter((s) => s.payload.kind === "ref" && s.payload.id === id).map((s) => s.line);
4324
+ const markersFor = (id) => id === void 0 ? [] : page.statements.filter(
4325
+ (s) => s.payload.kind === "ref" && s.payload.ids.includes(id)
4326
+ );
4327
+ const dropped = /* @__PURE__ */ new Map();
4328
+ const drop = (statement, id) => {
4329
+ const held = dropped.get(statement.line) ?? { statement, ids: /* @__PURE__ */ new Set() };
4330
+ held.ids.add(id);
4331
+ dropped.set(statement.line, held);
4332
+ };
4211
4333
  const removed = [];
4212
4334
  const indices = /* @__PURE__ */ new Set();
4213
4335
  const markerLines = [];
@@ -4218,20 +4340,22 @@ async function runRemove(opts) {
4218
4340
  const id = idOf(input?.entry);
4219
4341
  const markers2 = markersFor(id);
4220
4342
  indices.add(index);
4221
- markerLines.push(...markers2);
4343
+ markerLines.push(...markers2.map((s) => s.line));
4344
+ if (id !== void 0) for (const statement of markers2) drop(statement, id);
4222
4345
  removed.push({
4223
4346
  ...id === void 0 ? {} : { id },
4224
4347
  index,
4225
4348
  ...input === void 0 ? {} : { origin: input.origin },
4226
- markerLines: markers2
4349
+ markerLines: markers2.map((s) => s.line)
4227
4350
  });
4228
4351
  matched.add(value);
4229
4352
  }
4230
4353
  if (found.length > 0) continue;
4231
4354
  const markers = markersFor(value);
4232
4355
  if (markers.length === 0) continue;
4233
- markerLines.push(...markers);
4234
- removed.push({ id: value, markerLines: markers });
4356
+ markerLines.push(...markers.map((s) => s.line));
4357
+ for (const statement of markers) drop(statement, value);
4358
+ removed.push({ id: value, markerLines: markers.map((s) => s.line) });
4235
4359
  matched.add(value);
4236
4360
  }
4237
4361
  const nothing = { file: label, removed: [], diff: "", written: false };
@@ -4240,16 +4364,24 @@ async function runRemove(opts) {
4240
4364
  planned.push({ page: nothing, ...path === void 0 ? {} : { path } });
4241
4365
  return;
4242
4366
  }
4243
- const ordered = [...new Set(markerLines)].sort((a, b) => a - b);
4244
- for (const line of ordered) {
4367
+ const touched = [...new Set(markerLines)].sort((a, b) => a - b);
4368
+ for (const line of touched) {
4245
4369
  if (!isMarkerLine(lines[line - 1] ?? "", format)) {
4246
4370
  const id = page.statements.find((s) => s.line === line)?.payload;
4247
- const named4 = id?.kind === "ref" ? ` ${id.id}` : "";
4371
+ const named4 = id?.kind === "ref" ? ` ${id.ids.join(" ")}` : "";
4248
4372
  throw new CiteError(
4249
4373
  `the marker${named4} at ${label}:${String(line)} shares its line with text; remove it by hand.`
4250
4374
  );
4251
4375
  }
4252
4376
  }
4377
+ const kept = /* @__PURE__ */ new Map();
4378
+ const ordered = [];
4379
+ for (const line of touched) {
4380
+ const held = dropped.get(line);
4381
+ const [head, ...rest] = held === void 0 || held.statement.payload.kind !== "ref" ? [] : held.statement.payload.ids.filter((id) => !held.ids.has(id));
4382
+ if (head === void 0) ordered.push(line);
4383
+ else kept.set(line, [head, ...rest]);
4384
+ }
4253
4385
  const survivors = page.citations.filter((c) => !indices.has(c.origin.index));
4254
4386
  const shifted = shiftedEntries({
4255
4387
  citations: survivors,
@@ -4258,11 +4390,23 @@ async function runRemove(opts) {
4258
4390
  bodyLine: page.bodyLine,
4259
4391
  label
4260
4392
  });
4261
- let after = content;
4393
+ const edits = [];
4394
+ for (const [line, ids] of kept) {
4395
+ const held = dropped.get(line);
4396
+ if (held === void 0) continue;
4397
+ const edit = respellEdit(content, held.statement, ids);
4398
+ if (edit === void 0) {
4399
+ throw new CiteError(
4400
+ `manni cannot read the marker at ${label}:${String(line)} to rewrite it; remove it by hand.`
4401
+ );
4402
+ }
4403
+ edits.push(edit);
4404
+ }
4405
+ edits.push(...lineRemovals(content, ordered));
4406
+ let after = applyEdits(content, edits);
4262
4407
  for (const { index, lines: moved } of shifted.frontmatter) {
4263
4408
  after = spliceEntryField(after, format, index, ["claim", "lines"], moved);
4264
4409
  }
4265
- for (const line of [...ordered].sort((a, b) => b - a)) after = removeLine(after, line);
4266
4410
  if (owner === void 0) {
4267
4411
  after = removeFrontmatterCitations(after, format, indices, label);
4268
4412
  } else {
@@ -4271,12 +4415,12 @@ async function runRemove(opts) {
4271
4415
  `${label} carries no ${owner.join}: value, so its citations cannot be keyed in ${owner.file}.`
4272
4416
  );
4273
4417
  }
4274
- const kept = inputs.map((input, index) => {
4418
+ const kept2 = inputs.map((input, index) => {
4275
4419
  const moved = shifted.manifest.get(index);
4276
4420
  return moved === void 0 ? input.entry : withClaimLines(input.entry, moved);
4277
4421
  }).filter((_entry, index) => !indices.has(index));
4278
- if (kept.length === 0) await manifests.remove(owner, setup.sidecar.entry);
4279
- else void await manifests.write(owner, setup.sidecar.entry, kept, 0);
4422
+ if (kept2.length === 0) await manifests.remove(owner, setup.sidecar.entry);
4423
+ else void await manifests.write(owner, setup.sidecar.entry, kept2, 0);
4280
4424
  }
4281
4425
  const out = {
4282
4426
  file: label,
@@ -6269,4 +6413,4 @@ export {
6269
6413
  runKeyRotate,
6270
6414
  runKeySet
6271
6415
  };
6272
- //# sourceMappingURL=chunk-S5FMDWW4.js.map
6416
+ //# sourceMappingURL=chunk-FHRU3GN6.js.map