Skip to content
Open
60 changes: 60 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { promises as fs } from "node:fs";
import { program } from "commander";

program
.name("cat")
.description("Concatenate and print files")
.option("-n", "Number the output lines, starting at 1")
.option("-b", "Number the non-blank output lines, starting at 1")
.argument("<sample-files...>", "The file path to process")
.parse();

const argv = program.args;

const opts = program.opts();

const countLines = (data) => {
const lines = data.split("\n");
if (lines[lines.length - 1] === "") {
lines.pop();
}

let lineNum = 1;

Choose a reason for hiding this comment

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

If I pass multiple files in, the line numbering resets to 1 for each file. Can you make your implementation work more like the original cat which uses a continuous numbering?

Copy link
Author

Choose a reason for hiding this comment

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

Since I’m on macOS, the built-in cat also resets line numbers to 1 for each file. Its output looks like this:

cyf@cyfs-MacBook-Pro cat % cat -n sample-files/*
     1  Once upon a time...
     1  There was a house made of gingerbread.
     1  It looked delicious.
     2  I was tempted to take a bite of it.
     3  But this seemed like a bad idea...
     4
     5  There's more to come, though...


for (const line of lines) {
if (opts.b) {
if (line.trim() === "") {
console.log();
} else {
console.log(`${lineNum} ${line}`);
lineNum++;
}
} else if (opts.n) {
console.log(`${lineNum} ${line}`);
lineNum++;
}
}
};

async function example(path) {
try {
const data = await fs.readFile(path, { encoding: "utf8" });
if (opts["b"]) {
countLines(data);
} else if (opts["n"]) {
countLines(data);
} else {
console.log(data.trimEnd());
}
} catch (err) {
console.error(err);
}
}

const handleInput = async () => {
for (const path of argv) {
await example(path);
}
};

handleInput();
101 changes: 101 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { promises as fs } from "node:fs";
import process from "node:process";
import path from "node:path";
import { program } from "commander";

program
.name("ls")
.description("Shows files in directory")
.option("-1", "list one file per line")
.option(
"-a",
"Used to list all files, including hidden files, in the current directory"
)
.argument("[sample-files]", "The file path to process");

program.parse();

let pathToFile = "";

const programArgv = program.args;

(async () => {
if (programArgv.length === 1) {
pathToFile = programArgv[0];
try {
const stats = await fs.stat(pathToFile);
if (stats.isFile()) {
await listFiles("file");
} else if (stats.isDirectory()) {

listFiles("directory");
} else {
console.error("Not a file or directory.");
}
} catch (err) {
console.error("Invalid path:", err.message);
}
} else if (programArgv.length === 0) {
pathToFile = process.cwd();
await listFiles("directory");
} else {
console.error(
`Expected no more than 1 argument (sample-files) but got ${programArgv.length}.`
);
}
})();

const flag_1 = (files) => {
try {
files.forEach(function (file) {
console.log(file);

});
} catch (err) {
console.error("Error reading directory:", err);
}
};

const flag_a = (files) => {
try {
files.unshift("..");
files.unshift(".");
return files;
} catch (err) {
console.error("Error reading directory:", err);

Choose a reason for hiding this comment

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

You have an error condition here if there was a problem reading the directory. Under what conditions can that error happen here?

(I have the same comment for the other flag)

Copy link
Author

Choose a reason for hiding this comment

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

After reviewing the code, I realised this error condition will never occur. If we pass an invalid directory we get an error message before any flag is used, so I can remove that catch

}
};

async function listFiles(type) {
let output = []
try {
let formattedPath = "";
if (type == "directory") {
formattedPath = pathToFile;
} else if (type == "file") {
formattedPath = path.dirname(pathToFile);
}
const char = program.opts();
const files = await fs.readdir(formattedPath);
const sortedOutput = files.sort((a, b) => a.localeCompare(b));

if (char["a"]){
output = flag_a(sortedOutput)
} else {
sortedOutput.forEach(function (file) {

if (file[0] != "."){
output.push(file)
}
})
}

if (char["1"]){
flag_1(output)
} else {
console.log(output.join(" "));
}
} catch (err) {
console.error("Error reading directory:", err);
}
}
21 changes: 21 additions & 0 deletions implement-shell-tools/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions implement-shell-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"commander": "^14.0.0"
}
}
76 changes: 76 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { program } from "commander";
import { promises as fs } from "node:fs";

program
.name("count-containing-words")
.description("Counts words in a file that contain a particular character")
.option(
"-l",
"The number of lines in each input file is written to the standard output."
)
.option(
"-w",
"The number of words in each input file is written to the standard output."
)
.option(
"-c",
"The number of bytes in each input file is written to the standard output."
)
.argument("<path...>", "The file path to process")
.parse();

const argv = program.args;

const opts = program.opts();

const total = [];
const output = [];

const flag_c = (content) => {
output.push(Buffer.byteLength(content, "utf8"));
};

const flag_w = (content) => {
output.push(content.match(/\b[\w']+\b/g).length);
};

const flag_l = (content) => {
output.push(content.split("\n").length - 1);
};

const countAndDisplay = async (path) => {
const content = await fs.readFile(path, "utf-8");
output.splice(0);
if (opts["l"]) {
flag_l(content);
}
if (opts["w"]) {
flag_w(content);
}
if (opts["c"]) {
flag_c(content);
}
if (argv.length > 1) {
if (total.length == 0) {
total.push(...output);
} else {
for (let index = 0; index < output.length; index++) {
total[index] += output[index];
}
}
}
console.log([...output, path].join(" "));

Choose a reason for hiding this comment

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

Can you think of a way to make the files align neatly, as if in a table, when multiple files of different word counts are present?

Copy link
Author

Choose a reason for hiding this comment

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

yes, I can use padStart and length of the biggest value in a output as a length argument

};

const handleInput = async () => {
if (Object.keys(opts).length == 0) {
["l", "w", "c"].forEach((key) => (opts[key] = true));
}
for (const path of argv) {
await countAndDisplay(path);
}
if (argv.length > 1) {
console.log(`${total.join(" ")} total`);
}
};
handleInput();