-
-
Notifications
You must be signed in to change notification settings - Fork 11
London | Nadika Zavodovska | Module-Tools | Sprint 3 | Implement-Shell-Tools #45
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
d3fa55f
ff16c21
dc096f1
2ab48d1
509a5db
8ed1366
5d98dfd
a1bec5b
a33f391
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 |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import * as fs from "fs"; | ||
import { program } from "commander"; | ||
// For reading file line by line | ||
import * as readline from "readline"; | ||
// For handling "*.txt" | ||
import * as glob from "glob"; | ||
|
||
// Set up program | ||
program | ||
.name("cat command") | ||
.description("Implementing 'cat' command (-n, -b flags) functionality") | ||
.argument("<path...>", "Path") | ||
.option("-n", "Display line numbers") | ||
.option("-b", "Display line numbers only for non-empty lines") | ||
.parse(process.argv) | ||
|
||
// Function to print file contents with the flags | ||
async function displayFileContents(filePath, displayLineNumbers = false, displayNonEmptyLineNumbers = false) { | ||
try { | ||
// Create a readline interface to read file line by line | ||
const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' }); | ||
const rl = readline.createInterface({ | ||
input: fileStream, | ||
crlfDelay: Infinity | ||
}); | ||
|
||
// Initialise the line number | ||
let lineNumber = 1; | ||
|
||
// Process each line | ||
for await (const line of rl) { | ||
if (displayNonEmptyLineNumbers) { | ||
if (line.trim()) { | ||
console.log(` ${lineNumber++} ${line}`); | ||
} | ||
} else if (displayLineNumbers) { | ||
console.log(` ${lineNumber++} ${line}`); | ||
} else { | ||
console.log(line); | ||
} | ||
} | ||
} catch (error) { | ||
console.error(`Error reading directory: ${error.message}`); | ||
} | ||
} | ||
|
||
//Get user inputs from command line | ||
const filePaths = program.args; | ||
const displayLineNumbers = program.opts().n; | ||
const displayNonEmptyLineNumbers = program.opts().b; | ||
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: It's great to assign these options into variables that are more descriptive than the single letter flags, it makes the code much more readable 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. Thank you for the positive feedback, @ehwus Alex. I'll continue to follow this approach in the future! |
||
|
||
const arrayFilePaths = []; | ||
|
||
// Loop through the array with paths to handle "*" | ||
for (const filePath of filePaths) { | ||
// if filePath includes "*", use glob and push files to thr array | ||
if (filePath.includes("*")) { | ||
arrayFilePaths.push(...await glob(filePath)); | ||
} else { | ||
arrayFilePaths.push(filePath); | ||
} | ||
} | ||
|
||
// Handling multiply file processing using Promise.all and map() | ||
await Promise.all(arrayFilePaths.map(filePath => displayFileContents(filePath, displayLineNumbers, displayNonEmptyLineNumbers))); | ||
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: Why is 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. Thank you, @ehwus Alex, great question. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Getting the 'promises' part of the 'fs' module and calling it 'fs' to make it easier to use | ||
import { promises as fs } from "fs"; | ||
// Getting the 'resolve' function from 'path' module to turn relative path (from argument) into full path (absolute path) | ||
import { resolve } from "path"; | ||
// Importing the 'program' object from 'commander' to help build command-line interfaces, handle user inputs. | ||
import { program } from "commander"; | ||
// Importing the 'chalk' module to add colors and styles to text in the console | ||
import chalk from "chalk"; | ||
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: 🌈 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. Thank you! |
||
|
||
// Configure the CLI tool with name, description, arguments, and options | ||
// 'program' is from the Commander package and handles command-line arguments | ||
program | ||
.name("ls command") | ||
.description("Implementing ls command (-1, -a flags) functionality") | ||
.argument("[path]", "Path to list", ".") | ||
.option("-a", "List hidden files, directories") | ||
.option("-1", "List one file, directory per line") | ||
// Read and process command-line arguments, process.argv - an array containing the command-line arguments | ||
.parse(process.argv); | ||
|
||
// Function to list contents of a directory. | ||
async function listDirectoryFiles(directoryPath, showHiddenFiles = false, oneFilePerLine = false) { | ||
try { | ||
// Function call from the fs module. It reads contents of a directory, returns an array of file names (files) | ||
let files = await fs.readdir(directoryPath); | ||
// Array stores current directory, parent directory) | ||
let dotDirectories = []; | ||
// Array stores the names of the files and directories | ||
let displayFiles = []; | ||
// Ensure '.', '..' are on the top of list when we list hidden files | ||
if (showHiddenFiles) { | ||
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 clever way of adding the dot directories |
||
dotDirectories = [".", ".."]; | ||
} | ||
// Loop through files and add to displayFiles, including hidden files if -a flag is set | ||
for (const file of files) { | ||
if (!file.startsWith(".") || showHiddenFiles) { | ||
displayFiles.push(file); | ||
} | ||
} | ||
// Sort files without leading dots for hidden files | ||
displayFiles.sort((a, b) => { | ||
const fileNameA = a.startsWith(".") ? a.slice(1) : a; | ||
const fileNameB = b.startsWith(".") ? b.slice(1) : b; | ||
// Compares two filenames, considering numbers and ignoring case sensitivity | ||
return fileNameA.localeCompare(fileNameB, "en", { numeric: true, sensitivity: "base" }); | ||
}); | ||
// Merging two arrays into one | ||
let sortedFiles = [...dotDirectories, ...displayFiles]; | ||
|
||
const allFiles = sortedFiles.map(file => { | ||
// Creating the absolute path for the file by combining directoryPath and file name | ||
const absoluteFilePath = resolve(directoryPath, file); | ||
// Check if the file is a directory; if yes, color it blue, otherwise keep it normal. | ||
return fs.stat(absoluteFilePath).then(stats => | ||
stats.isDirectory() ? chalk.blue.bold(file) : file | ||
); | ||
}); | ||
// Array with style directory in bold and blue | ||
const styledAllFiles = await Promise.all(allFiles); | ||
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: Smart awaiting of Promises here 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. Thank you! |
||
// Print files either one per line or in a single line separated by spaces | ||
console.log(oneFilePerLine ? styledAllFiles.join("\n") : styledAllFiles.join(" ")); | ||
// If an error occurs while reading the directory, console the error message | ||
} catch (error) { | ||
console.error(`Error reading directory: ${error.message}`); | ||
} | ||
} | ||
|
||
// Get user inputs from command-line options and arguments | ||
const directory = program.args[0] || "."; | ||
const showHiddenFiles = program.opts().a; | ||
const oneFilePerLine = program.opts()["1"]; | ||
|
||
// Resolve the absolute path of the directory | ||
const absoluteDirectoryPath = resolve(directory); | ||
// Call the function to list the files in the directory (with options) | ||
await listDirectoryFiles(absoluteDirectoryPath, showHiddenFiles, oneFilePerLine); |
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.
issue: This function works and is readable, but we're loading the same data into memory multiple times in different ways. Can you think of any ways that this could be made more efficient?
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.
@ehwus Alex, thank you for the really useful feedback! Yes, there is a more efficient way, I can use the readline module. I've updated the code to use readline to read the file line by line, which helps prevent loading the entire file into memory at once.