Line data Source code
1 : /* fold -- wrap each input line to fit in specified width.
2 : Copyright (C) 91, 1995-2006 Free Software Foundation, Inc.
3 :
4 : This program is free software: you can redistribute it and/or modify
5 : it under the terms of the GNU General Public License as published by
6 : the Free Software Foundation, either version 3 of the License, or
7 : (at your option) any later version.
8 :
9 : This program is distributed in the hope that it will be useful,
10 : but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : GNU General Public License for more details.
13 :
14 : You should have received a copy of the GNU General Public License
15 : along with this program. If not, see <http://www.gnu.org/licenses/>. */
16 :
17 : /* Written by David MacKenzie, djm@gnu.ai.mit.edu. */
18 :
19 : #include <config.h>
20 :
21 : #include <stdio.h>
22 : #include <getopt.h>
23 : #include <sys/types.h>
24 :
25 : #include "system.h"
26 : #include "error.h"
27 : #include "quote.h"
28 : #include "xstrtol.h"
29 :
30 : #define TAB_WIDTH 8
31 :
32 : /* The official name of this program (e.g., no `g' prefix). */
33 : #define PROGRAM_NAME "fold"
34 :
35 : #define AUTHORS "David MacKenzie"
36 :
37 : /* The name this program was run with. */
38 : char *program_name;
39 :
40 : /* If nonzero, try to break on whitespace. */
41 : static bool break_spaces;
42 :
43 : /* If nonzero, count bytes, not column positions. */
44 : static bool count_bytes;
45 :
46 : /* If nonzero, at least one of the files we read was standard input. */
47 : static bool have_read_stdin;
48 :
49 : static char const shortopts[] = "bsw:0::1::2::3::4::5::6::7::8::9::";
50 :
51 : static struct option const longopts[] =
52 : {
53 : {"bytes", no_argument, NULL, 'b'},
54 : {"spaces", no_argument, NULL, 's'},
55 : {"width", required_argument, NULL, 'w'},
56 : {GETOPT_HELP_OPTION_DECL},
57 : {GETOPT_VERSION_OPTION_DECL},
58 : {NULL, 0, NULL, 0}
59 : };
60 :
61 : void
62 9 : usage (int status)
63 : {
64 9 : if (status != EXIT_SUCCESS)
65 8 : fprintf (stderr, _("Try `%s --help' for more information.\n"),
66 : program_name);
67 : else
68 : {
69 1 : printf (_("\
70 : Usage: %s [OPTION]... [FILE]...\n\
71 : "),
72 : program_name);
73 1 : fputs (_("\
74 : Wrap input lines in each FILE (standard input by default), writing to\n\
75 : standard output.\n\
76 : \n\
77 : "), stdout);
78 1 : fputs (_("\
79 : Mandatory arguments to long options are mandatory for short options too.\n\
80 : "), stdout);
81 1 : fputs (_("\
82 : -b, --bytes count bytes rather than columns\n\
83 : -s, --spaces break at spaces\n\
84 : -w, --width=WIDTH use WIDTH columns instead of 80\n\
85 : "), stdout);
86 1 : fputs (HELP_OPTION_DESCRIPTION, stdout);
87 1 : fputs (VERSION_OPTION_DESCRIPTION, stdout);
88 1 : emit_bug_reporting_address ();
89 : }
90 9 : exit (status);
91 : }
92 :
93 : /* Assuming the current column is COLUMN, return the column that
94 : printing C will move the cursor to.
95 : The first column is 0. */
96 :
97 : static size_t
98 26 : adjust_column (size_t column, char c)
99 : {
100 26 : if (!count_bytes)
101 : {
102 22 : if (c == '\b')
103 : {
104 4 : if (column > 0)
105 1 : column--;
106 : }
107 18 : else if (c == '\r')
108 2 : column = 0;
109 16 : else if (c == '\t')
110 15 : column += TAB_WIDTH - column % TAB_WIDTH;
111 : else /* if (isprint (c)) */
112 1 : column++;
113 : }
114 : else
115 4 : column++;
116 26 : return column;
117 : }
118 :
119 : /* Fold file FILENAME, or standard input if FILENAME is "-",
120 : to stdout, with maximum line length WIDTH.
121 : Return true if successful. */
122 :
123 : static bool
124 65 : fold_file (char const *filename, size_t width)
125 : {
126 : FILE *istream;
127 : int c;
128 65 : size_t column = 0; /* Screen column where next char will go. */
129 65 : size_t offset_out = 0; /* Index in `line_out' for next char. */
130 : static char *line_out = NULL;
131 : static size_t allocated_out = 0;
132 : int saved_errno;
133 :
134 65 : if (STREQ (filename, "-"))
135 : {
136 44 : istream = stdin;
137 44 : have_read_stdin = true;
138 : }
139 : else
140 21 : istream = fopen (filename, "r");
141 :
142 65 : if (istream == NULL)
143 : {
144 12 : error (0, errno, "%s", filename);
145 12 : return false;
146 : }
147 :
148 418 : while ((c = getc (istream)) != EOF)
149 : {
150 312 : if (offset_out + 1 >= allocated_out)
151 37 : line_out = X2REALLOC (line_out, &allocated_out);
152 :
153 312 : if (c == '\n')
154 : {
155 293 : line_out[offset_out++] = c;
156 293 : fwrite (line_out, sizeof (char), offset_out, stdout);
157 293 : column = offset_out = 0;
158 293 : continue;
159 : }
160 :
161 19 : rescan:
162 25 : column = adjust_column (column, c);
163 :
164 25 : if (column > width)
165 : {
166 : /* This character would make the line too long.
167 : Print the line plus a newline, and make this character
168 : start the next line. */
169 15 : if (break_spaces)
170 : {
171 9 : bool found_blank = false;
172 9 : size_t logical_end = offset_out;
173 :
174 : /* Look for the last blank. */
175 21 : while (logical_end)
176 : {
177 5 : --logical_end;
178 5 : if (isblank (to_uchar (line_out[logical_end])))
179 : {
180 2 : found_blank = true;
181 2 : break;
182 : }
183 : }
184 :
185 9 : if (found_blank)
186 : {
187 : size_t i;
188 :
189 : /* Found a blank. Don't output the part after it. */
190 2 : logical_end++;
191 2 : fwrite (line_out, sizeof (char), (size_t) logical_end,
192 : stdout);
193 2 : putchar ('\n');
194 : /* Move the remainder to the beginning of the next line.
195 : The areas being copied here might overlap. */
196 2 : memmove (line_out, line_out + logical_end,
197 : offset_out - logical_end);
198 2 : offset_out -= logical_end;
199 3 : for (column = i = 0; i < offset_out; i++)
200 1 : column = adjust_column (column, line_out[i]);
201 2 : goto rescan;
202 : }
203 : }
204 :
205 13 : if (offset_out == 0)
206 : {
207 9 : line_out[offset_out++] = c;
208 9 : continue;
209 : }
210 :
211 4 : line_out[offset_out++] = '\n';
212 4 : fwrite (line_out, sizeof (char), (size_t) offset_out, stdout);
213 4 : column = offset_out = 0;
214 4 : goto rescan;
215 : }
216 :
217 10 : line_out[offset_out++] = c;
218 : }
219 :
220 53 : saved_errno = errno;
221 :
222 53 : if (offset_out)
223 1 : fwrite (line_out, sizeof (char), (size_t) offset_out, stdout);
224 :
225 53 : if (ferror (istream))
226 : {
227 7 : error (0, saved_errno, "%s", filename);
228 7 : if (!STREQ (filename, "-"))
229 7 : fclose (istream);
230 7 : return false;
231 : }
232 46 : if (!STREQ (filename, "-") && fclose (istream) == EOF)
233 : {
234 0 : error (0, errno, "%s", filename);
235 0 : return false;
236 : }
237 :
238 46 : return true;
239 : }
240 :
241 : int
242 79 : main (int argc, char **argv)
243 : {
244 79 : size_t width = 80;
245 : int i;
246 : int optc;
247 : bool ok;
248 :
249 : initialize_main (&argc, &argv);
250 79 : program_name = argv[0];
251 79 : setlocale (LC_ALL, "");
252 : bindtextdomain (PACKAGE, LOCALEDIR);
253 : textdomain (PACKAGE);
254 :
255 79 : atexit (close_stdout);
256 :
257 79 : break_spaces = count_bytes = have_read_stdin = false;
258 :
259 185 : while ((optc = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
260 : {
261 : char optargbuf[2];
262 :
263 60 : switch (optc)
264 : {
265 9 : case 'b': /* Count bytes rather than columns. */
266 9 : count_bytes = true;
267 9 : break;
268 :
269 7 : case 's': /* Break at word boundaries. */
270 7 : break_spaces = true;
271 7 : break;
272 :
273 10 : case '0': case '1': case '2': case '3': case '4':
274 : case '5': case '6': case '7': case '8': case '9':
275 10 : if (optarg)
276 7 : optarg--;
277 : else
278 : {
279 3 : optargbuf[0] = optc;
280 3 : optargbuf[1] = '\0';
281 3 : optarg = optargbuf;
282 : }
283 : /* Fall through. */
284 : case 'w': /* Line width. */
285 : {
286 : unsigned long int tmp_ulong;
287 45 : if (! (xstrtoul (optarg, NULL, 10, &tmp_ulong, "") == LONGINT_OK
288 12 : && 0 < tmp_ulong && tmp_ulong < SIZE_MAX - TAB_WIDTH))
289 23 : error (EXIT_FAILURE, 0,
290 : _("invalid number of columns: %s"), quote (optarg));
291 11 : width = tmp_ulong;
292 : }
293 11 : break;
294 :
295 1 : case_GETOPT_HELP_CHAR;
296 :
297 1 : case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
298 :
299 8 : default:
300 8 : usage (EXIT_FAILURE);
301 : }
302 : }
303 :
304 46 : if (argc == optind)
305 14 : ok = fold_file ("-", width);
306 : else
307 : {
308 32 : ok = true;
309 83 : for (i = optind; i < argc; i++)
310 51 : ok &= fold_file (argv[i], width);
311 : }
312 :
313 46 : if (have_read_stdin && fclose (stdin) == EOF)
314 0 : error (EXIT_FAILURE, errno, "-");
315 :
316 46 : exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
317 : }
|