-
-
Notifications
You must be signed in to change notification settings - Fork 15
London| Elhadj Abdoul Diallo| Module-Tools | WEEK3 - Implement-shell-tools #25
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
base: main
Are you sure you want to change the base?
Changes from all commits
f5382f7
cfa60fe
48355e1
16944c0
677386b
2fc1242
6c5e0c7
fe03c76
9fb84ad
2150905
2376e64
24ceb1c
d823cbe
97647ec
5cc3cac
6fe4d34
80b1b24
aee2490
cf568a4
bad7699
d8f858b
1239f5f
0021d1e
5b3d5c9
2b16d73
9ddebf4
e5ccf82
da2ec96
ea33cfd
cd2a6de
3ffac88
8b23d95
0e6ac90
22fda0d
f0b5293
8444b86
3d9756b
c5e2b07
faece38
b8fc18f
522e254
e53d221
19ed271
1517657
6aea96c
7c4ca2a
d81c7d8
c180a48
475f309
0ce2ebc
bb95ea4
417bb33
9e94b59
148ddae
0f26dac
b932eaf
b75d6f6
7ab2cca
353f36b
17a4b2d
d9d44a6
cd0b63c
fcf9a71
05cef26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
node_modules | ||
node_modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { promises as fs } from "node:fs"; | ||
import process from "node:process"; | ||
import { program } from "commander"; | ||
|
||
program | ||
.name("Implement 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("<paths...>", "The file paths to process") | ||
.parse(process.argv); | ||
|
||
const args = program.args; | ||
|
||
const displayLineNumber = program.opts().number; | ||
const displayNonEmptyLineNumber = program.opts().number2; | ||
let lineNumber = 1; | ||
|
||
async function readAndPrintFileContent(path) { | ||
try { | ||
const content = await fs.readFile(path, { encoding: "utf-8" }); | ||
const lines = content.split("\n"); | ||
lines[lines.length - 1] === "" ? lines.pop() : lines; | ||
printLinesWithOptions(lines); | ||
} catch (err) { | ||
console.error(err.message); | ||
} | ||
} | ||
|
||
function printLinesWithOptions(lines) { | ||
lines.forEach((line) => { | ||
displayLineNumber | ||
? console.log(`${lineNumber++} ${line}`) | ||
: displayNonEmptyLineNumber && line !== "" | ||
? console.log(`${lineNumber++} ${line}`) | ||
: console.log(line); | ||
}); | ||
} | ||
// display file/s contents with line numbers. | ||
args.map(readAndPrintFileContent); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import process from "node:process"; | ||
import { promises as fs } from "node:fs"; | ||
import { program } from "commander"; | ||
|
||
program | ||
.name("Implement ls") | ||
.description("Implements a version of the ls command") | ||
.option("-1, --one", "Output all files and directories each in a line") | ||
.option("-a, --hidden", "Output hidden files/directories") | ||
.argument("[paths...]", "the paths to be processed") | ||
.parse(process.argv); | ||
|
||
/** | ||
* Reads the contents of the specified directories and outputs the file names. | ||
* Supports options for displaying hidden files and listing each file on a new line. | ||
* | ||
* @param {string[]} filePaths - The paths to be processed. Defaults to the current directory if no argument is provided. | ||
* @param {boolean} one - If true, outputs each file and directory on a new line. | ||
* @param {boolean} hidden - If true, includes hidden files and directories in the output. | ||
*/ | ||
|
||
const filePaths = program.args.length ? program.args : ["."]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. praise: This is a good way of implementing a sensible default, and shows that you know a great deal about how |
||
|
||
const outputOnePerLine = program.opts().one; | ||
const includeHiddenFiles = program.opts().hidden; | ||
|
||
async function listDirectoryContents(filePath) { | ||
try { | ||
const files = await fs.readdir(filePath, { withFileTypes: true }); // is returned as a Dirent | ||
const output = []; | ||
|
||
files.forEach((file) => { | ||
if (includeHiddenFiles || !file.name.startsWith(".")) { | ||
output.push(file.name); | ||
} | ||
}); | ||
|
||
if (includeHiddenFiles) { | ||
output.unshift(".", ".."); | ||
} | ||
|
||
outputOnePerLine === true | ||
? output.forEach((file) => console.log(file)) | ||
: console.log(output.join(" ")); | ||
} catch (err) { | ||
console.error(`Error reading directory ${filePath}:`, err.message); | ||
} | ||
} | ||
|
||
//filePaths.map(listDirectoryContents);fi | ||
filePaths.forEach(listDirectoryContents); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { program } from "commander"; | ||
import process from "node:process"; | ||
import { promises as fs } from "node:fs"; | ||
|
||
program | ||
.name("Implement wc") | ||
.description("Implements a version of the wc command") | ||
.option("-l, --line", "Counts file lines") | ||
.option("-c, --char", "Counts characters in file") | ||
.option("-w, --word", "Counts words in file") | ||
.argument("[paths...]", "The path/s to process") | ||
.parse(process.argv); | ||
|
||
const args = program.args; | ||
|
||
const lineOption = program.opts().line; | ||
const charOption = program.opts().char; | ||
const wordOption = program.opts().word; | ||
|
||
async function countLinesWordsCharsInFile(path) { | ||
const content = await fs.readFile(path, { encoding: "utf-8" }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: What are the tradeoffs of reading files in this way? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if the file was large, that would caused high memory consumption and possible out of memory errors. |
||
|
||
const lines = content.split("\n") | ||
lines[lines.length - 1] === "" ? lines.pop() : lines; | ||
|
||
const words = lines.flatMap((element) => | ||
element.split(" ").filter((word) => word !== "") | ||
); | ||
|
||
const chars = content.split(""); | ||
|
||
const numberOfLines = lines.length; | ||
const numberOfWords = words.length; | ||
const numberOfChars = chars.length; | ||
|
||
if (lineOption && charOption) { | ||
return `${numberOfLines} ${numberOfChars} ${path}`; | ||
} | ||
|
||
if (lineOption && wordOption) { | ||
return `${numberOfLines} ${numberOfWords} ${path}`; | ||
} | ||
|
||
if (wordOption && charOption) { | ||
return `${numberOfWords} ${numberOfChars} ${path}`; | ||
} | ||
|
||
if (lineOption) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. I've not implemented this one. I am going to add the functionality |
||
return `${numberOfLines} ${path}`; | ||
} | ||
|
||
if (charOption) { | ||
return `${numberOfChars} ${path}`; | ||
} | ||
|
||
if (wordOption) { | ||
return `${numberOfWords} ${path}`; | ||
} | ||
|
||
return `${numberOfLines} ${numberOfWords} ${numberOfChars} ${path}`; | ||
} | ||
|
||
async function processFilesAndDisplayCounts() { | ||
const fileCounts = await Promise.all(args.map(countLinesWordsCharsInFile)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. praise: awaiting and using |
||
fileCounts.forEach((fileCount) => console.log(fileCount)); | ||
|
||
const aggregatedFilesData = aggregateFileData(fileCounts); | ||
|
||
if (aggregatedFilesData !== 0 && lineOption && !(wordOption || charOption)) { | ||
console.log(`${aggregatedFilesData[0]} total`); | ||
return; | ||
} | ||
|
||
if (aggregatedFilesData !== 0 && charOption && !(wordOption || lineOption)) { | ||
console.log(`${aggregatedFilesData[0]} total`); | ||
return; | ||
} | ||
|
||
if (aggregatedFilesData !== 0 && wordOption && !(lineOption || charOption)) { | ||
console.log(`${aggregatedFilesData[0]} total`); | ||
return; | ||
} | ||
|
||
|
||
if ( | ||
aggregatedFilesData !== 0 && | ||
((lineOption && charOption) || (lineOption && wordOption) || (wordOption && charOption)) | ||
) { | ||
console.log(`${aggregatedFilesData[0]} ${aggregatedFilesData[1]} total`); | ||
return; | ||
} | ||
|
||
aggregatedFilesData !== 0 | ||
? console.log( | ||
`${aggregatedFilesData[0]} ${aggregatedFilesData[1]} ${aggregatedFilesData[2]} total` | ||
) | ||
: null; | ||
} | ||
|
||
/** | ||
* Aggregates numerical data from an array of file content strings. | ||
* | ||
* @param {string[]} files - An array of strings, each representing the content of a file. | ||
* Each string should contain space-separated numbers. | ||
* @returns {number[]|number} - An array of sums for each column of numbers if there are multiple files, | ||
* or 0 if there is only one file. | ||
*/ | ||
function aggregateFileData(fileCounts) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Please could you talk me through this function line by line? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure the if (aggregatedFilesData !== 0 && lineOption && !(wordOption || charOption)) {
console.log(`${aggregatedFilesData[0]} total`);
return;
} whether aggregatedFilesData is not 0 and line option is provided but not the rest of the options(chars and words).if it is true, it means only sum of lines is available on it is the same for chars option in the following code: if (aggregatedFilesData !== 0 && charOption && !(wordOption || lineOption)) {
console.log(`${aggregatedFilesData[0]} total`);
return;
} Here: if (
aggregatedFilesData !== 0 &&
((lineOption && charOption) || (lineOption && wordOption))
) {
console.log(`${aggregatedFilesData[0]} ${aggregatedFilesData[1]} total`);
return;
} This code checks if two options are provided, it could be line option and char option or line and word option. if it is true, I know the The if the bove conditions are false: the sum of each option is display if |
||
const digits = fileCounts.map((element) => | ||
element.split(" ").slice(0, -1).map(Number) | ||
); | ||
const sums = | ||
digits.length > 1 | ||
? digits[0].map((_, colIndex) => | ||
digits.reduce((sum, row) => sum + row[colIndex], 0) | ||
) | ||
: digits[0]; | ||
return sums; | ||
} | ||
|
||
processFilesAndDisplayCounts(); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"type": "module", | ||
"dependencies": { | ||
"commander": "^13.1.0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. praise: Including this as a dependency increases the portability of the code and made my life easier, thanks! |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: What does this do to the file from the filesystem? Could we do this more memory efficiently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's reading the content of the file(path) provided through the CLI asyncronously which means it won't prevent other tasks from executing. Could we do this more memory efficiently? I am not 100% sure but I know if I was reading a large file, I would have used
Stream
to read it in chuncks instead of loading it once.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're along the right lines here, text files might be absolutely massive (e.g. logs) so making sure that our programs operate on them efficiently is really important, especially as we're processing line by line anyway