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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
demo*
1 change: 1 addition & 0 deletions implement-shell-tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
41 changes: 41 additions & 0 deletions implement-shell-tools/cat/customCat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node
import { program } from "commander";
import { promises as fs } from "node:fs";
import process from "node:process";

program
.name("ccat")
.description("CLI command to concatenate and print files")
.option("-n, --number", "Number all output lines starting, at 1")
.option("-b, --nonBlank", "Number only non-blank lines, starting at 1")
.argument("<files...>", "Files to read");

program.parse();

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

for (const filePath of argv) {
try {
const content = await fs.readFile(filePath, "utf-8");
const lines = content.split("\n");

if (lines[lines.length - 1] === "") lines.pop();

lines.forEach((line, index) => {
if (options.nonBlank) {
if (line.trim() !== "") {
process.stdout.write(
`${(index + 1).toString().padStart(6)} ${line}\n`,
);
} else process.stdout.write(`${line}\n`);
} else if (options.number) {
process.stdout.write(`${(index + 1).toString().padStart(6)} ${line}\n`);
} else {
process.stdout.write(`${line}\n`);
}
});
} catch (error) {
console.error(`ccat: ${filePath}: No such file or directory.`);
}
}
53 changes: 53 additions & 0 deletions implement-shell-tools/ls/customLs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env node

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

program
.name("cls")
.description("List contents of a directory")
.option("-1", "Force output to be one entry per line")
.option("-a", "Include hidden files")
.argument("[path...]", "directories to list");

program.parse();

const options = program.opts();
const targetPaths = program.args.length > 0 ? program.args : ["."];

async function listDir(dirPath, showHeader) {
try {
let contents = await fs.readdir(dirPath);

if (options.a) {
contents.push(".", "..");
} else {
contents = contents.filter((name) => !name.startsWith("."));
}

contents.sort();

if (showHeader) {
process.stdout.write(`${dirPath}:\n`);
}

if (options["1"]) {
contents.forEach((item) => process.stdout.write(`${item}\n`));
} else {
process.stdout.write(`${contents.join(" ")}\n`);
}
} catch (error) {
console.error(`cls: ${dirPath}: ${error.message}`);
process.exit(1);
}
}

for (let i = 0; i < targetPaths.length; i++) {
const path = targetPaths[i];
const isMultiplePath = targetPaths.length > 1;

await listDir(path, isMultiplePath);

if (isMultiplePath && i < targetPaths.length - 1) process.stdout.write(`\n`);
}
20 changes: 20 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.3"
}
}
69 changes: 69 additions & 0 deletions implement-shell-tools/wc/customWc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env node

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

program
.name("cwc")
.description("Displays number of lines, words, and bytes in a file")
.option("-l, --lines", "Counts number of newline characters")
.option(
"-w, --words",
"Counts sequence of characters separated by whitespace",
)
.option("-c, --bytes", "Counts raw size of the files in bytes")
.argument("<files...>", "File(s) to read and count");

program.parse();

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

const noFlags = !options.lines && !options.words && !options.bytes;

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

async function countFiles(file) {
try {
const buffer = await fs.readFile(file);
const content = buffer.toString("utf-8");

const lineCount = content === "" ? 0 : content.split("\n").length - 1;
const wordCount = content.trim() ? content.trim().split(/\s+/).length : 0;
const byteCount = buffer.length;

totalLines += lineCount;
totalWords += wordCount;
totalBytes += byteCount;

let result = "";

if (options.lines || noFlags) result += `${String(lineCount).padStart(8)}`;
if (options.words || noFlags) result += `${String(wordCount).padStart(8)}`;
if (options.bytes || noFlags) result += `${String(byteCount).padStart(8)}`;

process.stdout.write(`${result} ${file}\n`);
} catch (error) {
console.error(`cwc: ${file}: ${error.message}`);
process.exit(1);
}
}

(async () => {
for (const file of files) {
await countFiles(file);
}

if (files.length > 1) {
let total = "";

if (options.lines || noFlags) total += `${String(totalLines).padStart(8)}`;
if (options.words || noFlags) total += `${String(totalWords).padStart(8)}`;
if (options.bytes || noFlags) total += `${String(totalBytes).padStart(8)}`;

process.stdout.write(`${total} total\n`);
}
})();
Loading