commit b26b6ca20289f22698b0fa830d665e6df85ea977
parent ba12277a0d039cb8659a5f24942779524d034780
Author: Byron Torres <b@torresjrjr.com>
Date: Mon, 13 Sep 2021 14:49:25 +0100
nl: impl all options, paging, and parametric mods
This is a near-full implementation of POSIX nl, with the exception of
the `-b p` option-argument.
This commit depends on hare fmt parametric modifiers (revision v3).
Diffstat:
M | nl.ha | | | 220 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
1 file changed, 177 insertions(+), 43 deletions(-)
diff --git a/nl.ha b/nl.ha
@@ -1,3 +1,4 @@
+use ascii;
use bufio;
use fmt;
use fs;
@@ -6,9 +7,32 @@ use io;
use main;
use os;
use strconv;
+use strings;
+use types;
+
+type section = enum {
+ HEAD,
+ BODY,
+ FOOT,
+};
+
+type style = enum {
+ ALL, // number all lines
+ TXT, // number lines containing non-whitespace text
+ NON, // number no lines
+};
+
+type context = struct {
+ linenum: int,
+ conblanks: uint, // counter of consecutive blank lines
+ maxblanks: uint, // 1 + maximum run of unnumbered blank lines
+ sep: str,
+ incr: int,
+ mod: *fmt::modifiers,
+};
export fn utilmain() (void | main::error) = {
- const helplist: [_]getopt::help = [
+ const help: [_]getopt::help = [
"line numbering filter",
('p', "do not restart numbering at logical page delimiters"),
('b', "type", "set body line numbering strategy"),
@@ -21,55 +45,94 @@ export fn utilmain() (void | main::error) = {
('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]"
+ "[file]",
];
- const cmd = getopt::parse(os::args, helplist...);
+ const cmd = getopt::parse(os::args, help...);
defer getopt::finish(&cmd);
- let incr = 1;
- let sep = "\t";
- let width: uint = 6;
+ static const delim_buf: [2]u8 = [0, 0];
+ let delim = "\\:";
+
+ let head_style = style::NON;
+ let body_style = style::TXT;
+ let foot_style = style::NON;
+
+ let paged_numbering = true;
let startnum = 1;
+ const ctx = context {
+ conblanks = 0,
+ maxblanks = 1,
+ sep = "\t",
+ incr = 1,
+ mod = &fmt::modifiers {
+ width = 6,
+ padding = fmt::padding::ALIGN_RIGHT,
+ ...
+ },
+ };
+
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"),
+ 'p' => paged_numbering = false,
+ 'b' => body_style = switch (opt.1) {
+ * => usage(help),
+ "a" => style::ALL,
+ "t" => style::TXT,
+ "n" => style::NON,
+ },
+ 'd' => delim = switch (len(opt.1)) {
+ * => usage(help),
+ 1 => fmt::bsprintf(delim_buf, "{}:", opt.1),
+ 2 => opt.1,
+ },
+ 'f' => foot_style = switch (opt.1) {
+ * => usage(help),
+ "a" => style::ALL,
+ "t" => style::TXT,
+ "n" => style::NON,
+ },
+ 'h' => head_style = switch (opt.1) {
+ * => usage(help),
+ "a" => style::ALL,
+ "t" => style::TXT,
+ "n" => style::NON,
+ },
+ 'i' => ctx.incr = match (strconv::stoi(opt.1)) {
+ (strconv::invalid | strconv::overflow) => usage(help),
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,
+ 'l' => ctx.maxblanks = match (strconv::stou(opt.1)) {
+ (strconv::invalid | strconv::overflow) => usage(help),
+ maxblanks: uint => switch (maxblanks > 0) {
+ false => usage(help),
+ true => maxblanks,
+ },
},
- '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);
+ 'n' => ctx.mod.padding = switch (opt.1) {
+ * => usage(help),
+ "ln" => fmt::padding::ALIGN_LEFT,
+ "rn" => fmt::padding::ALIGN_RIGHT,
+ "rz" => fmt::padding::ZEROES,
+ },
+ 's' => ctx.sep = opt.1,
+ 'w' => ctx.mod.width = match (strconv::stou(opt.1)) {
+ (strconv::invalid | strconv::overflow) => usage(help),
+ width: uint => switch (width > 0) {
+ false => usage(help),
+ true => width,
},
+ },
+ 'v' => startnum = match (strconv::stoi(opt.1)) {
+ (strconv::invalid | strconv::overflow) => usage(help),
startnum: int => startnum,
},
};
};
if (len(cmd.args) > 1) {
- getopt::printusage(os::stderr, os::args[0], helplist);
- os::exit(1);
+ usage(help);
};
const use_file = len(cmd.args) == 1 && cmd.args[0] != "-";
@@ -87,24 +150,95 @@ export fn utilmain() (void | main::error) = {
};
defer io::close(input);
- // prepares line number format string, e.g.: "{0:6}{1}"
- const fmtstr = fmt::asprintf("{{0:{0}}}{{1}}", width);
+ static const delim_head_buf: [2 * 3]u8 = [0...];
+ static const delim_body_buf: [2 * 2]u8 = [0...];
+ const delim_head = fmt::bsprintf(delim_head_buf, "{0}{0}{0}", delim);
+ const delim_body = fmt::bsprintf(delim_body_buf, "{0}{0}", delim);
+ const delim_foot = delim;
- let linenum = startnum;
+ let section = section::BODY;
+ ctx.linenum = startnum;
for (true) {
- const line = match (bufio::scanline(input)) {
+ const rawline = match (bufio::scanline(input)) {
err: io::error => return err,
io::EOF => break,
- line: []u8 => line,
+ rawline: []u8 => rawline,
+ };
+ defer free(rawline);
+ const line = fmt::bsprint(rawline);
+
+ if (line == delim_head) {
+ section = section::HEAD;
+ fmt::println()?;
+ if (paged_numbering == true)
+ ctx.linenum = startnum;
+ continue;
+ };
+ if (line == delim_body) {
+ section = section::BODY;
+ fmt::println()?;
+ continue;
+ };
+ if (line == delim_foot) {
+ section = section::FOOT;
+ fmt::println()?;
+ continue;
};
- defer free(line);
- if (len(line) != 0) {
- fmt::printf(fmtstr, linenum, sep)?;
- linenum += incr;
+ switch (section) {
+ section::HEAD => println(line, head_style, &ctx),
+ section::BODY => println(line, body_style, &ctx),
+ section::FOOT => println(line, foot_style, &ctx),
+ };
+ };
+};
+
+fn println(line: str, style: style, ctx: *context) void = {
+ switch (style) {
+ style::ALL => {
+ if (!isblank(line)) {
+ fmt::printf("{%}", ctx.linenum, ctx.mod)?;
+ fmt::print(ctx.sep)?;
+ ctx.linenum += ctx.incr;
+ ctx.conblanks = 0;
+ } else {
+ ctx.conblanks += 1;
+ if (ctx.conblanks == ctx.maxblanks) {
+ fmt::printf("{%}", ctx.linenum, ctx.mod)?;
+ fmt::print(ctx.sep)?;
+ ctx.linenum += ctx.incr;
+ ctx.conblanks = 0;
+ };
+ };
+ },
+ style::TXT => {
+ if (!isblank(line)) {
+ fmt::printf("{%}", ctx.linenum, ctx.mod)?;
+ fmt::print(ctx.sep)?;
+ ctx.linenum += ctx.incr;
+ };
+ },
+ style::NON => void,
+ };
+ fmt::println(line)?;
+};
+
+fn isblank(line: str) bool = {
+ const iter = strings::iter(line);
+ for (true) {
+ const r = match (strings::next(&iter)) {
+ r: rune => r,
+ void => break,
+ };
+ if (!ascii::isspace(r)) {
+ return false;
};
- io::write(os::stdout, line)?;
- fmt::println()?;
};
+ return true;
+};
+
+@noreturn fn usage(help: []getopt::help) void = {
+ getopt::printusage(os::stderr, os::args[0], help);
+ os::exit(1);
};