/* * 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; }