master xplshn/aruu / cmd / posix / unexpand.c
  1/* See LICENSE file for copyright and license details. */
  2
  3
  4#include <stdint.h>
  5#include <stdlib.h>
  6#include <string.h>
  7
  8#include "utf.h"
  9#include "util.h"
 10
 11static int     aflag      = 0;
 12static size_t *tablist    = NULL;
 13static size_t  tablistlen = 8;
 14
 15static size_t
 16parselist(const char *s)
 17{
 18	size_t i;
 19	char  *p, *tmp;
 20
 21	tmp = estrdup(s);
 22	for (i = 0; (p = strsep(&tmp, " ,")); i++) {
 23		if (*p == '\0')
 24			eprintf("empty field in tablist\n");
 25		tablist = ereallocarray(tablist, i + 1, sizeof(*tablist));
 26		tablist[i] = estrtonum(p, 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
 27		if (i > 0 && tablist[i - 1] >= tablist[i])
 28			eprintf("tablist must be ascending\n");
 29	}
 30	tablist = ereallocarray(tablist, i + 1, sizeof(*tablist));
 31
 32	return i;
 33}
 34
 35static void
 36unexpandspan(size_t last, size_t col)
 37{
 38	size_t off, i, j;
 39	Rune r;
 40
 41	if (tablistlen == 1) {
 42		i = 0;
 43		off = last % tablist[i];
 44
 45		if ((col - last) + off >= tablist[i] && last < col)
 46			last -= off;
 47
 48		r = '\t';
 49		for (; last + tablist[i] <= col; last += tablist[i])
 50			efputrune(&r, stdout, "<stdout>");
 51		r = ' ';
 52		for (; last < col; last++)
 53			efputrune(&r, stdout, "<stdout>");
 54	} else {
 55		for (i = 0; i < tablistlen; i++)
 56			if (col < tablist[i])
 57				break;
 58		for (j = 0; j < tablistlen; j++)
 59			if (last < tablist[j])
 60				break;
 61		r = '\t';
 62		for (; j < i; j++) {
 63			efputrune(&r, stdout, "<stdout>");
 64			last = tablist[j];
 65		}
 66		r = ' ';
 67		for (; last < col; last++)
 68			efputrune(&r, stdout, "<stdout>");
 69	}
 70}
 71
 72static void
 73unexpand(const char *file, FILE *fp)
 74{
 75	Rune r;
 76	size_t last = 0, col = 0, i;
 77	int bol = 1;
 78
 79	while (efgetrune(&r, fp, file)) {
 80		switch (r) {
 81		case ' ':
 82			if (!bol && !aflag)
 83				last++;
 84			col++;
 85			break;
 86		case '\t':
 87			if (tablistlen == 1) {
 88				if (!bol && !aflag)
 89					last += tablist[0] - col % tablist[0];
 90				col += tablist[0] - col % tablist[0];
 91			} else {
 92				for (i = 0; i < tablistlen; i++)
 93					if (col < tablist[i])
 94						break;
 95				if (!bol && !aflag)
 96					last = tablist[i];
 97				col = tablist[i];
 98			}
 99			break;
100		case '\b':
101			if (bol || aflag)
102				unexpandspan(last, col);
103			col -= (col > 0);
104			last = col;
105			bol = 0;
106			break;
107		case '\n':
108			if (bol || aflag)
109				unexpandspan(last, col);
110			last = col = 0;
111			bol = 1;
112			break;
113		default:
114			if (bol || aflag)
115				unexpandspan(last, col);
116			last = ++col;
117			bol = 0;
118			break;
119		}
120		if ((r != ' ' && r != '\t') || (!aflag && !bol))
121			efputrune(&r, stdout, "<stdout>");
122	}
123	if (last < col && (bol || aflag))
124		unexpandspan(last, col);
125}
126
127static void
128usage(void)
129{
130	eprintf("usage: %s [-a] [-t tablist] [file ...]\n", argv0);
131}
132
133// ?man unexpand: convert spaces to tabs
134// ?man arguments: file ...
135// ?man convert space characters to tab characters
136int
137main(int argc, char *argv[])
138{
139	FILE *fp;
140	int ret = 0;
141	char *tl = "8";
142
143	ARGBEGIN {
144	// ?man -t:str: sort or specify timestamp
145	case 't':
146		tl = EARGF(usage());
147		if (!*tl)
148			eprintf("tablist cannot be empty\n");
149		/* fallthrough */
150	// ?man -a: print or show all entries
151	case 'a':
152		aflag = 1;
153		break;
154	default:
155		usage();
156	} ARGEND
157
158	tablistlen = parselist(tl);
159
160	if (!argc) {
161		unexpand("<stdin>", stdin);
162	} else {
163		for (; *argv; argc--, argv++) {
164			if (!strcmp(*argv, "-")) {
165				*argv = "<stdin>";
166				fp = stdin;
167			} else if (!(fp = fopen(*argv, "r"))) {
168				weprintf("fopen %s:", *argv);
169				ret = 1;
170				continue;
171			}
172			unexpand(*argv, fp);
173			if (fp != stdin && fshut(fp, *argv))
174				ret = 1;
175		}
176	}
177
178	ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
179
180	return ret;
181}