


















@@ -44,6 +44,14 @@ export interface TruncationOptions {
4444maxBytes?: number;
4545}
464647+interface ResolvedTruncationInput {
48+lines: string[];
49+totalLines: number;
50+totalBytes: number;
51+maxLines: number;
52+maxBytes: number;
53+}
54+4755interface RuntimeBuffer {
4856byteLength(content: string, encoding: "utf8"): number;
4957}
@@ -137,6 +145,50 @@ export function formatSize(bytes: number): string {
137145return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
138146}
139147148+function resolveTruncationInput(
149+content: string,
150+options: TruncationOptions,
151+): ResolvedTruncationInput {
152+const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
153+const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
154+const totalBytes = utf8ByteLength(content);
155+const lines = splitLinesForCounting(content);
156+return {
157+ lines,
158+totalLines: lines.length,
159+ totalBytes,
160+ maxLines,
161+ maxBytes,
162+};
163+}
164+165+function buildTruncationResult(
166+input: ResolvedTruncationInput,
167+params: {
168+content: string;
169+truncated: boolean;
170+truncatedBy: TruncationResult["truncatedBy"];
171+outputLines: number;
172+outputBytes?: number;
173+lastLinePartial?: boolean;
174+firstLineExceedsLimit?: boolean;
175+},
176+): TruncationResult {
177+return {
178+content: params.content,
179+truncated: params.truncated,
180+truncatedBy: params.truncatedBy,
181+totalLines: input.totalLines,
182+totalBytes: input.totalBytes,
183+outputLines: params.outputLines,
184+outputBytes: params.outputBytes ?? utf8ByteLength(params.content),
185+lastLinePartial: params.lastLinePartial ?? false,
186+firstLineExceedsLimit: params.firstLineExceedsLimit ?? false,
187+maxLines: input.maxLines,
188+maxBytes: input.maxBytes,
189+};
190+}
191+140192/**
141193 * Truncate content from the head (keep first N lines/bytes).
142194 * Suitable for file reads where you want to see the beginning.
@@ -145,58 +197,39 @@ export function formatSize(bytes: number): string {
145197 * returns empty content with firstLineExceedsLimit=true.
146198 */
147199export function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {
148-const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
149-const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
150-151-const totalBytes = utf8ByteLength(content);
152-const lines = splitLinesForCounting(content);
153-const totalLines = lines.length;
200+const input = resolveTruncationInput(content, options);
154201155-// Check if no truncation needed
156-if (totalLines <= maxLines && totalBytes <= maxBytes) {
157-return {
202+if (input.totalLines <= input.maxLines && input.totalBytes <= input.maxBytes) {
203+return buildTruncationResult(input, {
158204 content,
159205truncated: false,
160206truncatedBy: null,
161- totalLines,
162- totalBytes,
163-outputLines: totalLines,
164-outputBytes: totalBytes,
165-lastLinePartial: false,
166-firstLineExceedsLimit: false,
167- maxLines,
168- maxBytes,
169-};
207+outputLines: input.totalLines,
208+outputBytes: input.totalBytes,
209+});
170210}
171211172-// Check if first line alone exceeds byte limit
173-const firstLineBytes = utf8ByteLength(lines[0]);
174-if (firstLineBytes > maxBytes) {
175-return {
212+const firstLineBytes = utf8ByteLength(input.lines[0]);
213+if (firstLineBytes > input.maxBytes) {
214+return buildTruncationResult(input, {
176215content: "",
177216truncated: true,
178217truncatedBy: "bytes",
179- totalLines,
180- totalBytes,
181218outputLines: 0,
182219outputBytes: 0,
183-lastLinePartial: false,
184220firstLineExceedsLimit: true,
185- maxLines,
186- maxBytes,
187-};
221+});
188222}
189223190-// Collect complete lines that fit
191224const outputLinesArr: string[] = [];
192225let outputBytesCount = 0;
193-let truncatedBy: "lines" | "bytes" = totalLines > maxLines ? "lines" : "bytes";
226+let truncatedBy: "lines" | "bytes" = input.totalLines > input.maxLines ? "lines" : "bytes";
194227195-for (let i = 0; i < lines.length && i < maxLines; i++) {
196-const line = lines[i];
228+for (let i = 0; i < input.lines.length && i < input.maxLines; i++) {
229+const line = input.lines[i];
197230const lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline
198231199-if (outputBytesCount + lineBytes > maxBytes) {
232+if (outputBytesCount + lineBytes > input.maxBytes) {
200233truncatedBy = "bytes";
201234break;
202235}
@@ -205,27 +238,22 @@ export function truncateHead(content: string, options: TruncationOptions = {}):
205238outputBytesCount += lineBytes;
206239}
207240208-// If we exited due to line limit
209-if (totalLines > maxLines && outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
241+if (
242+input.totalLines > input.maxLines &&
243+outputLinesArr.length >= input.maxLines &&
244+outputBytesCount <= input.maxBytes
245+) {
210246truncatedBy = "lines";
211247}
212248213249const outputContent = outputLinesArr.join("\n");
214-const finalOutputBytes = utf8ByteLength(outputContent);
215250216-return {
251+return buildTruncationResult(input, {
217252content: outputContent,
218253truncated: true,
219254 truncatedBy,
220- totalLines,
221- totalBytes,
222255outputLines: outputLinesArr.length,
223-outputBytes: finalOutputBytes,
224-lastLinePartial: false,
225-firstLineExceedsLimit: false,
226- maxLines,
227- maxBytes,
228-};
256+});
229257}
230258231259/**
@@ -235,46 +263,33 @@ export function truncateHead(content: string, options: TruncationOptions = {}):
235263 * May return partial first line if the last line of original content exceeds byte limit.
236264 */
237265export function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {
238-const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
239-const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
240-241-const totalBytes = utf8ByteLength(content);
242-const lines = splitLinesForCounting(content);
243-const totalLines = lines.length;
266+const input = resolveTruncationInput(content, options);
244267245-// Check if no truncation needed
246-if (totalLines <= maxLines && totalBytes <= maxBytes) {
247-return {
268+if (input.totalLines <= input.maxLines && input.totalBytes <= input.maxBytes) {
269+return buildTruncationResult(input, {
248270 content,
249271truncated: false,
250272truncatedBy: null,
251- totalLines,
252- totalBytes,
253-outputLines: totalLines,
254-outputBytes: totalBytes,
255-lastLinePartial: false,
256-firstLineExceedsLimit: false,
257- maxLines,
258- maxBytes,
259-};
273+outputLines: input.totalLines,
274+outputBytes: input.totalBytes,
275+});
260276}
261277262-// Work backwards from the end
263278const outputLinesArr: string[] = [];
264279let outputBytesCount = 0;
265-let truncatedBy: "lines" | "bytes" = totalLines > maxLines ? "lines" : "bytes";
280+let truncatedBy: "lines" | "bytes" = input.totalLines > input.maxLines ? "lines" : "bytes";
266281let lastLinePartial = false;
267282268-for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
269-const line = lines[i];
283+for (let i = input.lines.length - 1; i >= 0 && outputLinesArr.length < input.maxLines; i--) {
284+const line = input.lines[i];
270285const lineBytes = utf8ByteLength(line) + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
271286272-if (outputBytesCount + lineBytes > maxBytes) {
287+if (outputBytesCount + lineBytes > input.maxBytes) {
273288truncatedBy = "bytes";
274289// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
275290// take the end of the line (partial)
276291if (outputLinesArr.length === 0) {
277-const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
292+const truncatedLine = truncateStringToBytesFromEnd(line, input.maxBytes);
278293outputLinesArr.unshift(truncatedLine);
279294outputBytesCount = utf8ByteLength(truncatedLine);
280295lastLinePartial = true;
@@ -286,27 +301,23 @@ export function truncateTail(content: string, options: TruncationOptions = {}):
286301outputBytesCount += lineBytes;
287302}
288303289-// If we exited due to line limit
290-if (totalLines > maxLines && outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
304+if (
305+input.totalLines > input.maxLines &&
306+outputLinesArr.length >= input.maxLines &&
307+outputBytesCount <= input.maxBytes
308+) {
291309truncatedBy = "lines";
292310}
293311294312const outputContent = outputLinesArr.join("\n");
295-const finalOutputBytes = utf8ByteLength(outputContent);
296313297-return {
314+return buildTruncationResult(input, {
298315content: outputContent,
299316truncated: true,
300317 truncatedBy,
301- totalLines,
302- totalBytes,
303318outputLines: outputLinesArr.length,
304-outputBytes: finalOutputBytes,
305319 lastLinePartial,
306-firstLineExceedsLimit: false,
307- maxLines,
308- maxBytes,
309-};
320+});
310321}
311322312323/**
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。