1/* See LICENSE file for copyright and license details. */
2
3#include <ctype.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 int bflag = 0;
14static int sflag = 0;
15static size_t width = 80;
16
17static void
18foldline(struct line *l, const char *fname)
19{
20 size_t i, col, last, spacesect, len;
21 Rune r;
22 int runelen;
23
24 for (i = 0, last = 0, col = 0, spacesect = 0; i < l->len; i += runelen) {
25 if (col >= width && ((l->data[i] != '\r' && l->data[i] != '\b') || bflag)) {
26 if (bflag && col > width)
27 i -= runelen; /* never split a character */
28 len = ((sflag && spacesect) ? spacesect : i) - last;
29 if (fwrite(l->data + last, 1, len, stdout) != len)
30 eprintf("fwrite <stdout>:");
31 if (l->data[i] != '\n')
32 putchar('\n');
33 if (sflag && spacesect)
34 i = spacesect;
35 last = i;
36 col = 0;
37 spacesect = 0;
38 }
39 runelen = charntorune(&r, l->data + i, l->len - i);
40 if (!runelen || r == Runeerror)
41 eprintf("charntorune: %s: invalid utf\n", fname);
42 if (sflag && isblankrune(r))
43 spacesect = i + runelen;
44 if (!bflag && iscntrl(l->data[i])) {
45 switch (l->data[i]) {
46 case '\b':
47 col -= (col > 0);
48 break;
49 case '\r':
50 col = 0;
51 break;
52 case '\t':
53 col += (8 - (col % 8));
54 if (col >= width)
55 i--;
56 break;
57 }
58 } else {
59 col += bflag ? runelen : 1;
60 }
61 }
62 if (l->len - last)
63 fwrite(l->data + last, 1, l->len - last, stdout);
64}
65
66static void
67fold(FILE *fp, const char *fname)
68{
69 static struct line line;
70 static size_t size = 0;
71 ssize_t len;
72
73 while ((len = getline(&line.data, &size, fp)) > 0) {
74 line.len = len;
75 foldline(&line, fname);
76 }
77 if (ferror(fp))
78 eprintf("getline %s:", fname);
79}
80
81static void
82usage(void)
83{
84 eprintf("usage: %s [-bs] [-w num | -num] [FILE ...]\n", argv0);
85}
86
87// ?man fold: wrap lines to fit width
88// ?man arguments: FILE ...
89// ?man wrap input lines to fit a specified width
90int
91main(int argc, char *argv[])
92{
93 FILE *fp;
94 int ret = 0;
95
96 ARGBEGIN
97 {
98 // ?man -b: specify block size or base directory
99 case 'b':
100 bflag = 1;
101 break;
102 // ?man -s: silent mode or print summary
103 case 's':
104 sflag = 1;
105 break;
106 // ?man -w:num: wait for completion
107 case 'w':
108 width = estrtonum(
109 EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX)
110 );
111 break;
112 // ?man ARGNUM: specify RGNUM option
113 ARGNUM:
114 if (!(width = ARGNUMF()))
115 eprintf("illegal width value, too small\n");
116 break;
117 default:
118 usage();
119 }
120 ARGEND
121
122 if (!argc) {
123 fold(stdin, "<stdin>");
124 } else {
125 for (; *argv; argc--, argv++) {
126 if (!strcmp(*argv, "-")) {
127 *argv = "<stdin>";
128 fp = stdin;
129 } else if (!(fp = fopen(*argv, "r"))) {
130 weprintf("fopen %s:", *argv);
131 ret = 1;
132 continue;
133 }
134 fold(fp, *argv);
135 if (fp != stdin && fshut(fp, *argv))
136 ret = 1;
137 }
138 }
139
140 ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
141
142 return ret;
143}