Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
40 changes: 40 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { program } from "commander";
import { promises as fs } from "node:fs";

program
.name("cat command")
.description("create cat command")
.option("-n", "Number all lines")
.option("-b", "Number non-empty lines")
.argument("<paths...>", "File paths");

program.parse();
const paths = program.args;
const number = program.opts().n;
const nonEmptyLine = program.opts().b;

let lineNumber = 1;

for (const path of paths) {
try {
const read = await fs.readFile(path, "utf-8");
const lines = read.split("\n");
for (let i of lines) {
if (number) {

Choose a reason for hiding this comment

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

This section has some repetition - two print formats, and two "print a number"s. The nested ifs are also a bit difficult to read. Can you think of any way to tidy this up?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for feedback

I created a new function to handle the repeated parts.

console.log(`${String(lineNumber).padStart(6, " ")} ${i}`);
lineNumber++;
} else if (nonEmptyLine) {
if (i.trim() !== "") {
console.log(`${String(lineNumber).padStart(6, " ")} ${i}`);
lineNumber++;
} else {
console.log(i);
}
} else {
console.log(i);
}
}
} catch (err) {
console.error(`File could not read: ${path}`);
}
}
43 changes: 43 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { program } from "commander";
import { promises as fs } from "node:fs";

program
.name("ls command")
.description("create ls command")
.option("-1", "One entry per line")
.option("-a", "Show all files including hidden")
.argument("[paths...]", "File paths");

program.parse();

const opts = program.opts();
const paths = program.args.length > 0 ? program.args : ["."];
const onePerLine = opts["1"];
const showAll = opts.a;

for (const targetPath of paths) {
try {
let files = await fs.readdir(targetPath);

if (!showAll) {
files = files.filter((f) => !f.startsWith("."));
}


if (paths.length > 1) {
console.log(`${targetPath}:`);
}

if (onePerLine) {
for (const f of files) console.log(f);
} else {
console.log(files.join(" "));
}

if (paths.length > 1) {
console.log();
}
} catch (err) {
console.error(`ls: cannot access '${targetPath}': ${err.message}`);
}
}
25 changes: 25 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.

16 changes: 16 additions & 0 deletions implement-shell-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "implement-shell-tools",
"version": "1.0.0",
"description": "Your task is to re-implement shell tools you have used.",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"commander": "^14.0.2"
}
}
53 changes: 53 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

import { program } from "commander";
import { promises as fs } from "node:fs";

program
.option("-l", "Print only the line count")
.option("-w", "Print only the word count")
.option("-c", "Print only the byte count")
.argument("<paths...>", "One or more file paths");

program.parse();

const opts = program.opts();
const paths = program.args;

async function wcFile(path) {
const content = await fs.readFile(path, "utf8");

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

return { lines, words, bytes };
}

function formatOutput(counts, filename, opts) {
if (opts.l) return `${counts.lines} ${filename}`;
if (opts.w) return `${counts.words} ${filename}`;
if (opts.c) return `${counts.bytes} ${filename}`;

return `${counts.lines} ${counts.words} ${counts.bytes} ${filename}`;

Choose a reason for hiding this comment

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

If I request multiple files using *, the numbers dont format into columns. Can you figure out why that happens?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the feedback
I used padStart() to align the numbers in columns .

}

async function main() {
let total = { lines: 0, words: 0, bytes: 0 };
let multipleFiles = paths.length > 1;

for (const path of paths) {
const counts = await wcFile(path);

total.lines += counts.lines;
total.words += counts.words;
total.bytes += counts.bytes;

console.log(formatOutput(counts, path, opts));
}

if (multipleFiles) {
console.log(formatOutput(total, "total", opts));
}
}

main();