Skip to content

Commit cf8040c

Browse files
committed
ls: add -a (include dotfiles) and accept -1 (one-per-line)
1 parent f495a40 commit cf8040c

File tree

1 file changed

+53
-27
lines changed
  • implement-shell-tools/ls

1 file changed

+53
-27
lines changed

implement-shell-tools/ls/ls.js

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,73 @@
11
#!/usr/bin/env node
2-
// ls.js — ESM basic version (no flags)
3-
// - If no paths: list '.'
4-
// - Excludes dotfiles by default
5-
// - One entry per line, alphabetical
6-
// - Multiple paths: print "path:" header before each directory group
7-
// - GNU-like errors; exit code 1 on any failure
2+
83

94
import fs from "node:fs";
105
import { pathToFileURL } from "node:url";
116

12-
const args = process.argv.slice(2);
13-
const targets = args.length ? args : ["."];
14-
let hadError = false;
7+
const raw = process.argv.slice(2);
158

16-
for (let i = 0; i < targets.length; i++) {
17-
const target = targets[i];
9+
let includeAll = false; // -a
10+
let onePerLine = false; // -1 (format already one-per-line)
11+
const targets = [];
1812

13+
// Parse flags (supports combined like -a1 / -1a). Treat lone "-" as a path.
14+
for (const arg of raw) {
15+
if (arg === "-" || !arg.startsWith("-")) {
16+
targets.push(arg);
17+
continue;
18+
}
19+
if (arg === "-1") {
20+
onePerLine = true;
21+
continue;
22+
}
23+
for (const ch of arg.slice(1)) {
24+
if (ch === "a") includeAll = true;
25+
else if (ch === "1") onePerLine = true;
26+
else {
27+
// ignore unknown short flags
28+
}
29+
}
30+
}
31+
32+
const paths = targets.length ? targets : ["."];
33+
let hadError = false;
34+
35+
for (let i = 0; i < paths.length; i++) {
36+
const p = paths[i];
1937
try {
20-
const stat = await fs.promises.lstat(target);
38+
const st = await fs.promises.lstat(p);
39+
40+
if (st.isDirectory()) {
41+
if (paths.length > 1) console.log(`${p}:`);
2142

22-
if (stat.isDirectory()) {
23-
if (targets.length > 1) console.log(`${target}:`);
43+
const entries = await fs.promises.readdir(p, { withFileTypes: true });
44+
let names = entries.map(d => d.name);
2445

25-
const entries = await fs.promises.readdir(target, { withFileTypes: true });
26-
const names = entries
27-
.map(d => d.name)
28-
.filter(n => !n.startsWith(".")) // no dotfiles (we’ll add -a later)
29-
.sort((a, b) => a.localeCompare(b));
46+
if (!includeAll) {
47+
names = names.filter(n => !n.startsWith("."));
48+
} else {
49+
// mimic `ls -a` by including "." and ".."
50+
names = ["." , "..", ...names];
51+
}
3052

31-
for (const name of names) console.log(name);
53+
names.sort((a, b) => a.localeCompare(b));
54+
for (const name of names) {
55+
// one-per-line output; -1 flag just affirms it
56+
console.log(name);
57+
}
3258

33-
if (targets.length > 1 && i !== targets.length - 1) console.log(""); // blank line between dir groups
59+
if (paths.length > 1 && i !== paths.length - 1) console.log("");
3460
} else {
35-
// plain file or symlink -> print the argument as given
36-
console.log(target);
61+
// file or symlink => print the argument as given
62+
console.log(p);
3763
}
3864
} catch (err) {
3965
if (err?.code === "ENOENT") {
40-
console.error(`ls: cannot access '${target}': No such file or directory`);
66+
console.error(`ls: cannot access '${p}': No such file or directory`);
4167
} else if (err?.code === "EACCES") {
42-
console.error(`ls: cannot open directory '${target}': Permission denied`);
68+
console.error(`ls: cannot open directory '${p}': Permission denied`);
4369
} else {
44-
console.error(`ls: ${target}: ${err?.message || "Error"}`);
70+
console.error(`ls: ${p}: ${err?.message || "Error"}`);
4571
}
4672
hadError = true;
4773
}
@@ -52,5 +78,5 @@ if (hadError) process.exitCode = 1;
5278
// run only when executed directly
5379
const isDirect = import.meta.url === pathToFileURL(process.argv[1]).href;
5480
if (!isDirect) {
55-
// allow import in tests without auto-running
81+
// allow importing in tests without auto-executing
5682
}

0 commit comments

Comments
 (0)