mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
422 lines
9.1 KiB
C
422 lines
9.1 KiB
C
/*
|
|
* 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 <err.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <regex.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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;
|
|
}
|