master xplshn/aruu / cmd / posix / nl.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <limits.h>
  4#include <stdint.h>
  5#include <stdio.h>
  6#include <stdlib.h>
  7#include <string.h>
  8
  9#include "text.h"
 10#include "utf.h"
 11#include "util.h"
 12
 13static size_t  startnum  = 1;
 14static size_t  incr      = 1;
 15static size_t  blines    = 1;
 16static size_t  delimlen  = 2;
 17static size_t  seplen    = 1;
 18static int     width     = 6;
 19static int     pflag     = 0;
 20static char    type[]    = {'n', 't', 'n'}; /* footer, body, header */
 21static char   *delim     = "\\:";
 22static char    format[6] = "%*ld";
 23static char   *sep       = "\t";
 24static regex_t preg[3];
 25
 26static int
 27getsection(struct line *l, int *section)
 28{
 29  size_t i;
 30  int    sectionchanged = 0, newsection = *section;
 31
 32  for (i = 0; (l->len - i) >= delimlen && !memcmp(l->data + i, delim, delimlen); i += delimlen) {
 33    if (!sectionchanged) {
 34      sectionchanged = 1;
 35      newsection     = 0;
 36    } else {
 37      newsection = (newsection + 1) % 3;
 38    }
 39  }
 40
 41  if (!(l->len - i) || l->data[i] == '\n')
 42    *section = newsection;
 43  else
 44    sectionchanged = 0;
 45
 46  return sectionchanged;
 47}
 48
 49static void
 50nl(const char *fname, FILE *fp)
 51{
 52  static struct line line;
 53  static size_t      size;
 54  size_t             number = startnum, bl = 1;
 55  ssize_t            len;
 56  int                donumber, oldsection, section = 1;
 57
 58  while ((len = getline(&line.data, &size, fp)) > 0) {
 59    line.len   = len;
 60    donumber   = 0;
 61    oldsection = section;
 62
 63    if (getsection(&line, &section)) {
 64      if ((section >= oldsection) && !pflag)
 65        number = startnum;
 66      continue;
 67    }
 68
 69    switch (type[section]) {
 70      case 't':
 71        if (line.data[0] != '\n')
 72          donumber = 1;
 73        break;
 74        // ?man -p: preserve file attributes
 75      case 'p':
 76        if (!regexec(preg + section, line.data, 0, NULL, 0))
 77          donumber = 1;
 78        break;
 79      case 'a':
 80        if (line.data[0] == '\n' && bl < blines) {
 81          ++bl;
 82        } else {
 83          donumber = 1;
 84          bl       = 1;
 85        }
 86    }
 87
 88    if (donumber) {
 89      printf(format, width, number);
 90      fwrite(sep, 1, seplen, stdout);
 91      number += incr;
 92    }
 93    fwrite(line.data, 1, line.len, stdout);
 94  }
 95  free(line.data);
 96  if (ferror(fp))
 97    eprintf("getline %s:", fname);
 98}
 99
100static void
101usage(void)
102{
103  eprintf(
104      "usage: %s [-p] [-b type] [-d delim] [-f type]\n"
105      "       [-h type] [-i num] [-l num] [-n format]\n"
106      "       [-s sep] [-v num] [-w num] [file]\n",
107      argv0
108  );
109}
110
111static char
112getlinetype(char *type, regex_t *preg)
113{
114  if (type[0] == 'p')
115    eregcomp(preg, type + 1, REG_NOSUB);
116  else if (!type[0] || !strchr("ant", type[0]))
117    usage();
118
119  return type[0];
120}
121
122// ?man nl: number lines
123// ?man number the lines of files
124int
125main(int argc, char *argv[])
126{
127  FILE  *fp = NULL;
128  size_t s;
129  int    ret = 0;
130  char  *d, *formattype, *formatblit;
131
132  ARGBEGIN
133  {
134    // ?man -d:str: specify directory
135    case 'd':
136      switch (utflen((d = EARGF(usage())))) {
137        case 0:
138          eprintf("empty logical page delimiter\n");
139          break;
140        case 1:
141          s     = strlen(d);
142          delim = emalloc(s + 1 + 1);
143          estrlcpy(delim, d, s + 1 + 1);
144          estrlcat(delim, ":", s + 1 + 1);
145          delimlen = s + 1;
146          break;
147        default:
148          delim    = d;
149          delimlen = strlen(delim);
150          break;
151      }
152      break;
153    // ?man -f:str: force the operation
154    case 'f':
155      type[0] = getlinetype(EARGF(usage()), preg);
156      break;
157    // ?man -b:str: specify block size or base directory
158    case 'b':
159      type[1] = getlinetype(EARGF(usage()), preg + 1);
160      break;
161    // ?man -h:str: suppress headers or print help
162    case 'h':
163      type[2] = getlinetype(EARGF(usage()), preg + 2);
164      break;
165    // ?man -i:num: interactive mode or prompt for confirmation
166    case 'i':
167      incr = estrtonum(
168          EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX)
169      );
170      break;
171    // ?man -l:num: list in long format
172    case 'l':
173      blines = estrtonum(
174          EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX)
175      );
176      break;
177    // ?man -n:str: print line numbers or counts
178    case 'n':
179      formattype = EARGF(usage());
180      estrlcpy(format, "%", sizeof(format));
181
182      if (!strcmp(formattype, "ln")) {
183        formatblit = "-";
184      } else if (!strcmp(formattype, "rn")) {
185        formatblit = "";
186      } else if (!strcmp(formattype, "rz")) {
187        formatblit = "0";
188      } else {
189        eprintf("%s: bad format\n", formattype);
190      }
191
192      estrlcat(format, formatblit, sizeof(format));
193      estrlcat(format, "*ld", sizeof(format));
194      break;
195    // ?man -p: preserve file attributes
196    case 'p':
197      pflag = 1;
198      break;
199    // ?man -s:str: silent mode or print summary
200    case 's':
201      sep    = EARGF(usage());
202      seplen = unescape(sep);
203      break;
204    // ?man -v:num: verbose mode; show progress
205    case 'v':
206      startnum = estrtonum(
207          EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX)
208      );
209      break;
210    // ?man -w:num: wait for completion
211    case 'w':
212      width = estrtonum(EARGF(usage()), 1, INT_MAX);
213      break;
214    default:
215      usage();
216  }
217  ARGEND
218
219  if (argc > 1)
220    usage();
221
222  if (!argc) {
223    nl("<stdin>", stdin);
224  } else {
225    if (!strcmp(argv[0], "-")) {
226      argv[0] = "<stdin>";
227      fp      = stdin;
228    } else if (!(fp = fopen(argv[0], "r"))) {
229      eprintf("fopen %s:", argv[0]);
230    }
231    nl(argv[0], fp);
232  }
233
234  ret |= fp && fp != stdin && fshut(fp, argv[0]);
235  ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
236
237  return ret;
238}