-
-
Notifications
You must be signed in to change notification settings - Fork 105
London | 26-SDC-Jul | Raihan Sharif | Sprint 3 | Implement shell tools #609
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
04e0f69
8cadca9
8294ca8
7144124
aeca1f7
1d9d1df
d577899
4d0e873
75e9d0a
b77ab50
abce30a
98c1c43
5e36e1f
1a27139
667e3bb
c7449be
45766e0
2c3458f
3c8e345
1a80d5b
bdfbbba
0b3d37d
0741a9a
add67c9
d7ddea2
c8e7c29
bc623a1
9135017
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 { readFileSync } from "node:fs"; | ||
| import process from "node:process"; | ||
|
|
||
| // Note: takes only one flag, -n or -b, and accepts whatever the last flag is | ||
| // can parse flag from any position in args | ||
|
|
||
| // capturing the user args | ||
| const args = process.argv.slice(2); | ||
|
|
||
| let flag; | ||
| const paths = []; | ||
|
|
||
| // the * (glob expansion is done automatically by zsh, bash etc. on linux) | ||
| for (const arg of args) { | ||
| if (arg === "-n" || arg === "-b") { | ||
| flag = arg; | ||
| } else { | ||
| paths.push(arg); | ||
| } | ||
| } | ||
|
|
||
| // if no file is supplied exit with error | ||
| if (paths.length === 0) { | ||
| console.error("usage: cat [-n] <file...>"); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // starting file number, if lines need to be prepended | ||
|
|
||
| for (const path of paths) { | ||
| let lineNum = 1; | ||
| let file; | ||
| try { | ||
| // using sync as it's a simple short program | ||
| file = readFileSync(path, "utf-8"); | ||
| } catch (err) { | ||
| console.error(`cat: ${path}: ${err.message}`); | ||
| continue; // Real cat continues to next file if current file not found | ||
| } | ||
|
|
||
| const lines = file.split("\n"); | ||
|
|
||
| // remove trailing empty line as this how real cat works | ||
| if (lines[lines.length - 1] === "") lines.pop(); | ||
|
|
||
| if (flag === "-n") { | ||
| for (const line of lines) { | ||
| console.log(`${String(lineNum).padStart(6, " ")}\t${line}`); | ||
| lineNum++; | ||
| } | ||
| } else if (flag === "-b") { | ||
| for (const line of lines) { | ||
| if (line === "") { | ||
| console.log(line); | ||
| } else { | ||
| console.log(`${String(lineNum).padStart(6, " ")}\t${line}`); | ||
| lineNum++; | ||
| } | ||
| } | ||
| } else { | ||
| for (const line of lines) { | ||
| console.log(line); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "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" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC", | ||
| "type": "module" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import fs from "node:fs"; | ||
| import process from "node:process"; | ||
|
|
||
| const args = process.argv.slice(2); | ||
|
|
||
| const flags = new Set(); | ||
| let paths = []; | ||
|
|
||
| let isFlag = true; | ||
| for (const arg of args) { | ||
| if (isFlag && arg === "--") { | ||
| isFlag = false; | ||
| } else if (isFlag && arg.startsWith("-") && arg !== "-") { | ||
| // capture the flags without the - | ||
| // supports combined flags like -1a | ||
| for (const ch of arg.slice(1)) { | ||
| flags.add(ch); | ||
| } | ||
| } else { | ||
| paths.push(arg); | ||
| } | ||
| } | ||
|
|
||
| if (paths.length === 0) { | ||
| paths.push("."); | ||
| } | ||
|
|
||
| // returns all entries for a given path | ||
| // if -a flag, then include dotfiles, else exclude dotfiles | ||
| function getPathEntries(path, aFlag = flags.has("a")) { | ||
| let entries = fs.readdirSync(path); | ||
|
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. Compare node ls.js -1 -a sample-files to the real ls -1 -a sample-files, line by line. Which two entries does the real one show that yours doesn't? Does the fs.readdirSync call on line 31 ever return those, and if not, where could they come from?
Author
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. You mean this? Those are current and parent directories. I figured it'd be ok to leave them out as they're not captured by process. I've added them now. function getPathEntries(path, aFlag = flags.has("a")) {
let entries = fs.readdirSync(path);
entries = [".", "..", ...entries];
if (!aFlag) {
entries = entries.filter((e) => !e.startsWith("."));
}
return entries;
}Appears to be working as expected. |
||
| entries = [".", "..", ...entries]; | ||
| entries.sort(); | ||
|
|
||
|
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. Your output matches here, but is the order fs.readdirSync (line 31) returns entries in actually guaranteed? What does real ls do to its list before printing — and would yours still match on a machine that read the directory in a different order?
Author
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. No it's not guaranteed. I made an assumption given the entries appears to be in the right order each time. Real ls orders the path strings alphabetically before output. I have now added a line to sort the entries. function getPathEntries(path, aFlag = flags.has("a")) {
let entries = fs.readdirSync(path);
entries = [".", "..", ...entries];
entries.sort(); // sort before output.
if (!aFlag) {
entries = entries.filter((e) => !e.startsWith("."));
}
return entries;
} |
||
| if (!aFlag) { | ||
| entries = entries.filter((e) => !e.startsWith(".")); | ||
| } | ||
| return entries; | ||
| } | ||
|
|
||
| // formatter: if -1 flag, print entry per line | ||
| // else all in one line with | ||
| function printEntries(entries, onePerLineFlag = flags.has("1")) { | ||
| if (onePerLineFlag) { | ||
| entries.forEach((e) => console.log(e)); | ||
| } else { | ||
| // if join on empty entries arr, add extra blank line | ||
| if (entries.length !== 0) { | ||
| console.log(entries.join("\t")); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // this is needed to group files at the top and folers at the bottom when giving mutiple path arguements | ||
| // to argv e.g. node ls.js sample-files/* | ||
| const fileArgs = paths.filter((p) => !fs.statSync(p).isDirectory()); | ||
| const dirArgs = paths.filter((p) => fs.statSync(p).isDirectory()); | ||
|
|
||
| // First print all plain file arguments together, as one group | ||
| if (fileArgs.length > 0) { | ||
| printEntries(fileArgs); | ||
| } | ||
|
|
||
| // Then print each directory's listing, with headers if needed | ||
| dirArgs.forEach((path, index) => { | ||
| if (paths.length > 1) { | ||
| if (index > 0 || fileArgs.length > 0) console.log(""); | ||
| console.log(`${path}:`); | ||
| } | ||
| printEntries(getPathEntries(path)); | ||
| }); | ||
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,16 @@ | ||
| { | ||
| "name": "wc", | ||
| "version": "1.0.0", | ||
| "description": "You should already be familiar with the `wc` command line tool.", | ||
| "main": "wc.js", | ||
| "scripts": { | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
| }, | ||
| "keywords": [], | ||
| "author": "", | ||
| "license": "ISC", | ||
| "type": "module", | ||
| "dependencies": { | ||
| "commander": "^15.0.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import { program } from "commander"; | ||
| import fs from "node:fs"; | ||
| import process from "node:process"; | ||
|
|
||
| program | ||
| .name("wc") | ||
| .description("Mimics the wc command line tool") | ||
| .option("-w") | ||
| .option("-c") | ||
| .option("-l") | ||
| .argument("<files...>", "files to process"); | ||
|
|
||
| program.parse(); | ||
|
|
||
| const options = program.opts(); | ||
| const paths = program.args; | ||
|
|
||
| // if no -lwc flags are supplied, wc prints | ||
| // lines, words, bytes of each file | ||
| // whereas if any flags are supplied only those | ||
| // values are printed | ||
| if (Object.keys(options).length === 0) { | ||
| options.l = options.w = options.c = true; | ||
| } | ||
|
|
||
| // keeps track of total values for each of the data | ||
| // incrementally updated in the loop below | ||
| const totals = { l: 0, w: 0, c: 0 }; | ||
|
|
||
| // wc actually prints total if there is more than one argument, not more than one file | ||
| // e.g. wc invalid.txt valid.txt | ||
| // there will be a total line with the same stats are the valid.txt stats | ||
| let fileCount = 0; | ||
|
|
||
| for (const path of paths) { | ||
| let isDir; | ||
| try { | ||
| isDir = fs.statSync(path).isDirectory(); | ||
| } catch (err) { | ||
| console.error(`wc: ${path} open: No such file or directory`); | ||
| fileCount++; | ||
| continue; | ||
| } | ||
|
|
||
| if (isDir) { | ||
| console.eror(`wc: ${path}: read: Is a directory`); | ||
| } else { | ||
| fileCount++; | ||
| let outputStr = ""; | ||
| const file = fs.readFileSync(path, "utf-8"); | ||
| if (options.l) { | ||
| const lines = file.split("\n"); | ||
| // exclude trailing empty line from count | ||
| if (lines.at(-1) === "") { | ||
| lines.pop(); | ||
| } | ||
| const lineCount = lines.length; | ||
| totals.l += lineCount; | ||
| outputStr += `\t${lineCount}`; | ||
| } | ||
|
|
||
| if (options.w) { | ||
| // real wc splits not just on " ", but on white spaces more generally | ||
| const words = file.split(/\s+/).filter(Boolean); | ||
| const wordCount = words.length; | ||
| totals.w += wordCount; | ||
| outputStr += `\t${wordCount}`; | ||
| } | ||
|
|
||
| if (options.c) { | ||
| file.size; | ||
| const byteCount = fs.statSync(path).size; | ||
| totals.c += byteCount; | ||
| outputStr += `\t${byteCount}`; | ||
| } | ||
|
|
||
| outputStr += ` ${path}`; | ||
| console.log(outputStr); | ||
| } | ||
| } | ||
|
|
||
| // if there's more than one file, print out a total | ||
| // if a flag is not selected, then the value for that flag is 0 | ||
| // filter out anything with a total of 0 | ||
| if (fileCount > 1) { | ||
| const totalsArr = Object.values(totals).filter((elem) => elem !== 0); | ||
| console.log(`\t${totalsArr.join("\t")} total`); | ||
| } |
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 declare let lineNum = 1 inside the for (const path of paths) loop. When you run cat -n sample-files/*.txt against the real cat, does the numbering restart at 1 for each file, or keep climbing across all of them? Where would lineNum need to be declared to match what you see?
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.
I don't see the difference. Other than the spacing at the start of each line.
I initally had the
lineNumoutside the loop so that the numbers would increment across files.You can see this in the commit 5e36e1fccc18b08a5495f9ede132c9ed0b0cfa4b