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