(raw: string)
| 60 | } as const satisfies Record<string, keyof Omit<ParsedAnnotateArgs, "filePath" | "rawFilePath">>; |
| 61 | |
| 62 | export function parseAnnotateArgs(raw: string): ParsedAnnotateArgs { |
| 63 | const s = (raw ?? "").trim(); |
| 64 | const flags = { gate: false, json: false, hook: false, renderHtml: false, renderMarkdown: false, noJina: false }; |
| 65 | |
| 66 | const segments: Segment[] = []; |
| 67 | for (let i = 0; i < s.length;) { |
| 68 | const isWs = /\s/.test(s[i]); |
| 69 | const start = i; |
| 70 | while (i < s.length && /\s/.test(s[i]) === isWs) i++; |
| 71 | segments.push({ type: isWs ? "ws" : "tok", text: s.slice(start, i) }); |
| 72 | } |
| 73 | |
| 74 | const keep = segments.map(() => true); |
| 75 | for (let j = 0; j < segments.length; j++) { |
| 76 | const seg = segments[j]; |
| 77 | if (seg.type !== "tok") continue; |
| 78 | const key = FLAG_MAP[seg.text as keyof typeof FLAG_MAP]; |
| 79 | if (!key) continue; |
| 80 | |
| 81 | flags[key] = true; |
| 82 | keep[j] = false; |
| 83 | |
| 84 | // Drop one adjacent whitespace run so removed flags don't leave dangling |
| 85 | // spaces. Prefer trailing whitespace; fall back to leading if at the end. |
| 86 | if (j + 1 < segments.length && segments[j + 1].type === "ws") { |
| 87 | keep[j + 1] = false; |
| 88 | } else if (j > 0 && segments[j - 1].type === "ws") { |
| 89 | keep[j - 1] = false; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // Trim covers the case where two adjacent flags (`... --gate --json`) |
| 94 | // both claim the single whitespace between them, leaving a trailing space |
| 95 | // after the kept token. Wrapping quotes come from OpenCode/Pi users who |
| 96 | // quote paths with spaces (shell muscle memory); strip them here so |
| 97 | // downstream callers never see tokenization artifacts. |
| 98 | const rawFilePath = stripWrappingQuotes( |
| 99 | segments |
| 100 | .filter((_, j) => keep[j]) |
| 101 | .map((seg) => seg.text) |
| 102 | .join("") |
| 103 | .trim(), |
| 104 | ); |
| 105 | |
| 106 | if (flags.hook) flags.gate = true; |
| 107 | |
| 108 | return { filePath: stripAtPrefix(rawFilePath), rawFilePath, ...flags }; |
| 109 | } |
no test coverage detected