diff --git a/src/puter-shell/coreutils/head.js b/src/puter-shell/coreutils/head.js index 286f9c7..e96d857 100644 --- a/src/puter-shell/coreutils/head.js +++ b/src/puter-shell/coreutils/head.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ import { Exit } from './coreutil_lib/exit.js'; -import { resolveRelativePath } from '../../util/path.js'; +import { fileLines } from '../../util/file.js'; export default { name: 'head', @@ -41,9 +41,8 @@ export default { } }, execute: async ctx => { - const { in_, out, err } = ctx.externs; + const { out, err } = ctx.externs; const { positionals, values } = ctx.locals; - const { filesystem } = ctx.platform; if (positionals.length > 1) { // TODO: Support multiple files (this is POSIX) @@ -63,32 +62,12 @@ export default { lineCount = parsedLineCount; } - // TODO: head can stop reading from the input as soon as it completes lineCount lines. - let lines = []; - if (relPath === '-') { - lines = await in_.collect(); - } else { - const absPath = resolveRelativePath(ctx.vars, relPath); - const fileData = await filesystem.read(absPath); - // DRY: Similar logic in wc and tail - if (fileData instanceof Blob) { - const arrayBuffer = await fileData.arrayBuffer(); - const fileText = new TextDecoder().decode(arrayBuffer); - lines = fileText.split(/\n|\r|\r\n/).map(it => it + '\n'); - } else if (typeof fileData === 'string') { - lines = fileData.split(/\n|\r|\r\n/).map(it => it + '\n'); - } else { - // ArrayBuffer or TypedArray - const fileText = new TextDecoder().decode(fileData); - lines = fileText.split(/\n|\r|\r\n/).map(it => it + '\n'); - } - } - if ( lines.length > lineCount ) { - lines = lines.slice(0, lineCount); - } - - for ( const line of lines ) { + let processedLineCount = 0; + for await (const line of fileLines(ctx, relPath)) { await out.write(line); + processedLineCount++; + if (processedLineCount >= lineCount) + break; } } }; diff --git a/src/puter-shell/coreutils/tail.js b/src/puter-shell/coreutils/tail.js index 54d56ee..f93c548 100644 --- a/src/puter-shell/coreutils/tail.js +++ b/src/puter-shell/coreutils/tail.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ import { Exit } from './coreutil_lib/exit.js'; -import { resolveRelativePath } from '../../util/path.js'; +import { fileLines } from '../../util/file.js'; export default { name: 'tail', @@ -41,9 +41,8 @@ export default { } }, execute: async ctx => { - const { in_, out, err } = ctx.externs; + const { out, err } = ctx.externs; const { positionals, values } = ctx.locals; - const { filesystem } = ctx.platform; if (positionals.length > 1) { // TODO: Support multiple files (this is an extension to POSIX, but available in the GNU tail) @@ -64,30 +63,21 @@ export default { } let lines = []; - if (relPath === '-') { - lines = await in_.collect(); - } else { - const absPath = resolveRelativePath(ctx.vars, relPath); - const fileData = await filesystem.read(absPath); - // DRY: Similar logic in wc - if (fileData instanceof Blob) { - const arrayBuffer = await fileData.arrayBuffer(); - const fileText = new TextDecoder().decode(arrayBuffer); - lines = fileText.split(/\n|\r|\r\n/).map(it => it + '\n'); - } else if (typeof fileData === 'string') { - lines = fileData.split(/\n|\r|\r\n/).map(it => it + '\n'); - } else { - // ArrayBuffer or TypedArray - const fileText = new TextDecoder().decode(fileData); - lines = fileText.split(/\n|\r|\r\n/).map(it => it + '\n'); + for await (const line of fileLines(ctx, relPath)) { + lines.push(line); + // We keep lineCount+1 lines, to account for a possible trailing blank line. + if (lines.length > lineCount + 1) { + lines.shift(); } } + + // Ignore trailing blank line if ( lines.length > 0 && lines[lines.length - 1] === '\n') { - // Ignore trailing blank line lines.pop(); } + // Now we remove the extra line if it's there. if ( lines.length > lineCount ) { - lines = lines.slice(-1 * lineCount); + lines.shift(); } for ( const line of lines ) { diff --git a/src/puter-shell/coreutils/wc.js b/src/puter-shell/coreutils/wc.js index 45cd25f..1924a4d 100644 --- a/src/puter-shell/coreutils/wc.js +++ b/src/puter-shell/coreutils/wc.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ import { resolveRelativePath } from '../../util/path.js'; +import { fileLines } from '../../util/file.js'; const TAB_SIZE = 8; @@ -100,26 +101,15 @@ export default { let inWord = false; let currentLineLength = 0; - let accumulateData = async (input) => { - let stringInput; - if (input instanceof Blob) { - const arrayBuffer = await input.arrayBuffer(); - stringInput = new TextDecoder().decode(arrayBuffer); - counts.bytes += arrayBuffer.byteLength; - } else if (typeof input === 'string') { - stringInput = input; - if (printBytes) { - const byteInput = new TextEncoder().encode(input); - counts.bytes += byteInput.length; - } - } else { - // ArrayBuffer or TypedArray - stringInput = new TextDecoder().decode(input); - counts.bytes += input.length; + + for await (const line of fileLines(ctx, relPath)) { + counts.chars += line.length; + if (printBytes) { + const byteInput = new TextEncoder().encode(line); + counts.bytes += byteInput.length; } - counts.chars += stringInput.length; - for (const char of stringInput) { + for (const char of line) { // "The wc utility shall consider a word to be a non-zero-length string of characters delimited by white space." if (/\s/.test(char)) { if (char === '\r' || char === '\n') { @@ -142,19 +132,6 @@ export default { } } - if (relPath === '-') { - let chunk, done; - const nextChunk = async () => { - ({ value: chunk, done } = await ctx.externs.in_.read()); - } - for ( await nextChunk() ; ! done ; await nextChunk() ) { - await accumulateData(chunk); - } - } else { - const absPath = resolveRelativePath(ctx.vars, relPath); - const fileData = await filesystem.read(absPath); - await accumulateData(fileData); - } counts.maxLineLength = Math.max(counts.maxLineLength, currentLineLength); newlinesWidth = Math.max(newlinesWidth, counts.newlines.toString().length); diff --git a/src/util/file.js b/src/util/file.js new file mode 100644 index 0000000..a29045f --- /dev/null +++ b/src/util/file.js @@ -0,0 +1,28 @@ +import { resolveRelativePath } from './path.js'; + +// Iterate the given file, one line at a time. +// TODO: Make this read one line at a time, instead of all at once. +export async function* fileLines(ctx, relPath, options = { dashIsStdin: true }) { + let lines = []; + if (options.dashIsStdin && relPath === '-') { + lines = await ctx.externs.in_.collect(); + } else { + const absPath = resolveRelativePath(ctx.vars, relPath); + const fileData = await ctx.platform.filesystem.read(absPath); + if (fileData instanceof Blob) { + const arrayBuffer = await fileData.arrayBuffer(); + const fileText = new TextDecoder().decode(arrayBuffer); + lines = fileText.split(/\n|\r|\r\n/).map(it => it + '\n'); + } else if (typeof fileData === 'string') { + lines = fileData.split(/\n|\r|\r\n/).map(it => it + '\n'); + } else { + // ArrayBuffer or TypedArray + const fileText = new TextDecoder().decode(fileData); + lines = fileText.split(/\n|\r|\r\n/).map(it => it + '\n'); + } + } + + for (const line of lines) { + yield line; + } +} \ No newline at end of file