Build a CLI tool
A small wc: it takes file names as arguments, prints line, word and byte counts, and exits with status 1 if a file is missing. It uses process.argv.slice(2), node:fs, node:path, an interface and array methods.
ts
// @args: docs/examples/wc/poem.txt docs/examples/wc/missing.txt docs/examples/wc/notes.txt
// A small `wc`: line, word and byte counts per file, a total, and exit status 1 if any file
// could not be read.
import { existsSync, readFileSync } from "node:fs";
import { basename } from "node:path";
interface Counts {
lines: number;
words: number;
bytes: number;
}
function count(text: string): Counts {
const words = text
.split("\n")
.join(" ")
.split(" ")
.filter((w: string): boolean => w !== "");
return {
lines: text.split("\n").length - 1,
words: words.length,
bytes: text.length, // ASCII input: one byte per character
};
}
function row(c: Counts, label: string): string {
const cols = [c.lines, c.words, c.bytes].map((n: number): string => String(n).padStart(6));
return `${cols.join("")} ${label}`;
}
const files = process.argv.slice(2);
if (files.length === 0) {
console.log("usage: wc <file>...");
process.exit(2);
}
const total: Counts = { lines: 0, words: 0, bytes: 0 };
let failed = false;
for (const file of files) {
if (!existsSync(file)) {
console.log(`wc: ${basename(file)}: no such file`);
failed = true;
continue;
}
const c = count(readFileSync(file, "utf8"));
total.lines += c.lines;
total.words += c.words;
total.bytes += c.bytes;
console.log(row(c, basename(file)));
}
console.log(row(total, "total"));
if (failed) process.exit(1);Build and run
sh
bin/chad build main.ts -o wc
./wc poem.txt missing.txt notes.txt; echo "exit $?"text
5 8 41 poem.txt
wc: missing.txt: no such file
2 7 42 notes.txt
7 15 83 totaltext
exit 1The docs test suite runs exactly this (with the paths on the program's first line) under Node and as a native binary, and compares stdout and the exit code. The input files:
txt
so much depends
upon
a red wheel
barrowtxt
compile once
run anywhere the binary runsThings to know
- Only
process.argv.slice(2)is accepted (CS1229): Node'sargv[0]andargv[1](the node binary and the script path) have no equivalent in a compiled binary. process.exit(code)sets the exit status; an uncaught error exits with 1, as in Node.- The binary starts in about a millisecond, so it is cheap to call from scripts and loops; see Benchmarks.
- Strings are UTF-8 and exact for ASCII. Counting bytes as
text.lengthis right here because the input is ASCII; non-ASCII string literals are rejected for now (CS1216).
Next: Language reference.