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
65 changes: 65 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { readFileSync } from "node:fs";
import process from "node:process";

// Note: takes only one flag, -n or -b, and accepts whatever the last flag is
// can parse flag from any position in args

// capturing the user args
const args = process.argv.slice(2);

let flag;
const paths = [];

// the * (glob expansion is done automatically by zsh, bash etc. on linux)
for (const arg of args) {
if (arg === "-n" || arg === "-b") {
flag = arg;
} else {
paths.push(arg);
}
}

// if no file is supplied exit with error
if (paths.length === 0) {
console.error("usage: cat [-n] <file...>");
process.exit(1);
}

// starting file number, if lines need to be prepended

for (const path of paths) {
let lineNum = 1;
let file;
try {
// using sync as it's a simple short program
file = readFileSync(path, "utf-8");
} catch (err) {
console.error(`cat: ${path}: ${err.message}`);
continue; // Real cat continues to next file if current file not found
}

const lines = file.split("\n");

// remove trailing empty line as this how real cat works
if (lines[lines.length - 1] === "") lines.pop();

if (flag === "-n") {
for (const line of lines) {
console.log(`${lineNum} ${line}`);
lineNum++;
}
} else if (flag === "-b") {
for (const line of lines) {
if (line === "") {
console.log(line);
} else {
console.log(`${lineNum} ${line}`);
lineNum++;
}
}
} else {
for (const line of lines) {
console.log(line);
}
}
}
13 changes: 13 additions & 0 deletions implement-shell-tools/cat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "cat",
"version": "1.0.0",
"description": "You should already be familiar with the `cat` command line tool.",
"main": "cat.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module"
}
69 changes: 69 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import fs from "node:fs";
import process from "node:process";

const args = process.argv.slice(2);

const flags = new Set();
let paths = [];

let isFlag = true;
for (const arg of args) {
if (isFlag && arg === "--") {
isFlag = false;
} else if (isFlag && arg.startsWith("-") && arg !== "-") {
// capture the flags without the -
// supports combined flags like -1a
for (const ch of arg.slice(1)) {
flags.add(ch);
}
} else {
paths.push(arg);
}
}

if (paths.length === 0) {
paths.push(".");
}

// returns all entries for a given path
// if -a flag, then include dotfiles, else exclude dotfiles
function getPathEntries(path, aFlag = flags.has("a")) {
let entries = fs.readdirSync(path);

if (!aFlag) {
entries = entries.filter((e) => !e.startsWith("."));
}
return entries;
}

// formatter: if -1 flag, print entry per line
// else all in one line with
function printEntries(entries, onePerLineFlag = flags.has("1")) {
if (onePerLineFlag) {
entries.forEach((e) => console.log(e));
} else {
// if join on empty entries arr, add extra blank line
if (entries.length !== 0) {
console.log(entries.join("\t"));
}
}
}

// this is needed to group files at the top and folers at the bottom when giving mutiple path arguements
// to argv e.g. node ls.js sample-files/*
const fileArgs = paths.filter((p) => !fs.statSync(p).isDirectory());
const dirArgs = paths.filter((p) => fs.statSync(p).isDirectory());

// First print all plain file arguments together, as one group
if (fileArgs.length > 0) {
printEntries(fileArgs);
}

// Then print each directory's listing, with headers if needed
dirArgs.forEach((path, index) => {
if (paths.length > 1) {
if (index > 0 || fileArgs.length > 0) console.log("");
console.log(`${path}:`);
}
printEntries(getPathEntries(path));
});
25 changes: 25 additions & 0 deletions implement-shell-tools/wc/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/wc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "wc",
"version": "1.0.0",
"description": "You should already be familiar with the `wc` command line tool.",
"main": "wc.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"commander": "^15.0.0"
}
}
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 @@
import { program } from "commander";
import fs, { chownSync } from "node:fs";
import process from "node:process";

program
.name("wc")
.description("Mimics the wc command line tool")
.option("-w")
.option("-c")
.option("-l")
.argument("<files...>", "files to process");

program.parse();

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

// if no -lwc flags are supplied, wc prints
// lines, words, bytes of each file
// whereas if any flags are supplied only those
// values are printed
if (Object.keys(options).length === 0) {
options.l = options.w = options.c = true;
}

// keeps track of total values for each of the data
// incrementally updated in the loop below
const totals = { l: 0, w: 0, c: 0 };

let fileCount = 0;
for (const path of paths) {
if (fs.statSync(path).isDirectory()) {
console.log(`wc: ${path}: read: Is a directory`);
} else {
fileCount++;
let outputStr = "";
const file = fs.readFileSync(path, "utf-8");
if (options.l) {
const lines = file.split("\n");
// exclude trailing empty line from count
if (lines.at(-1) === "") {
lines.pop();
}
const lineCount = lines.length;
totals.l += lineCount;
outputStr += `\t${lineCount}`;
}

if (options.w) {
// real wc splits not just on " ", but on white spaces more generally
const words = file.split(/\s+/).filter(Boolean);
const wordCount = words.length;
totals.w += wordCount;
outputStr += `\t${wordCount}`;
}

if (options.c) {
file.size;
const byteCount = fs.statSync(path).size;
totals.c += byteCount;
outputStr += `\t${byteCount}`;
}

outputStr += ` ${path}`;
console.log(outputStr);
}
}

// if there's more than one file, print out a total
// if a flag is not selected, then the value for that flag is 0
// filter out anything with a total of 0
if (fileCount > 1) {
const totalsArr = Object.values(totals).filter((elem) => elem !== 0);
console.log(`\t${totalsArr.join("\t")} total`);
}
Loading