Skip to content

LONDON SDC | Anna Fedyna | Module-Tools | Week 3 | Implemented Shell Commands in JS #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
48 changes: 48 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { program } from "commander";
import { promises as fs } from "node:fs";
import process from "node:process";

program
.name("concatenate-and-print-files")
.description("Concatenate and print files")
.option("-b", "number the non-blank output lines, starting at 1.")
.option("-n", "number all output lines, starting at 1.")
.option("-s", "remove multiple blank lines in the text.")
.argument("<paths...>", "The file paths to process");

program.parse();
const options = program.opts();
const argvs = program.args;

if (options.n && options.b) {
console.error(`Expected -n or -b option to be passed but got both.`);
process.exit(1);
}

for (const path of argvs) {
try {
let content = await fs.readFile(path, "utf-8");
const lines = content.split("\n");
lines.pop();
let lineNumber = 1;
if (content && content.trim()) {
if (options.s) {
content = content.replace(/\n{2,}/g, "\n");
}
for (const line of lines) {
if (options.n) {
console.log(lineNumber + " " + line);
lineNumber += 1;
} else if (line.trim() && options.b) {
console.log(lineNumber + " " + line);
lineNumber += 1;
} else {
console.log(line);
}
}
}
} catch (err) {
console.error(`Error reading ${path}: ${err.message}`);
process.exit(1);
}
}
67 changes: 67 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { program } from "commander";
import { promises as fs } from "node:fs";
import process from "node:process";
import path from "node:path";

program
.name("ls")
.description(
`For each operand that names a file of type directory, ls displays the names of files contained within that directory, as well as any requested, associated information.
\nIf no operands are given, the contents of the current directory are displayed.
\nIf more than one operand is given, non-directory operands are displayed first; directory and non-directory operands are sorted separately and in lexicographical order.`
)
.option("-1, --one-per-line", "output one entry per line.")
.option("-a, --all", "shows hidden files")
.argument("[paths...]", "The file paths to process");

program.parse();

const options = program.opts();
const arg = program.args;
let currentDirName = process.cwd();

async function listDirectory(currentDirName) {
try {
const files = await fs.readdir(currentDirName);
let output = [];
files.forEach((file) => {
if (!options["all"] && file[0] === ".") {
return;
} else {
output.push(file);
}
});
output.sort();
options.onePerLine
? output.forEach((file) => {
console.log(file);
})
: console.log(output.join(" "));
} catch (err) {
if (err.code === "ENOENT") {
console.error(`Error: The directory "${currentDirName}" does not exist.`);
} else if (err.code === "ENOTDIR") {
console.error(`Error: "${currentDirName}" is not a directory.`);
} else {
console.error(`Error listing ${currentDirName}: ${err.message}`);
}
}
}

if (arg[0]) {
for (const dirName of arg) {
try {
const dirPath = path.join(currentDirName, dirName);
console.log(dirName + ":");
await listDirectory(dirPath);
} catch (err) {
console.log("Path should be a string.");
}
}
} else {
try {
await listDirectory(currentDirName);
} catch (err) {
console.log("Path should be a string.");
}
}
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.

15 changes: 15 additions & 0 deletions implement-shell-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"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"
},
"author": "",
"license": "ISC",
"dependencies": {
"commander": "^13.1.0"
},
"type": "module"
}
131 changes: 131 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { program } from "commander";
import * as fs from "node:fs";
import process from "node:process";
import path from "node:path";

program
.name("wc")
.description(`word, line, character, and byte count`)
.option(
"-l, --lines",
"The number of lines in each input file is written to the standard output."
)
.option(
"-w, --words",
"The number of words in each input file is written to the standard output."
)
.option(
"-c, --bytes",
"The number of bytes in each input file is written to the standard output."
)
.argument("<paths...>", "The file paths to process");

program.parse();

const options = program.opts();
const arg = program.args;
let currentDirName = process.cwd();

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

function countLines(content) {
let counter = 0;
for (const line of content.split("\n")) {
counter += 1;
}
return counter - 1;
}

function countWords(content) {
let counter = 0;
for (const word of content.split(" ")) {
for (const w of word.split("\n")) {
if (w) {
counter += 1;
}
}
}
return counter;
}

function countBytes(content) {
let size = new Blob([content]).size;
return size;
}

async function fileContentInfo(filePath, file) {
try {
let content = await fs.promises.readFile(filePath, "utf-8");
let output = "";
if (Object.keys(options).length == 0) {
let numLines = countLines(content);
let numWords = countWords(content);
let numBytes = countBytes(content);
output += numLines + " ";
output += numWords + " ";
output += numBytes + " ";
totalLines += numLines;
totalWords += numWords;
totalBytes += numBytes;
}
if (options.lines) {
let numLines = countLines(content);
output += numLines + " ";
totalLines += numLines;
}
if (options.words) {
let numWords = countWords(content);
output += numWords + " ";
totalWords += numWords;
}
if (options.bytes) {
let numBytes = countBytes(content);
output += numBytes + " ";
totalBytes += numBytes;
}
output += file;
console.log(output);
} catch (err) {
console.log(err);
}
}

async function main() {
const promises = arg.map((argument) => {
const filePath = path.join(currentDirName, argument);
return new Promise((resolve, reject) => {
fs.stat(filePath, (err, stats) => {
if (err) {
if (err.code === "ENOENT") {
console.error(`wc: ${argument}: read: No such file or directory`);
} else {
console.error(`wc: ${argument}: read: Error: ${err.message}`);
}
resolve();
} else if (stats.isFile()) {
fileContentInfo(filePath, argument).then(resolve);
} else if (stats.isDirectory()) {
console.log(`wc: ${argument}: read: Is a directory`);
resolve();
} else {
console.error(`wc: ${argument}: read: Unknown file type`);
resolve();
}
});
});
});
await Promise.all(promises);
if (Object.keys(options).length == 0) {
console.log(totalLines, totalWords, totalBytes, "total");
} else {
let totalOutput = "";
options.lines ? (totalOutput += totalLines + " ") : null;
options.words ? (totalOutput += totalWords + " ") : null;
options.bytes ? (totalOutput += totalBytes + " ") : null;
console.log(totalOutput + "total");
}
}

main();