master xplshn/aruu / cmd / posix / cut.c
  1/* see LICENSE file for copyright and license details */
  2
  3#include <stdio.h>
  4#include <stdlib.h>
  5#include <string.h>
  6
  7#include "text.h"
  8#include "utf.h"
  9#include "util.h"
 10
 11struct Range {
 12  size_t        min, max;
 13  struct Range *next;
 14};
 15
 16static struct Range *list     = NULL;
 17static char          mode     = 0;
 18static char         *delim    = "\t";
 19static size_t        delimlen = 1;
 20static int           nflag    = 0;
 21static int           sflag    = 0;
 22
 23static void
 24insert(struct Range *r)
 25{
 26  struct Range *l, *p, *t;
 27
 28  for (p = NULL, l = list; l; p = l, l = l->next) {
 29    if (r->max && r->max + 1 < l->min) {
 30      r->next = l;
 31      break;
 32    } else if (!l->max || r->min < l->max + 2) {
 33      l->min = MIN(r->min, l->min);
 34      for (p = l, t = l->next; t; p = t, t = t->next)
 35        if (r->max && r->max + 1 < t->min)
 36          break;
 37      l->max  = (p->max && r->max) ? MAX(p->max, r->max) : 0;
 38      l->next = t;
 39      return;
 40    }
 41  }
 42  if (p)
 43    p->next = r;
 44  else
 45    list = r;
 46}
 47
 48static void
 49parselist(char *str)
 50{
 51  char         *s;
 52  size_t        n = 1;
 53  struct Range *r;
 54
 55  if (!*str)
 56    eprintf("empty list\n");
 57  for (s = str; *s; s++) {
 58    if (*s == ' ')
 59      *s = ',';
 60    if (*s == ',')
 61      n++;
 62  }
 63  r = ereallocarray(NULL, n, sizeof(*r));
 64  for (s = str; n; n--, s++) {
 65    r->min  = (*s == '-') ? 1 : strtoul(s, &s, 10);
 66    r->max  = (*s == '-') ? strtoul(s + 1, &s, 10) : r->min;
 67    r->next = NULL;
 68    if (!r->min || (r->max && r->max < r->min) || (*s && *s != ','))
 69      eprintf("bad list value\n");
 70    insert(r++);
 71  }
 72}
 73
 74static size_t
 75seek(struct line *s, size_t pos, size_t *prev, size_t count)
 76{
 77  size_t n = pos - *prev, i, j;
 78
 79  if (mode == 'b') {
 80    if (n >= s->len)
 81      return s->len;
 82    if (nflag)
 83      while (n && !UTF8_POINT(s->data[n]))
 84        n--;
 85    *prev += n;
 86    return n;
 87  } else if (mode == 'c') {
 88    for (n++, i = 0; i < s->len; i++)
 89      if (UTF8_POINT(s->data[i]) && !--n)
 90        break;
 91  } else {
 92    for (i = (count < delimlen + 1) ? 0 : delimlen; n && i < s->len;) {
 93      if ((s->len - i) >= delimlen && !memcmp(s->data + i, delim, delimlen)) {
 94        if (!--n && count)
 95          break;
 96        i += delimlen;
 97        continue;
 98      }
 99      for (j = 1; j + i <= s->len && !fullrune(s->data + i, j); j++)
100        ;
101      i += j;
102    }
103  }
104  *prev = pos;
105
106  return i;
107}
108
109static void
110cut(FILE *fp, const char *fname)
111{
112  struct Range       *r;
113
114  struct line        s;
115  static struct line line;
116  static size_t      size;
117  size_t             i, n, p;
118  ssize_t            len;
119
120  while ((len = getline(&line.data, &size, fp)) > 0) {
121    line.len = len;
122    if (line.data[line.len - 1] == '\n')
123      line.data[--line.len] = '\0';
124    if (mode == 'f' && !memmem(line.data, line.len, delim, delimlen)) {
125      if (!sflag) {
126        fwrite(line.data, 1, line.len, stdout);
127        fputc('\n', stdout);
128      }
129      continue;
130    }
131    for (i = 0, p = 1, s = line, r = list; r; r = r->next) {
132      n = seek(&s, r->min, &p, i);
133      s.data += n;
134      s.len -= n;
135      i += (mode == 'f') ? delimlen : 1;
136      if (!s.len)
137        break;
138      if (!r->max) {
139        fwrite(s.data, 1, s.len, stdout);
140        break;
141      }
142      n = seek(&s, r->max + 1, &p, i);
143      i += (mode == 'f') ? delimlen : 1;
144      if (fwrite(s.data, 1, n, stdout) != n)
145        eprintf("fwrite <stdout>:");
146      s.data += n;
147      s.len -= n;
148    }
149    putchar('\n');
150  }
151  if (ferror(fp))
152    eprintf("getline %s:", fname);
153}
154
155static void
156usage(void)
157{
158  eprintf(
159      "usage: %s -b list [-n] [file ...]\n"
160      "       %s -c list [file ...]\n"
161      "       %s -f list [-d delim] [-s] [file ...]\n",
162      argv0,
163      argv0,
164      argv0
165  );
166}
167
168// ?man cut: cut out fields from lines
169// ?man arguments: -b list [file ...]
170// ?man print selected parts of lines from files
171int
172main(int argc, char *argv[])
173{
174  FILE *fp;
175  int   ret = 0;
176
177  ARGBEGIN
178  {
179    // ?man -b: specify block size or base directory
180    case 'b':
181    // ?man -c: print count or perform stdout action
182    case 'c':
183    // ?man -f:mode: force the operation
184    case 'f':
185      mode = ARGC();
186      parselist(EARGF(usage()));
187      break;
188    // ?man -d:str: specify directory
189    case 'd':
190      delim = EARGF(usage());
191      if (!*delim)
192        eprintf("empty delimiter\n");
193      delimlen = unescape(delim);
194      break;
195    // ?man -n: print line numbers or counts
196    case 'n':
197      nflag = 1;
198      break;
199    // ?man -s: silent mode or print summary
200    case 's':
201      sflag = 1;
202      break;
203    default:
204      usage();
205  }
206  ARGEND
207
208  if (!mode)
209    usage();
210
211  if (!argc)
212    cut(stdin, "<stdin>");
213  else {
214    for (; *argv; argc--, argv++) {
215      if (!strcmp(*argv, "-")) {
216        *argv = "<stdin>";
217        fp    = stdin;
218      } else if (!(fp = fopen(*argv, "r"))) {
219        weprintf("fopen %s:", *argv);
220        ret = 1;
221        continue;
222      }
223      cut(fp, *argv);
224      if (fp != stdin && fshut(fp, *argv))
225        ret = 1;
226    }
227  }
228
229  ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
230
231  return ret;
232}