Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

function cat(files, options) {
let lineNumber = 1;

files.forEach((file) => {
try {
const data = fs.readFileSync(file, 'utf8');
const lines = data.split('\n');

lines.forEach((line) => {
let prefix = '';
if (options.numberNonEmpty && line.trim()) {
prefix = `${lineNumber}\t`;
} else if (options.numberLines) {
prefix = `${lineNumber}\t`;
}
console.log(`${prefix}${line}`);
if (prefix) lineNumber++;
});
} catch (err) {
if (err.code === 'ENOENT') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much cleaner error handling!

console.error(`cat: ${file}: No such file or directory`);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job including the file name that caused the error in the message :)

} else if (err.code === 'EACCES') {
console.error(`cat: ${file}: Permission denied`);
} else {
console.error(`cat: ${file}: An error occurred`);
}
process.exit(1);
}
});
}

function main() {
const args = process.argv.slice(2);
const options = {
numberLines: false,
numberNonEmpty: false,
};

const files = [];

args.forEach((arg) => {
if (arg === '-n') {
options.numberLines = true;
} else if (arg === '-b') {
options.numberNonEmpty = true;
} else {
files.push(arg);
}
});

if (files.length === 0) {
console.error('Usage: node cat.js [-n | -b] <file>...');
process.exit(1);
}

cat(files, options);
}

main();
48 changes: 48 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node

const fs = require('fs');

function listFiles(directory, options) {
try {
const files = fs.readdirSync(directory, { withFileTypes: true });

files.forEach((file) => {
if (!options.all && file.name.startsWith('.')) {
return; // Skip hidden files unless -a is specified
}
console.log(`${directory}/${file.name}`);
});
} catch (err) {
console.error(`ls: cannot access '${directory}': ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`);
process.exit(1);
}
}

function main() {
const args = process.argv.slice(2);
const options = {
all: false,
};

const directories = [];

args.forEach((arg) => {
if (arg === '-1') {
// -1 is the default behavior, so no action needed
} else if (arg === '-a') {
options.all = true;
} else {
directories.push(arg);
}
});

if (directories.length === 0) {
directories.push('.');
}

directories.forEach((directory) => {
listFiles(directory, options);
});
}

main();
75 changes: 75 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env node

const fs = require('fs');

function countFile(file, options) {
try {
const data = fs.readFileSync(file, 'utf8');

const lines = data.split('\n').length;
const words = data.split(/\s+/).filter(Boolean).length;
const bytes = Buffer.byteLength(data, 'utf8');

const results = [];
if (options.lines || (!options.lines && !options.words && !options.bytes)) results.push(lines);
if (options.words || (!options.lines && !options.words && !options.bytes)) results.push(words);
if (options.bytes || (!options.lines && !options.words && !options.bytes)) results.push(bytes);

console.log(`${results.join('\t')}\t${file}`);

return { lines, words, bytes };
} catch (err) {
console.error(`wc: ${file}: ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`);
process.exit(1);
}
}

function main() {
const args = process.argv.slice(2);
const options = {
lines: false,
words: false,
bytes: false,
};

const files = [];

args.forEach((arg) => {
if (arg === '-l') {
options.lines = true;
} else if (arg === '-w') {
options.words = true;
} else if (arg === '-c') {
options.bytes = true;
} else {
files.push(arg);
}
Comment on lines +38 to +46
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if someone specifies more than one of these flags? What should happen?

Given the test cases we gave you in the README file, it's ok if your implementation doesn't do the same thing as the real wc does, though that would be ideal, but in general ignoring user input is bad - so if someone asks for both -l and -c and you ignore one of them, that can be confusing. Either showing both, or giving an error, is probably preferable.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the code to handle multiple flags. If multiple flags are specified, the program now displays all the requested results together, ensuring user input is respected.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixed some problems, but introduced some new ones too :)

What happens if you wc /some/file with no flags? What does your program do?

Also, what happens if you wc -l /some/file /some/other/file? What does your program do? What's different between them?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing this out! Here's how the program currently behaves:

If no flags are provided (e.g., wc /some/file), the program defaults to displaying all metrics (lines, words, and bytes) for the file. This behavior is consistent with the standard wc command.

If multiple files are provided with a single flag (e.g., wc -l /some/file /some/other/file), the program outputs the line count for each file on separate lines. However, it does not provide a total count across all files, which I’ve made the updates to align with the standard wc behavior.

});

if (files.length === 0) {
console.error('Usage: wc [-l | -w | -c] <file>...');
process.exit(1);
}

let totalLines = 0;
let totalWords = 0;
let totalBytes = 0;

files.forEach((file) => {
const { lines, words, bytes } = countFile(file, options);
totalLines += lines;
totalWords += words;
totalBytes += bytes;
});

if (files.length > 1) {
const totalResults = [];
if (options.lines || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalLines);
if (options.words || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalWords);
if (options.bytes || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalBytes);

console.log(`${totalResults.join('\t')}\ttotal`);
}
}

main();
Loading