|
| 1 | +import type { FileHandle } from 'node:fs/promises' |
1 | 2 | import type { DecodeOptions, EncodeOptions } from '../../toon/src' |
2 | 3 | import type { InputSource } from './types' |
3 | 4 | import * as fsp from 'node:fs/promises' |
@@ -34,38 +35,42 @@ export async function encodeToToon(config: { |
34 | 35 | flattenDepth: config.flattenDepth, |
35 | 36 | } |
36 | 37 |
|
37 | | - let toonOutput: string |
38 | | - |
39 | 38 | // When printing stats, we need the full string for token counting |
40 | 39 | if (config.printStats) { |
41 | | - toonOutput = encode(data, encodeOptions) |
42 | | - } |
43 | | - else { |
44 | | - // Use streaming encoder for non-stats path |
45 | | - const lines = Array.from(encodeLines(data, encodeOptions)) |
46 | | - toonOutput = lines.join('\n') |
47 | | - } |
| 40 | + const toonOutput = encode(data, encodeOptions) |
48 | 41 |
|
49 | | - if (config.output) { |
50 | | - await fsp.writeFile(config.output, toonOutput, 'utf-8') |
51 | | - const relativeInputPath = formatInputLabel(config.input) |
52 | | - const relativeOutputPath = path.relative(process.cwd(), config.output) |
53 | | - consola.success(`Encoded \`${relativeInputPath}\` → \`${relativeOutputPath}\``) |
54 | | - } |
55 | | - else { |
56 | | - console.log(toonOutput) |
57 | | - } |
| 42 | + if (config.output) { |
| 43 | + await fsp.writeFile(config.output, toonOutput, 'utf-8') |
| 44 | + } |
| 45 | + else { |
| 46 | + console.log(toonOutput) |
| 47 | + } |
58 | 48 |
|
59 | | - if (config.printStats) { |
60 | 49 | const jsonTokens = estimateTokenCount(jsonContent) |
61 | 50 | const toonTokens = estimateTokenCount(toonOutput) |
62 | 51 | const diff = jsonTokens - toonTokens |
63 | 52 | const percent = ((diff / jsonTokens) * 100).toFixed(1) |
64 | 53 |
|
| 54 | + if (config.output) { |
| 55 | + const relativeInputPath = formatInputLabel(config.input) |
| 56 | + const relativeOutputPath = path.relative(process.cwd(), config.output) |
| 57 | + consola.success(`Encoded \`${relativeInputPath}\` → \`${relativeOutputPath}\``) |
| 58 | + } |
| 59 | + |
65 | 60 | console.log() |
66 | 61 | consola.info(`Token estimates: ~${jsonTokens} (JSON) → ~${toonTokens} (TOON)`) |
67 | 62 | consola.success(`Saved ~${diff} tokens (-${percent}%)`) |
68 | 63 | } |
| 64 | + else { |
| 65 | + // Use streaming encoder for memory-efficient output |
| 66 | + await writeStreamingToon(encodeLines(data, encodeOptions), config.output) |
| 67 | + |
| 68 | + if (config.output) { |
| 69 | + const relativeInputPath = formatInputLabel(config.input) |
| 70 | + const relativeOutputPath = path.relative(process.cwd(), config.output) |
| 71 | + consola.success(`Encoded \`${relativeInputPath}\` → \`${relativeOutputPath}\``) |
| 72 | + } |
| 73 | + } |
69 | 74 | } |
70 | 75 |
|
71 | 76 | export async function decodeToJson(config: { |
@@ -102,3 +107,50 @@ export async function decodeToJson(config: { |
102 | 107 | console.log(jsonOutput) |
103 | 108 | } |
104 | 109 | } |
| 110 | + |
| 111 | +/** |
| 112 | + * Writes TOON lines to a file or stdout using streaming approach. |
| 113 | + * Lines are written one at a time without building the full string in memory. |
| 114 | + * |
| 115 | + * @param lines - Iterable of TOON lines (without trailing newlines) |
| 116 | + * @param outputPath - File path to write to, or undefined for stdout |
| 117 | + */ |
| 118 | +async function writeStreamingToon( |
| 119 | + lines: Iterable<string>, |
| 120 | + outputPath?: string, |
| 121 | +): Promise<void> { |
| 122 | + let isFirst = true |
| 123 | + |
| 124 | + // Stream to file using fs/promises API |
| 125 | + if (outputPath) { |
| 126 | + let fileHandle: FileHandle | undefined |
| 127 | + |
| 128 | + try { |
| 129 | + fileHandle = await fsp.open(outputPath, 'w') |
| 130 | + |
| 131 | + for (const line of lines) { |
| 132 | + if (!isFirst) |
| 133 | + await fileHandle.write('\n') |
| 134 | + |
| 135 | + await fileHandle.write(line) |
| 136 | + isFirst = false |
| 137 | + } |
| 138 | + } |
| 139 | + finally { |
| 140 | + await fileHandle?.close() |
| 141 | + } |
| 142 | + } |
| 143 | + // Stream to stdout |
| 144 | + else { |
| 145 | + for (const line of lines) { |
| 146 | + if (!isFirst) |
| 147 | + process.stdout.write('\n') |
| 148 | + |
| 149 | + process.stdout.write(line) |
| 150 | + isFirst = false |
| 151 | + } |
| 152 | + |
| 153 | + // Add final newline for stdout |
| 154 | + process.stdout.write('\n') |
| 155 | + } |
| 156 | +} |
0 commit comments