diff --git a/utils/.gitignore b/utils/.gitignore index 96c0cdb3..f1f0b59f 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -32,6 +32,7 @@ memstat mkdir mktemp mv +nl pager passwd ps diff --git a/utils/Makefile b/utils/Makefile index f6087ddc..032db7fc 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -43,6 +43,7 @@ memstat \ mkdir \ mktemp \ mv \ +nl \ pager \ passwd \ ps \ diff --git a/utils/nl.c b/utils/nl.c new file mode 100644 index 00000000..0cb8991a --- /dev/null +++ b/utils/nl.c @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2021 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * nl.c + * Number lines. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum format +{ + LN, + RN, + RZ, +}; + +struct style +{ + char c; + regex_t regex; +}; + +static char delim[] = "\\:\\:\\:"; +static off_t initial = 1; +static off_t increment = 1; +static bool restart = false; +static off_t blank_join = 1; +static enum format format = RN; +static int width = 6; +static const char* separator = "\t"; +static struct style styles[] = +{ + {.c = 'n'}, + {.c = 't'}, + {.c = 'n'}, +}; + +static off_t line_number; +static size_t state = 1; +static off_t blanks = 0; + +static bool nl(FILE* fp, const char* path) +{ + char* line = NULL; + size_t line_size = 0; + ssize_t line_length; + while ( 0 < (line_length = getline(&line, &line_size, fp)) ) + { + if ( line[line_length - 1] == '\n' ) + line[--line_length] = '\0'; + bool error = false; + if ( delim[0] && !strcmp(line, delim) ) + { + state = 0; + if ( restart ) + line_number = initial; + error = printf("\n") == EOF; + } + else if ( delim[0] && !strcmp(line, delim + 2) ) + { + state = 1; + error = printf("\n") == EOF; + } + else if ( delim[0] && !strcmp(line, delim + 4) ) + { + state = 2; + error = printf("\n") == EOF; + } + else + { + const struct style* style = &styles[state]; + bool counts = + style->c == 'a' || + (style->c == 't' && strcmp(line, "") != 0) || + (style->c == 'p' && !regexec(&style->regex, line, 0, NULL, 0)); + if ( counts ) + { + if ( !strcmp(line, "") ) + { + blanks++; + if ( blank_join <= blanks ) + blanks = 0; + else + counts = false; + } + else + blanks = 0; + } + if ( counts ) + { + switch ( format ) + { + case LN: + error = printf("%-*ji%s%s\n", width, (intmax_t) line_number, + separator, line) == EOF; + break; + case RN: + error = printf("%*ji%s%s\n", width, (intmax_t) line_number, + separator, line) == EOF; + break; + case RZ: + error = printf("%0*ji%s%s\n", width, (intmax_t) line_number, + separator, line) == EOF; + break; + } + line_number += increment; + } + else + error = printf("%*s %s\n", width, "", line) == EOF; + } + if ( error ) + { + warn("stdout"); + free(line); + return false; + } + } + free(line); + if ( ferror(fp) ) + { + warn("%s", path); + return false; + } + return true; +} + +static bool nl_path(const char* path) +{ + if ( !strcmp(path, "-") ) + return nl(stdin, path); + FILE* fp = fopen(path, "r"); + if ( !fp ) + { + warn("%s", path); + return false; + } + bool ret = nl(fp, path); + fclose(fp); + return ret; +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +static void set_style(struct style* style, const char* string, const char* opt) +{ + if ( style->c == 'p' ) + regfree(&style->regex); + if ( !strcmp(string, "a") || !strcmp(string, "t") || !strcmp(string, "n") ) + style->c = string[0]; + else if ( string[0] == 'p' || string[0] == 'E' ) + { + int error = regcomp(&style->regex, string + 1, + string[0] == 'E' ? REG_EXTENDED : 0); + if ( error ) + { + size_t size = regerror(error, NULL, NULL, 0); + char* error_string = malloc(size); + if ( !error_string ) + errx(1, "%s: Invalid regular expression: %s", opt, string); + regerror(error, NULL, error_string, size); + errx(1, "%s: %s: %s", opt, error_string, string); + } + style->c = 'p'; + } + else + errx(1, "%s: Invalid style: %s", opt, string); +} + +int main(int argc, char* argv[]) +{ + const char* parameter; + + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + char c; + while ( (c = *++arg) ) switch ( c ) + { + case 'b': + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'b'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + set_style(&styles[1], parameter, "-b"); + arg = "b"; + break; + case 'd': + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'd'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + if ( !parameter[0] ) + memset(delim, 0, sizeof(delim)); + else if ( !parameter[1] ) + { + delim[0] = parameter[0]; + delim[1] = ':'; + delim[2] = parameter[0]; + delim[3] = ':'; + delim[4] = parameter[0]; + delim[5] = ':'; + } + else if ( !parameter[2] ) + { + delim[0] = parameter[0]; + delim[1] = parameter[1]; + delim[2] = parameter[0]; + delim[3] = parameter[1]; + delim[4] = parameter[0]; + delim[5] = parameter[1]; + } + else + errx(1, "-d: Delimiter must be one or two characters"); + arg = "d"; + break; + case 'f': + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'f'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + set_style(&styles[2], parameter, "-f"); + arg = "f"; + break; + case 'h': + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'h'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + set_style(&styles[0], parameter, "-h"); + arg = "h"; + break; + case 'i': + { + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'i'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + char* end; + errno = 0; + intmax_t value = strtoimax(parameter, &end, 10); + if ( value < 0 || (off_t) value != value || *end ) + errno = ERANGE; + if ( errno ) + err(1, "-i: %s", parameter); + increment = (off_t) value; + arg = "i"; + break; + } + case 'l': + { + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'l'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + char* end; + errno = 0; + intmax_t value = strtoimax(parameter, &end, 10); + if ( value < 0 || (off_t) value != value || *end ) + errno = ERANGE; + if ( errno ) + err(1, "-l: %s", parameter); + blank_join = (off_t) value; + arg = "l"; + break; + } + case 'n': + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'n'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + if ( !strcmp(parameter, "ln") ) + format = LN; + else if ( !strcmp(parameter, "rn") ) + format = RN; + else if ( !strcmp(parameter, "rz") ) + format = RZ; + else + errx(1, "-n: Invalid format: %s", parameter); + arg = "n"; + break; + case 'p': restart = false; break; + case 's': + if ( !*(separator = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 's'"); + separator = argv[i+1]; + argv[++i] = NULL; + } + arg = "s"; + break; + case 'v': + { + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'v'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + char* end; + errno = 0; + intmax_t value = strtoimax(parameter, &end, 10); + if ( value < 0 || (off_t) value != value || *end ) + errno = ERANGE; + if ( errno ) + err(1, "-v: %s", parameter); + initial = (off_t) value; + arg = "v"; + break; + } + case 'w': + { + if ( !*(parameter = arg + 1) ) + { + if ( i + 1 == argc ) + errx(2, "option requires an argument -- 'w'"); + parameter = argv[i+1]; + argv[++i] = NULL; + } + char* end; + errno = 0; + long value = strtol(parameter, &end, 10); + if ( value < 0 || (int) value != value || *end ) + errno = ERANGE; + if ( errno ) + err(1, "-w: %s", parameter); + width = (int) value; + arg = "w"; + break; + } + default: + errx(2, "unknown option -- '%c'", c); + } + } + else + errx(2, "unknown option: %s", arg); + } + + compact_arguments(&argc, &argv); + + line_number = initial; + + int ret = 0; + + if ( argc == 1 ) + { + if ( !nl(stdin, "-") ) + ret = 1; + } + else + { + for ( int i = 1; i < argc; i++ ) + { + if ( !nl_path(argv[i]) ) + ret = 1; + } + } + + if ( ferror(stdout) || fflush(stdout) == EOF ) + return 1; + return ret; +}