From b841fb72bfe1c8c9133dd58b025944be91db1937 Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Wed, 12 Mar 2025 10:30:42 +0000 Subject: [PATCH 01/12] Implement basic functionality for custom cat command with line numbering options --- implement-shell-tools/cat/cat.js | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 0000000..8dd87ea --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,43 @@ +const { promises: fs } = require("fs"); +const process = require("process"); +const { program } = require("commander"); + +program + .name("cat") + .description("Implement a version of the cat program") + .option("-n, --number", "Number the output lines, starting at 1") + .option("-b, --number2", "Number the nonempty lines, starting at 1") + .argument("", "The file paths to process") + .parse(process.argv); + +let args = program.args; +const nOption = program.opts().number; +const bOption = program.opts().number2; +let lineNumber = 1; +let nonEmptyLineNumber = 1; + +function printLinesWithOptions(lines) { + lines.forEach((line, index) => { + if (nOption) { + console.log(`${lineNumber++} ${line}`); + } else if (bOption && line.trim()) { + console.log(`${nonEmptyLineNumber++} ${line}`); + } else { + console.log(line); + } + }); +} + +async function readAndPrintFileContent(path) { + try { + const content = await fs.readFile(path, { encoding: "utf-8" }); + const lines = content.split("\n"); + printLinesWithOptions(lines); + } catch (err) { + console.error(`Error reading file ${path}: ${err.message}`); + } +} + +args.forEach((arg) => { + readAndPrintFileContent(arg); +}); From 8ec04077f3783563ab8ab258671624b2b889cab6 Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Thu, 13 Mar 2025 11:14:16 +0000 Subject: [PATCH 02/12] Refactor cat command to improve option handling and rename read function --- implement-shell-tools/cat/cat.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 8dd87ea..ec31edb 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -11,8 +11,9 @@ program .parse(process.argv); let args = program.args; -const nOption = program.opts().number; -const bOption = program.opts().number2; +//const nOption = program.opts().number; +//const bOption = program.opts().number2; +const { number: nOption, number2: bOption } = program.opts(); let lineNumber = 1; let nonEmptyLineNumber = 1; @@ -28,10 +29,13 @@ function printLinesWithOptions(lines) { }); } -async function readAndPrintFileContent(path) { +async function readFileContent(path) { try { const content = await fs.readFile(path, { encoding: "utf-8" }); const lines = content.split("\n"); + if(lines[lines.length - 1] === "") { + lines.pop(); + } printLinesWithOptions(lines); } catch (err) { console.error(`Error reading file ${path}: ${err.message}`); @@ -39,5 +43,5 @@ async function readAndPrintFileContent(path) { } args.forEach((arg) => { - readAndPrintFileContent(arg); + readFileContent(arg); }); From 8562ec4fd78d376c55a4aef34381a59600f543c4 Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Thu, 13 Mar 2025 11:16:14 +0000 Subject: [PATCH 03/12] Add ls command implementation with options for hidden files and line formatting --- implement-shell-tools/ls/ls.js | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 implement-shell-tools/ls/ls.js diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 0000000..c352e05 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,37 @@ +const fs = require('fs'); +const { program } = require('commander'); + +program + .option('-a, --all', 'Show hidden files') + .option('-1', 'Show one file per line') + .argument('[dirPath]', 'Directory path', '.') + .parse(process.argv); + +const { all, '1': onePerLine } = program.opts(); +const dirPath = program.args[0] || '.'; + +if (typeof dirPath !== 'string') { + console.error('Error: Invalid directory path'); + process.exit(1); +} + +function listDirectoryContents(dirPath, showHidden = false, onePerLine = false) { + fs.readdir(dirPath, (err, files) => { + if (err) { + console.error(`Error reading directory: ${err.message}`); + return; + } + + if (!showHidden) { + files = files.filter(file => !file.startsWith('.')); + } + + if (onePerLine) { + files.forEach(file => console.log(file)); + } else { + console.log(files.join(' ')); + } + }); +} + +listDirectoryContents(dirPath, all, onePerLine); From 1e51d9bc006be5764f2cfb1c49d43076d82a3c53 Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Thu, 20 Mar 2025 10:59:51 +0000 Subject: [PATCH 04/12] Implement wc command to count lines, words, and characters in files --- implement-shell-tools/wc/wc.js | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 0000000..3661064 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,71 @@ +const { promises: fs } = require('fs'); +const path = require('path'); +const { program } = require('commander'); + +program + .description('Count lines, words, and characters in the specified files') + .option('-l, --lines', 'Count the lines') + .option('-w, --words', 'Count the words') + .option('-c, --characters', 'Count the characters') + .parse(process.argv); + +const options = program.opts(); +const files = program.args; + +if (files.length === 0) { + console.error("Error: No files provided."); + process.exit(1); +} + +async function countFileStats(filePath, options) { + try { + const data = await fs.readFile(filePath, 'utf8'); + + const lines = data.split('\n').length; + const words = data.split(/\s+/).filter(Boolean).length; + const characters = data.length; + + const result = { lines, words, characters }; + const output = []; + if (options.lines) output.push(result.lines); + if (options.words) output.push(result.words); + if (options.characters) output.push(result.characters); + if (output.length === 0) output.push(result.lines, result.words, result.characters); + + return { file: filePath, output: output.join(' '), lines, words, characters }; + } catch (err) { + console.error(`Error reading file: ${filePath}`); + return null; + } +} + +async function processFiles() { + let totalLines = 0; + let totalWords = 0; + let totalCharacters = 0; + + for (const file of files) { + const filePath = path.resolve(file); + const result = await countFileStats(filePath, options); + + if (result) { + console.log(result.output, result.file); + totalLines += result.lines; + totalWords += result.words; + totalCharacters += result.characters; + } + } + + if (files.length > 1) { + const totals = []; + if (options.lines !== false) totals.push(totalLines); + if (options.words !== false) totals.push(totalWords); + if (options.characters !== false) totals.push(totalCharacters); + + if (totals.length > 0) { + console.log(totals.join(' ') + ' total'); + } + } +} + +processFiles(); From ad139f63f3e69f55ba914eea4f3576f756aa56fd Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 11:36:51 +0100 Subject: [PATCH 05/12] Remove unnecessary comments --- implement-shell-tools/cat/cat.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index ec31edb..c4cdedd 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -11,8 +11,7 @@ program .parse(process.argv); let args = program.args; -//const nOption = program.opts().number; -//const bOption = program.opts().number2; + const { number: nOption, number2: bOption } = program.opts(); let lineNumber = 1; let nonEmptyLineNumber = 1; From 00bceffad0d3a255c3aca360b00aceed7ffc8a97 Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 11:39:46 +0100 Subject: [PATCH 06/12] Remove unnecessary spance between if and opening parenthesis --- implement-shell-tools/cat/cat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index c4cdedd..10b0b38 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -18,9 +18,9 @@ let nonEmptyLineNumber = 1; function printLinesWithOptions(lines) { lines.forEach((line, index) => { - if (nOption) { + if(nOption) { console.log(`${lineNumber++} ${line}`); - } else if (bOption && line.trim()) { + } else if(bOption && line.trim()) { console.log(`${nonEmptyLineNumber++} ${line}`); } else { console.log(line); From ddc34e23cd3e4ac8300c62798e7bc3ab5d730b0d Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 11:59:08 +0100 Subject: [PATCH 07/12] Fix line numbering to match cat -b behavior across multiple files --- implement-shell-tools/cat/cat.js | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 10b0b38..1be395f 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,26 +1,25 @@ const { promises: fs } = require("fs"); -const process = require("process"); const { program } = require("commander"); program .name("cat") .description("Implement a version of the cat program") - .option("-n, --number", "Number the output lines, starting at 1") - .option("-b, --number2", "Number the nonempty lines, starting at 1") + .option("-n, --number", "Number all output lines, starting at 1") + .option("-b, --number2", "Number non-empty output lines, starting at 1") .argument("", "The file paths to process") - .parse(process.argv); - -let args = program.args; + .parse(); const { number: nOption, number2: bOption } = program.opts(); +const paths = program.args; + let lineNumber = 1; let nonEmptyLineNumber = 1; function printLinesWithOptions(lines) { - lines.forEach((line, index) => { - if(nOption) { + lines.forEach(line => { + if (nOption) { console.log(`${lineNumber++} ${line}`); - } else if(bOption && line.trim()) { + } else if (bOption && line.trim()) { console.log(`${nonEmptyLineNumber++} ${line}`); } else { console.log(line); @@ -30,17 +29,12 @@ function printLinesWithOptions(lines) { async function readFileContent(path) { try { - const content = await fs.readFile(path, { encoding: "utf-8" }); + const content = await fs.readFile(path, "utf-8"); const lines = content.split("\n"); - if(lines[lines.length - 1] === "") { - lines.pop(); - } printLinesWithOptions(lines); } catch (err) { console.error(`Error reading file ${path}: ${err.message}`); } } -args.forEach((arg) => { - readFileContent(arg); -}); +Promise.all(paths.map(readFileContent)); From 36817a026cab26b9b9fd8e1c40ac36f34898e100 Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 12:04:52 +0100 Subject: [PATCH 08/12] Align line numbers using padStart like cat -n --- implement-shell-tools/cat/cat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 1be395f..562074a 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -18,9 +18,9 @@ let nonEmptyLineNumber = 1; function printLinesWithOptions(lines) { lines.forEach(line => { if (nOption) { - console.log(`${lineNumber++} ${line}`); + console.log(`${String(lineNumber++).padStart(6)} ${line}`); } else if (bOption && line.trim()) { - console.log(`${nonEmptyLineNumber++} ${line}`); + console.log(`${String(nonEmptyLineNumber++).padStart(6)} ${line}`); } else { console.log(line); } From 6e9c662e42b1c1e1db8acaf75ab1149b19413dfd Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 12:22:47 +0100 Subject: [PATCH 09/12] Simplify option checks by removing unnecessary !== false comparisons --- implement-shell-tools/wc/wc.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 3661064..a4338f0 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -21,7 +21,7 @@ async function countFileStats(filePath, options) { try { const data = await fs.readFile(filePath, 'utf8'); - const lines = data.split('\n').length; + const lines = (data.match(/\n/g) || []).length; const words = data.split(/\s+/).filter(Boolean).length; const characters = data.length; @@ -58,8 +58,8 @@ async function processFiles() { if (files.length > 1) { const totals = []; - if (options.lines !== false) totals.push(totalLines); - if (options.words !== false) totals.push(totalWords); + if (options.lines) totals.push(totalLines); + if (options.words) totals.push(totalWords); if (options.characters !== false) totals.push(totalCharacters); if (totals.length > 0) { From 7416d4e00767374e5af910bbb3a90b29d5c78c3a Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 12:27:07 +0100 Subject: [PATCH 10/12] Print relative file paths instead of absolute ones for output consistency --- implement-shell-tools/wc/wc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index a4338f0..1f7a3c7 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -49,7 +49,7 @@ async function processFiles() { const result = await countFileStats(filePath, options); if (result) { - console.log(result.output, result.file); + console.log(result.output, file); totalLines += result.lines; totalWords += result.words; totalCharacters += result.characters; From 4c422ff01a0ca369f4cd9f390d384b5fe665b709 Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 12:36:00 +0100 Subject: [PATCH 11/12] Add support for multiple directory arguments and improve error handling --- implement-shell-tools/ls/ls.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index c352e05..d05c6c2 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -4,21 +4,16 @@ const { program } = require('commander'); program .option('-a, --all', 'Show hidden files') .option('-1', 'Show one file per line') - .argument('[dirPath]', 'Directory path', '.') + .argument('[dirPaths...]', 'Directory paths', ['.']) // Support multiple directories .parse(process.argv); const { all, '1': onePerLine } = program.opts(); -const dirPath = program.args[0] || '.'; - -if (typeof dirPath !== 'string') { - console.error('Error: Invalid directory path'); - process.exit(1); -} +const dirPaths = program.args; function listDirectoryContents(dirPath, showHidden = false, onePerLine = false) { fs.readdir(dirPath, (err, files) => { if (err) { - console.error(`Error reading directory: ${err.message}`); + console.error(`Error reading directory '${dirPath}': ${err.message}`); return; } @@ -26,12 +21,19 @@ function listDirectoryContents(dirPath, showHidden = false, onePerLine = false) files = files.filter(file => !file.startsWith('.')); } + console.log(`\n${dirPath}:`); if (onePerLine) { - files.forEach(file => console.log(file)); + files.forEach(file => console.log(file)); } else { - console.log(files.join(' ')); + console.log(files.join(' ')); } }); } -listDirectoryContents(dirPath, all, onePerLine); +dirPaths.forEach(dirPath => { + if (typeof dirPath !== 'string') { + console.error(`Error: Invalid directory path '${dirPath}'`); + return; + } + listDirectoryContents(dirPath, all, onePerLine); +}); From f79d6fdc322823be6f1ba725d51f4fcbbf05418f Mon Sep 17 00:00:00 2001 From: ameneh-keshavarz Date: Sun, 25 May 2025 20:09:11 +0100 Subject: [PATCH 12/12] Add minimal package.json with commander dependency --- implement-shell-tools/package-lock.json | 20 ++++++++++++++++++++ implement-shell-tools/package.json | 7 +++++++ 2 files changed, 27 insertions(+) create mode 100644 implement-shell-tools/package-lock.json create mode 100644 implement-shell-tools/package.json diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 0000000..8b77de7 --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "implement-shell-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^11.0.0" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 0000000..8dbeb02 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,7 @@ +{ + "type": "commonjs", + "dependencies": { + "commander": "^11.0.0" + } + } + \ No newline at end of file