Skip to content

Commit 6fd0461

Browse files
Implement 'ls' command alternative with directory listing options
1 parent 775b0b7 commit 6fd0461

1 file changed

Lines changed: 83 additions & 0 deletions

File tree

  • implement-shell-tools/ls

implement-shell-tools/ls/ls.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { program } from "commander";
2+
import { promises as fs } from "node:fs";
3+
import process from "node:process";
4+
5+
// configure the CLI program with its name, description, arguments, options, and actions (the help instructions)
6+
program
7+
.name("ls")
8+
.description("An alternative to the 'ls' command")
9+
.argument("[directory]", "The directory to list")
10+
// Commander stores -1 as a string key that is accessed using options['1']
11+
.option("-1", "List all files, one per line")
12+
.option("-a, --all", "Include hidden files (those starting with .) in the listing")
13+
.action(async (directory, options) => {
14+
if (program.args.length > 1) {
15+
console.error(`Expected no more than 1 argument (sample-files) but got ${program.args.length}.`);
16+
process.exit(1);
17+
}
18+
19+
// default directory to current folder
20+
directory = directory || ".";
21+
22+
await newLs(directory, options['1'], options.all);
23+
});
24+
25+
program.parse(process.argv);
26+
27+
28+
async function newLs(directory, oneFlag, allFlag) {
29+
try {
30+
// check if the path exists
31+
const stats = await fs.stat(directory);
32+
33+
// if it’s a file, just print its name
34+
if (stats.isFile()) {
35+
console.log(directory);
36+
return;
37+
}
38+
39+
// read directory contents
40+
let entries = await fs.readdir(directory);
41+
42+
let finalEntries;
43+
44+
// organize -a output ('.' and '..' first, then visible files, then hidden (starts with a .) files)
45+
if (allFlag) {
46+
const dotEntries = ['.', '..'];
47+
const visibleFiles = entries.filter(name => !name.startsWith('.'));
48+
const hiddenFiles = entries.filter(name => name.startsWith('.') && name !== '.' && name !== '..');
49+
50+
// localeCompare = take into account rules of system language/region for ordering
51+
// undefined = uses the system default, numeric = regular number sorting, base = ignore case & accents
52+
visibleFiles.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
53+
hiddenFiles.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
54+
55+
// combine to a single new array
56+
finalEntries = dotEntries.concat(visibleFiles, hiddenFiles);
57+
} else {
58+
// sort just visible files
59+
finalEntries = entries
60+
.filter(name => !name.startsWith('.'))
61+
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
62+
}
63+
64+
// organize -1 output
65+
if (oneFlag) {
66+
for (const entry of finalEntries) {
67+
console.log(entry);
68+
}
69+
} else {
70+
//no flags (separated by 2 spaces)
71+
console.log(finalEntries.join(' '));
72+
}
73+
74+
} catch (err) {
75+
console.error(`ls: cannot access '${directory}': No such file or directory`);
76+
}
77+
}
78+
79+
80+
81+
82+
83+

0 commit comments

Comments
 (0)