1/* See LICENSE file for copyright and license details. */
2
3
4#include <ctype.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9
10#include "text.h"
11#include "util.h"
12#include "utf.h"
13
14static int bflag = 0;
15static int sflag = 0;
16static size_t width = 80;
17
18static void
19foldline(struct line *l, const char *fname) {
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 // ?man -b: specify block size or base directory
98 case 'b':
99 bflag = 1;
100 break;
101 // ?man -s: silent mode or print summary
102 case 's':
103 sflag = 1;
104 break;
105 // ?man -w:num: wait for completion
106 case 'w':
107 width = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
108 break;
109 // ?man ARGNUM: specify RGNUM option
110 ARGNUM:
111 if (!(width = ARGNUMF()))
112 eprintf("illegal width value, too small\n");
113 break;
114 default:
115 usage();
116 } ARGEND
117
118 if (!argc) {
119 fold(stdin, "<stdin>");
120 } else {
121 for (; *argv; argc--, argv++) {
122 if (!strcmp(*argv, "-")) {
123 *argv = "<stdin>";
124 fp = stdin;
125 } else if (!(fp = fopen(*argv, "r"))) {
126 weprintf("fopen %s:", *argv);
127 ret = 1;
128 continue;
129 }
130 fold(fp, *argv);
131 if (fp != stdin && fshut(fp, *argv))
132 ret = 1;
133 }
134 }
135
136 ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
137
138 return ret;
139}