1 /* $OpenBSD: column.c,v 1.26 2018/06/22 12:27:00 rob Exp $ */ 2 /* $NetBSD: column.c,v 1.4 1995/09/02 05:53:03 jtc Exp $ */ 3 4 /* 5 * Copyright (c) 1989, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include <sys/types.h> 34 #include <sys/ioctl.h> 35 36 #include <ctype.h> 37 #include <err.h> 38 #include <limits.h> 39 #include <locale.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 #include <wchar.h> 45 #include <wctype.h> 46 47 void c_columnate(void); 48 void *ereallocarray(void *, size_t, size_t); 49 void input(FILE *); 50 void maketbl(void); 51 void print(void); 52 void r_columnate(void); 53 __dead void usage(void); 54 55 struct field { 56 char *content; 57 int width; 58 }; 59 60 int termwidth; /* default terminal width */ 61 int entries; /* number of records */ 62 int eval; /* exit value */ 63 int *maxwidths; /* longest record per column */ 64 struct field **table; /* one array of pointers per line */ 65 wchar_t *separator = L"\t "; /* field separator for table option */ 66 67 int 68 main(int argc, char *argv[]) 69 { 70 struct winsize win; 71 FILE *fp; 72 int ch, tflag, xflag; 73 char *p; 74 const char *errstr; 75 76 setlocale(LC_CTYPE, ""); 77 78 termwidth = 0; 79 if ((p = getenv("COLUMNS")) != NULL) 80 termwidth = strtonum(p, 1, INT_MAX, NULL); 81 if (termwidth == 0 && ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && 82 win.ws_col > 0) 83 termwidth = win.ws_col; 84 if (termwidth == 0) 85 termwidth = 80; 86 87 if (pledge("stdio rpath", NULL) == -1) 88 err(1, "pledge"); 89 90 tflag = xflag = 0; 91 while ((ch = getopt(argc, argv, "c:s:tx")) != -1) { 92 switch(ch) { 93 case 'c': 94 termwidth = strtonum(optarg, 1, INT_MAX, &errstr); 95 if (errstr != NULL) 96 errx(1, "%s: %s", errstr, optarg); 97 break; 98 case 's': 99 if ((separator = reallocarray(NULL, strlen(optarg) + 1, 100 sizeof(*separator))) == NULL) 101 err(1, NULL); 102 if (mbstowcs(separator, optarg, strlen(optarg) + 1) == 103 (size_t) -1) 104 err(1, "sep"); 105 break; 106 case 't': 107 tflag = 1; 108 break; 109 case 'x': 110 xflag = 1; 111 break; 112 default: 113 usage(); 114 } 115 } 116 117 if (!tflag) 118 separator = L""; 119 argv += optind; 120 121 if (*argv == NULL) { 122 input(stdin); 123 } else { 124 for (; *argv; ++argv) { 125 if ((fp = fopen(*argv, "r"))) { 126 input(fp); 127 (void)fclose(fp); 128 } else { 129 warn("%s", *argv); 130 eval = 1; 131 } 132 } 133 } 134 135 if (pledge("stdio", NULL) == -1) 136 err(1, "pledge"); 137 138 if (!entries) 139 return eval; 140 141 if (tflag) 142 maketbl(); 143 else if (*maxwidths >= termwidth) 144 print(); 145 else if (xflag) 146 c_columnate(); 147 else 148 r_columnate(); 149 return eval; 150 } 151 152 #define INCR_NEXTTAB(x) (x = (x + 8) & ~7) 153 void 154 c_columnate(void) 155 { 156 int col, numcols; 157 struct field **row; 158 159 INCR_NEXTTAB(*maxwidths); 160 if ((numcols = termwidth / *maxwidths) == 0) 161 numcols = 1; 162 for (col = 0, row = table;; ++row) { 163 fputs((*row)->content, stdout); 164 if (!--entries) 165 break; 166 if (++col == numcols) { 167 col = 0; 168 putchar('\n'); 169 } else { 170 while (INCR_NEXTTAB((*row)->width) <= *maxwidths) 171 putchar('\t'); 172 } 173 } 174 putchar('\n'); 175 } 176 177 void 178 r_columnate(void) 179 { 180 int base, col, numcols, numrows, row; 181 182 INCR_NEXTTAB(*maxwidths); 183 if ((numcols = termwidth / *maxwidths) == 0) 184 numcols = 1; 185 numrows = entries / numcols; 186 if (entries % numcols) 187 ++numrows; 188 189 for (base = row = 0; row < numrows; base = ++row) { 190 for (col = 0; col < numcols; ++col, base += numrows) { 191 fputs(table[base]->content, stdout); 192 if (base + numrows >= entries) 193 break; 194 while (INCR_NEXTTAB(table[base]->width) <= *maxwidths) 195 putchar('\t'); 196 } 197 putchar('\n'); 198 } 199 } 200 201 void 202 print(void) 203 { 204 int row; 205 206 for (row = 0; row < entries; row++) 207 puts(table[row]->content); 208 } 209 210 211 void 212 maketbl(void) 213 { 214 struct field **row; 215 int col; 216 217 for (row = table; entries--; ++row) { 218 for (col = 0; (*row)[col + 1].content != NULL; ++col) 219 printf("%s%*s ", (*row)[col].content, 220 maxwidths[col] - (*row)[col].width, ""); 221 puts((*row)[col].content); 222 } 223 } 224 225 #define DEFNUM 1000 226 #define DEFCOLS 25 227 228 void 229 input(FILE *fp) 230 { 231 static int maxentry = 0; 232 static int maxcols = 0; 233 static struct field *cols = NULL; 234 int col, width, twidth; 235 size_t blen; 236 ssize_t llen; 237 char *p, *s, *buf = NULL; 238 wchar_t wc; 239 int wlen; 240 241 while ((llen = getline(&buf, &blen, fp)) > -1) { 242 if (buf[llen - 1] == '\n') 243 buf[llen - 1] = '\0'; 244 245 p = buf; 246 for (col = 0;; col++) { 247 248 /* Skip lines containing nothing but whitespace. */ 249 250 for (s = p; (wlen = mbtowc(&wc, s, MB_CUR_MAX)) > 0; 251 s += wlen) 252 if (!iswspace(wc)) 253 break; 254 if (*s == '\0') 255 break; 256 257 /* Skip leading, multiple, and trailing separators. */ 258 259 while ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) > 0 && 260 wcschr(separator, wc) != NULL) 261 p += wlen; 262 if (*p == '\0') 263 break; 264 265 /* 266 * Found a non-empty field. 267 * Remember the start and measure the width. 268 */ 269 270 s = p; 271 width = 0; 272 while (*p != '\0') { 273 if ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) == -1) { 274 width++; 275 p++; 276 continue; 277 } 278 if (wcschr(separator, wc) != NULL) 279 break; 280 if (*p == '\t') 281 INCR_NEXTTAB(width); 282 else { 283 width += (twidth = wcwidth(wc)) == -1 ? 284 1 : twidth; 285 } 286 p += wlen; 287 } 288 289 if (col + 1 >= maxcols) { 290 if (maxcols > INT_MAX - DEFCOLS) 291 err(1, "too many columns"); 292 maxcols += DEFCOLS; 293 cols = ereallocarray(cols, maxcols, 294 sizeof(*cols)); 295 maxwidths = ereallocarray(maxwidths, maxcols, 296 sizeof(*maxwidths)); 297 memset(maxwidths + col, 0, 298 DEFCOLS * sizeof(*maxwidths)); 299 } 300 301 /* 302 * Remember the width of the field, 303 * NUL-terminate and remeber the content, 304 * and advance beyond the separator, if any. 305 */ 306 307 cols[col].width = width; 308 if (maxwidths[col] < width) 309 maxwidths[col] = width; 310 if (*p != '\0') { 311 *p = '\0'; 312 p += wlen; 313 } 314 if ((cols[col].content = strdup(s)) == NULL) 315 err(1, NULL); 316 } 317 if (col == 0) 318 continue; 319 320 /* Found a non-empty line; remember it. */ 321 322 if (entries == maxentry) { 323 if (maxentry > INT_MAX - DEFNUM) 324 errx(1, "too many input lines"); 325 maxentry += DEFNUM; 326 table = ereallocarray(table, maxentry, sizeof(*table)); 327 } 328 table[entries] = ereallocarray(NULL, col + 1, 329 sizeof(*(table[entries]))); 330 table[entries][col].content = NULL; 331 while (col--) 332 table[entries][col] = cols[col]; 333 entries++; 334 } 335 } 336 337 void * 338 ereallocarray(void *ptr, size_t nmemb, size_t size) 339 { 340 if ((ptr = reallocarray(ptr, nmemb, size)) == NULL) 341 err(1, NULL); 342 return ptr; 343 } 344 345 __dead void 346 usage(void) 347 { 348 (void)fprintf(stderr, 349 "usage: column [-tx] [-c columns] [-s sep] [file ...]\n"); 350 exit(1); 351 } 352