10f66f451Sopenharmony_ci/* paste.c - Merge corresponding lines
20f66f451Sopenharmony_ci *
30f66f451Sopenharmony_ci * Copyright 2012 Felix Janda <felix.janda@posteo.de>
40f66f451Sopenharmony_ci *
50f66f451Sopenharmony_ci * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/paste.html
60f66f451Sopenharmony_ci *
70f66f451Sopenharmony_ci * Deviations from posix: the FILE argument isn't mandatory, none == '-'
80f66f451Sopenharmony_ci
90f66f451Sopenharmony_ciUSE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
100f66f451Sopenharmony_ci
110f66f451Sopenharmony_ciconfig PASTE
120f66f451Sopenharmony_ci  bool "paste"
130f66f451Sopenharmony_ci  default y
140f66f451Sopenharmony_ci  help
150f66f451Sopenharmony_ci    usage: paste [-s] [-d DELIMITERS] [FILE...]
160f66f451Sopenharmony_ci
170f66f451Sopenharmony_ci    Merge corresponding lines from each input file.
180f66f451Sopenharmony_ci
190f66f451Sopenharmony_ci    -d	List of delimiter characters to separate fields with (default is \t)
200f66f451Sopenharmony_ci    -s	Sequential mode: turn each input file into one line of output
210f66f451Sopenharmony_ci*/
220f66f451Sopenharmony_ci
230f66f451Sopenharmony_ci#define FOR_paste
240f66f451Sopenharmony_ci#include "toys.h"
250f66f451Sopenharmony_ci
260f66f451Sopenharmony_ciGLOBALS(
270f66f451Sopenharmony_ci  char *d;
280f66f451Sopenharmony_ci
290f66f451Sopenharmony_ci  int files;
300f66f451Sopenharmony_ci)
310f66f451Sopenharmony_ci
320f66f451Sopenharmony_ci// \0 is weird, and -d "" is also weird.
330f66f451Sopenharmony_ci
340f66f451Sopenharmony_cistatic void paste_files(void)
350f66f451Sopenharmony_ci{
360f66f451Sopenharmony_ci  FILE **fps = (void *)toybuf;
370f66f451Sopenharmony_ci  char *dpos, *dstr, *buf, c;
380f66f451Sopenharmony_ci  int i, any, dcount, dlen, len, seq = toys.optflags&FLAG_s;
390f66f451Sopenharmony_ci
400f66f451Sopenharmony_ci  // Loop through lines until no input left
410f66f451Sopenharmony_ci  for (;;) {
420f66f451Sopenharmony_ci
430f66f451Sopenharmony_ci    // Start of each line/file resets delimiter cycle
440f66f451Sopenharmony_ci    dpos = TT.d;
450f66f451Sopenharmony_ci
460f66f451Sopenharmony_ci    for (i = any = dcount = dlen = 0; seq || i<TT.files; i++) {
470f66f451Sopenharmony_ci      size_t blen;
480f66f451Sopenharmony_ci      unsigned wc;
490f66f451Sopenharmony_ci      FILE *ff = seq ? *fps : fps[i];
500f66f451Sopenharmony_ci
510f66f451Sopenharmony_ci      // Read and output line, preserving embedded NUL bytes.
520f66f451Sopenharmony_ci
530f66f451Sopenharmony_ci      buf = 0;
540f66f451Sopenharmony_ci      len = 0;
550f66f451Sopenharmony_ci      if (!ff || 0>=(len = getline(&buf, &blen, ff))) {
560f66f451Sopenharmony_ci        if (ff && ff!=stdin) fclose(ff);
570f66f451Sopenharmony_ci        if (seq) return;
580f66f451Sopenharmony_ci        fps[i] = 0;
590f66f451Sopenharmony_ci        if (!any) continue;
600f66f451Sopenharmony_ci      }
610f66f451Sopenharmony_ci      dcount = any ? 1 : i;
620f66f451Sopenharmony_ci      any = 1;
630f66f451Sopenharmony_ci
640f66f451Sopenharmony_ci      // Output delimiters as necessary: not at beginning/end of line,
650f66f451Sopenharmony_ci      // catch up if first few files had no input but a later one did.
660f66f451Sopenharmony_ci      // Entire line with no input means no output.
670f66f451Sopenharmony_ci
680f66f451Sopenharmony_ci      while (dcount) {
690f66f451Sopenharmony_ci
700f66f451Sopenharmony_ci        // Find next delimiter, which can be "", \n, or UTF8 w/combining chars
710f66f451Sopenharmony_ci        dstr = dpos;
720f66f451Sopenharmony_ci        dlen = 0;
730f66f451Sopenharmony_ci        dcount--;
740f66f451Sopenharmony_ci
750f66f451Sopenharmony_ci        if (!*TT.d) {;}
760f66f451Sopenharmony_ci        else if (*dpos == '\\') {
770f66f451Sopenharmony_ci          if (*++dpos=='0') dpos++;
780f66f451Sopenharmony_ci          else {
790f66f451Sopenharmony_ci            dlen = 1;
800f66f451Sopenharmony_ci            if ((c = unescape(*dpos))) {
810f66f451Sopenharmony_ci              dstr = &c;
820f66f451Sopenharmony_ci              dpos++;
830f66f451Sopenharmony_ci            }
840f66f451Sopenharmony_ci          }
850f66f451Sopenharmony_ci        } else {
860f66f451Sopenharmony_ci          while (0<(dlen = utf8towc(&wc, dpos, 99))) {
870f66f451Sopenharmony_ci            dpos += dlen;
880f66f451Sopenharmony_ci            if (!(dlen = wcwidth(wc))) continue;
890f66f451Sopenharmony_ci            if (dlen<0) dpos = dstr+1;
900f66f451Sopenharmony_ci            break;
910f66f451Sopenharmony_ci          }
920f66f451Sopenharmony_ci          dlen = dpos-dstr;
930f66f451Sopenharmony_ci        }
940f66f451Sopenharmony_ci        if (!*dpos) dpos = TT.d;
950f66f451Sopenharmony_ci
960f66f451Sopenharmony_ci        if (dlen) fwrite(dstr, dlen, 1, stdout);
970f66f451Sopenharmony_ci      }
980f66f451Sopenharmony_ci
990f66f451Sopenharmony_ci      if (0<len) {
1000f66f451Sopenharmony_ci        fwrite(buf, len-(buf[len-1]=='\n'), 1, stdout);
1010f66f451Sopenharmony_ci        free(buf);
1020f66f451Sopenharmony_ci      }
1030f66f451Sopenharmony_ci    }
1040f66f451Sopenharmony_ci
1050f66f451Sopenharmony_ci    // Only need a newline if we output something
1060f66f451Sopenharmony_ci    if (any) xputc('\n');
1070f66f451Sopenharmony_ci    else break;
1080f66f451Sopenharmony_ci  }
1090f66f451Sopenharmony_ci}
1100f66f451Sopenharmony_ci
1110f66f451Sopenharmony_cistatic void do_paste(int fd, char *name)
1120f66f451Sopenharmony_ci{
1130f66f451Sopenharmony_ci  FILE **fps = (void *)toybuf;
1140f66f451Sopenharmony_ci
1150f66f451Sopenharmony_ci  if (!(fps[TT.files++] = (fd ? fdopen(fd, "r") : stdin))) perror_exit(0);
1160f66f451Sopenharmony_ci  if (TT.files >= sizeof(toybuf)/sizeof(FILE *)) perror_exit("tilt");
1170f66f451Sopenharmony_ci  if (toys.optflags&FLAG_s) {
1180f66f451Sopenharmony_ci    paste_files();
1190f66f451Sopenharmony_ci    xputc('\n');
1200f66f451Sopenharmony_ci    TT.files = 0;
1210f66f451Sopenharmony_ci  }
1220f66f451Sopenharmony_ci}
1230f66f451Sopenharmony_ci
1240f66f451Sopenharmony_civoid paste_main(void)
1250f66f451Sopenharmony_ci{
1260f66f451Sopenharmony_ci  if (!(toys.optflags&FLAG_d)) TT.d = "\t";
1270f66f451Sopenharmony_ci
1280f66f451Sopenharmony_ci  loopfiles_rw(toys.optargs, O_RDONLY, 0, do_paste);
1290f66f451Sopenharmony_ci  if (!(toys.optflags&FLAG_s)) paste_files();
1300f66f451Sopenharmony_ci}
131