2026-02-23 18:51:32 +09:00
|
|
|
import type { HashlineEdit } from "./types"
|
|
|
|
|
|
2026-02-24 03:00:38 +09:00
|
|
|
type HashlineToolOp = "replace" | "append" | "prepend"
|
|
|
|
|
|
2026-02-23 18:51:32 +09:00
|
|
|
export interface RawHashlineEdit {
|
2026-02-24 03:00:38 +09:00
|
|
|
op?: HashlineToolOp
|
|
|
|
|
pos?: string
|
|
|
|
|
end?: string
|
|
|
|
|
lines?: string | string[] | null
|
2026-02-23 18:51:32 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 03:00:38 +09:00
|
|
|
function normalizeAnchor(value: string | undefined): string | undefined {
|
|
|
|
|
if (typeof value !== "string") return undefined
|
|
|
|
|
const trimmed = value.trim()
|
|
|
|
|
return trimmed === "" ? undefined : trimmed
|
2026-02-23 18:51:32 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 03:00:38 +09:00
|
|
|
function requireLines(edit: RawHashlineEdit, index: number): string | string[] {
|
|
|
|
|
if (edit.lines === undefined) {
|
|
|
|
|
throw new Error(`Edit ${index}: lines is required for ${edit.op ?? "unknown"}`)
|
2026-02-23 18:51:32 +09:00
|
|
|
}
|
2026-02-24 03:00:38 +09:00
|
|
|
if (edit.lines === null) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
return edit.lines
|
2026-02-23 18:51:32 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 03:00:38 +09:00
|
|
|
function requireLine(anchor: string | undefined, index: number, op: HashlineToolOp): string {
|
2026-02-23 18:51:32 +09:00
|
|
|
if (!anchor) {
|
2026-02-24 03:00:38 +09:00
|
|
|
throw new Error(`Edit ${index}: ${op} requires at least one anchor line reference (pos or end)`)
|
2026-02-23 18:51:32 +09:00
|
|
|
}
|
|
|
|
|
return anchor
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 03:00:38 +09:00
|
|
|
function normalizeReplaceEdit(edit: RawHashlineEdit, index: number): HashlineEdit {
|
|
|
|
|
const pos = normalizeAnchor(edit.pos)
|
|
|
|
|
const end = normalizeAnchor(edit.end)
|
|
|
|
|
const anchor = requireLine(pos ?? end, index, "replace")
|
|
|
|
|
const text = requireLines(edit, index)
|
|
|
|
|
|
|
|
|
|
if (pos && end) {
|
|
|
|
|
return {
|
|
|
|
|
type: "replace_lines",
|
|
|
|
|
start_line: pos,
|
|
|
|
|
end_line: end,
|
|
|
|
|
text,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
type: "set_line",
|
|
|
|
|
line: anchor,
|
|
|
|
|
text,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeAppendEdit(edit: RawHashlineEdit, index: number): HashlineEdit {
|
|
|
|
|
const pos = normalizeAnchor(edit.pos)
|
|
|
|
|
const end = normalizeAnchor(edit.end)
|
|
|
|
|
const anchor = pos ?? end
|
|
|
|
|
const text = requireLines(edit, index)
|
|
|
|
|
|
|
|
|
|
if (!anchor) {
|
|
|
|
|
return {
|
|
|
|
|
type: "append",
|
|
|
|
|
text,
|
2026-02-23 18:51:32 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 03:00:38 +09:00
|
|
|
return {
|
|
|
|
|
type: "insert_after",
|
|
|
|
|
line: anchor,
|
|
|
|
|
text,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizePrependEdit(edit: RawHashlineEdit, index: number): HashlineEdit {
|
|
|
|
|
const pos = normalizeAnchor(edit.pos)
|
|
|
|
|
const end = normalizeAnchor(edit.end)
|
|
|
|
|
const anchor = pos ?? end
|
|
|
|
|
const text = requireLines(edit, index)
|
|
|
|
|
|
|
|
|
|
if (!anchor) {
|
|
|
|
|
return {
|
|
|
|
|
type: "prepend",
|
|
|
|
|
text,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
type: "insert_before",
|
|
|
|
|
line: anchor,
|
|
|
|
|
text,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function normalizeHashlineEdits(rawEdits: RawHashlineEdit[]): HashlineEdit[] {
|
|
|
|
|
return rawEdits.map((rawEdit, index) => {
|
|
|
|
|
const edit = rawEdit ?? {}
|
|
|
|
|
|
|
|
|
|
switch (edit.op) {
|
|
|
|
|
case "replace":
|
|
|
|
|
return normalizeReplaceEdit(edit, index)
|
|
|
|
|
case "append":
|
|
|
|
|
return normalizeAppendEdit(edit, index)
|
|
|
|
|
case "prepend":
|
|
|
|
|
return normalizePrependEdit(edit, index)
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Edit ${index}: unsupported op "${String(edit.op)}". Legacy format was removed; use op/pos/end/lines.`
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-02-23 18:51:32 +09:00
|
|
|
}
|