commit c1f3bd94162c07f4890655a426c2262adb4ecd57
parent f7db4cf8582d596d7c81c04f2b63642111cddf02
Author: Byron Torres <b@torresjrjr.com>
Date: Fri, 20 Aug 2021 11:16:51 +0100
nl: new command
Only implements -iswv flags for now.
Does not implement logical page delimiters.
Diffstat:
M | .gitignore | | | 5 | +++-- |
M | Makefile | | | 2 | ++ |
A | nl.ha | | | 110 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 115 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,6 +1,7 @@
cat
false
-true
+head
+nl
tee
+true
uname
-head
diff --git a/Makefile b/Makefile
@@ -7,6 +7,7 @@ utils=\
cat \
false \
head \
+ nl \
tee \
true \
uname
@@ -25,6 +26,7 @@ clean:
cat: cat.ha main/main.ha
false: false.ha
head: head.ha
+nl: nl.ha
tee: tee.ha
true: true.ha
uname: uname.ha
diff --git a/nl.ha b/nl.ha
@@ -0,0 +1,110 @@
+use bufio;
+use fmt;
+use fs;
+use getopt;
+use io;
+use main;
+use os;
+use strconv;
+
+export fn utilmain() (void | main::error) = {
+ const helplist: [_]getopt::help = [
+ "line numbering filter",
+ ('p', "do not restart numbering at logical page delimiters"),
+ ('b', "type", "set body line numbering strategy"),
+ ('d', "delim", "set delimiter for new pages"),
+ ('f', "type", "set footer line numbering strategy"),
+ ('h', "type", "set header line numbering strategy"),
+ ('i', "incr", "amount to increment each page"),
+ ('l', "num", "number of consecutive blank lines considered one"),
+ ('n', "format", "set line number format"),
+ ('s', "sep", "set separator to place between line number and text"),
+ ('v', "startnum", "initial value for each page"),
+ ('w', "width", "number of columns for numbers"),
+ "[file]"
+ ];
+ const cmd = getopt::parse(os::args, helplist...);
+ defer getopt::finish(&cmd);
+
+ let incr = 1;
+ let sep = "\t";
+ let width: uint = 6;
+ let startnum = 1;
+
+ for (let i = 0z; i < len(cmd.opts); i += 1) {
+ const opt = cmd.opts[i];
+ switch (opt.0) {
+ 'b' => abort("TODO: implement -b flag"),
+ 'd' => abort("TODO: implement -d flag"),
+ 'f' => abort("TODO: implement -f flag"),
+ 'h' => abort("TODO: implement -h flag"),
+ 'i' => incr = match (strconv::stoi(opt.1)) {
+ (strconv::invalid | strconv::overflow) =>
+ fmt::fatal("Error: -i: argument must be an integer"),
+ incr: int => incr,
+ },
+ 'l' => abort("TODO: implement -l flag"),
+ 'n' => abort("TODO: implement -n flag"),
+ 'p' => abort("TODO: implement -p flag"),
+ 's' => sep = opt.1,
+ 'w' => width = match (strconv::stou(opt.1)) {
+ (strconv::invalid | strconv::overflow) =>
+ fmt::fatal("Error: -w: argument must be an ingeter"),
+ width: uint =>
+ if (width <= 0)
+ fmt::fatal("Error: -w: width must be greater than 0")
+ else
+ width,
+ },
+ 'v' => startnum = match (strconv::stoi(opt.1)) {
+ (strconv::invalid | strconv::overflow) => {
+ fmt::errorln("Error: -v: argument must be an integer")?;
+ getopt::printusage(os::stderr, os::args[0], helplist);
+ os::exit(1);
+ },
+ startnum: int => startnum,
+ },
+ };
+ };
+
+ if (len(cmd.args) > 1) {
+ getopt::printusage(os::stderr, os::args[0], helplist);
+ os::exit(1);
+ };
+
+ const use_file = len(cmd.args) == 1 && cmd.args[0] != "-";
+ const input = switch (use_file) {
+ true => match (os::open(cmd.args[0])) {
+ err: fs::error => fmt::fatal("Error opening '{}': {}",
+ cmd.args[0], fs::strerror(err)),
+ file: *io::stream => {
+ static const rbuf: [os::BUFSIZ]u8 = [0...];
+ static const wbuf: [os::BUFSIZ]u8 = [0...];
+ bufio::buffered(file, rbuf, wbuf);
+ },
+ },
+ false => os::stdin,
+ };
+ defer io::close(input);
+
+ // prepares line number format string, e.g.: "{0:6}{1}"
+ const fmtstr = fmt::asprintf("{{0:{0}}}{{1}}", width);
+
+ let linenum = startnum;
+
+ for (true) {
+ const line = match (bufio::scanline(input)) {
+ err: io::error => return err,
+ io::EOF => break,
+ line: []u8 => line,
+ };
+ defer free(line);
+
+ if (len(line) != 0) {
+ fmt::printf(fmtstr, linenum, sep)?;
+ linenum += incr;
+ };
+ io::write(os::stdout, line)?;
+ fmt::println()?;
+ };
+};