mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Add nl(1).
This commit is contained in:
parent
a8f8b4cfd6
commit
b52bfa5978
3 changed files with 424 additions and 0 deletions
1
utils/.gitignore
vendored
1
utils/.gitignore
vendored
|
@ -32,6 +32,7 @@ memstat
|
|||
mkdir
|
||||
mktemp
|
||||
mv
|
||||
nl
|
||||
pager
|
||||
passwd
|
||||
ps
|
||||
|
|
|
@ -43,6 +43,7 @@ memstat \
|
|||
mkdir \
|
||||
mktemp \
|
||||
mv \
|
||||
nl \
|
||||
pager \
|
||||
passwd \
|
||||
ps \
|
||||
|
|
422
utils/nl.c
Normal file
422
utils/nl.c
Normal file
|
@ -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 <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;
|
||||
}
|
Loading…
Reference in a new issue