hautils

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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:
Mnl.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); };