1 /* $OpenBSD: column.c,v 1.25 2016/09/04 20:33:36 martijn 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 *ecalloc(size_t, size_t); 50 void input(FILE *); 51 void maketbl(void); 52 void print(void); 53 void r_columnate(void); 54 __dead void usage(void); 55 56 struct field { 57 char *content; 58 int width; 59 }; 60 61 int termwidth; /* default terminal width */ 62 int entries; /* number of records */ 63 int eval; /* exit value */ 64 int *maxwidths; /* longest record per column */ 65 struct field **table; /* one array of pointers per line */ 66 wchar_t *separator = L"\t "; /* field separator for table option */ 67 68 int 69 main(int argc, char *argv[]) 70 { 71 struct winsize win; 72 FILE *fp; 73 int ch, tflag, xflag; 74 char *p; 75 const char *errstr; 76 77 setlocale(LC_CTYPE, ""); 78 79 termwidth = 0; 80 if ((p = getenv("COLUMNS")) != NULL) 81 termwidth = strtonum(p, 1, INT_MAX, NULL); 82 if (termwidth == 0 && ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && 83 win.ws_col > 0) 84 termwidth = win.ws_col; 85 if (termwidth == 0) 86 termwidth = 80; 87 88 if (pledge("stdio rpath", NULL) == -1) 89 err(1, "pledge"); 90 91 tflag = xflag = 0; 92 while ((ch = getopt(argc, argv, "c:s:tx")) != -1) { 93 switch(ch) { 94 case 'c': 95 termwidth = strtonum(optarg, 1, INT_MAX, &errstr); 96 if (errstr != NULL) 97 errx(1, "%s: %s", errstr, optarg); 98 break; 99 case 's': 100 if ((separator = reallocarray(NULL, strlen(optarg) + 1, 101 sizeof(*separator))) == NULL) 102 err(1, NULL); 103 if (mbstowcs(separator, optarg, strlen(optarg) + 1) == 104 (size_t) -1) 105 err(1, "sep"); 106 break; 107 case 't': 108 tflag = 1; 109 break; 110 case 'x': 111 xflag = 1; 112 break; 113 default: 114 usage(); 115 } 116 } 117 118 if (!tflag) 119 separator = L""; 120 argv += optind; 121 122 if (*argv == NULL) { 123 input(stdin); 124 } else { 125 for (; *argv; ++argv) { 126 if ((fp = fopen(*argv, "r"))) { 127 input(fp); 128 (void)fclose(fp); 129 } else { 130 warn("%s", *argv); 131 eval = 1; 132 } 133 } 134 } 135 136 if (pledge("stdio", NULL) == -1) 137 err(1, "pledge"); 138 139 if (!entries) 140 return eval; 141 142 if (tflag) 143 maketbl(); 144 else if (*maxwidths >= termwidth) 145 print(); 146 else if (xflag) 147 c_columnate(); 148 else 149 r_columnate(); 150 return eval; 151 } 152 153 #define INCR_NEXTTAB(x) (x = (x + 8) & ~7) 154 void 155 c_columnate(void) 156 { 157 int col, numcols; 158 struct field **row; 159 160 INCR_NEXTTAB(*maxwidths); 161 if ((numcols = termwidth / *maxwidths) == 0) 162 numcols = 1; 163 for (col = 0, row = table;; ++row) { 164 fputs((*row)->content, stdout); 165 if (!--entries) 166 break; 167 if (++col == numcols) { 168 col = 0; 169 putchar('\n'); 170 } else { 171 while (INCR_NEXTTAB((*row)->width) <= *maxwidths) 172 putchar('\t'); 173 } 174 } 175 putchar('\n'); 176 } 177 178 void 179 r_columnate(void) 180 { 181 int base, col, numcols, numrows, row; 182 183 INCR_NEXTTAB(*maxwidths); 184 if ((numcols = termwidth / *maxwidths) == 0) 185 numcols = 1; 186 numrows = entries / numcols; 187 if (entries % numcols) 188 ++numrows; 189 190 for (base = row = 0; row < numrows; base = ++row) { 191 for (col = 0; col < numcols; ++col, base += numrows) { 192 fputs(table[base]->content, stdout); 193 if (base + numrows >= entries) 194 break; 195 while (INCR_NEXTTAB(table[base]->width) <= *maxwidths) 196 putchar('\t'); 197 } 198 putchar('\n'); 199 } 200 } 201 202 void 203 print(void) 204 { 205 int row; 206 207 for (row = 0; row < entries; row++) 208 puts(table[row]->content); 209 } 210 211 212 void 213 maketbl(void) 214 { 215 struct field **row; 216 int col; 217 218 for (row = table; entries--; ++row) { 219 for (col = 0; (*row)[col + 1].content != NULL; ++col) 220 printf("%s%*s ", (*row)[col].content, 221 maxwidths[col] - (*row)[col].width, ""); 222 puts((*row)[col].content); 223 } 224 } 225 226 #define DEFNUM 1000 227 #define DEFCOLS 25 228 229 void 230 input(FILE *fp) 231 { 232 static int maxentry = 0; 233 static int maxcols = 0; 234 static struct field *cols = NULL; 235 int col, width, twidth; 236 size_t blen; 237 ssize_t llen; 238 char *p, *s, *buf = NULL; 239 wchar_t wc; 240 int wlen; 241 242 while ((llen = getline(&buf, &blen, fp)) > -1) { 243 if (buf[llen - 1] == '\n') 244 buf[llen - 1] = '\0'; 245 246 p = buf; 247 for (col = 0;; col++) { 248 249 /* Skip lines containing nothing but whitespace. */ 250 251 for (s = p; (wlen = mbtowc(&wc, s, MB_CUR_MAX)) > 0; 252 s += wlen) 253 if (!iswspace(wc)) 254 break; 255 if (*s == '\0') 256 break; 257 258 /* Skip leading, multiple, and trailing separators. */ 259 260 while ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) > 0 && 261 wcschr(separator, wc) != NULL) 262 p += wlen; 263 if (*p == '\0') 264 break; 265 266 /* 267 * Found a non-empty field. 268 * Remember the start and measure the width. 269 */ 270 271 s = p; 272 width = 0; 273 while (*p != '\0') { 274 if ((wlen = mbtowc(&wc, p, MB_CUR_MAX)) == -1) { 275 width++; 276 p++; 277 continue; 278 } 279 if (wcschr(separator, wc) != NULL) 280 break; 281 if (*p == '\t') 282 INCR_NEXTTAB(width); 283 else { 284 width += (twidth = wcwidth(wc)) == -1 ? 285 1 : twidth; 286 } 287 p += wlen; 288 } 289 290 if (col + 1 >= maxcols) { 291 if (maxcols > INT_MAX - DEFCOLS) 292 err(1, "too many columns"); 293 maxcols += DEFCOLS; 294 cols = ereallocarray(cols, maxcols, 295 sizeof(*cols)); 296 maxwidths = ereallocarray(maxwidths, maxcols, 297 sizeof(*maxwidths)); 298 memset(maxwidths + col, 0, 299 DEFCOLS * sizeof(*maxwidths)); 300 } 301 302 /* 303 * Remember the width of the field, 304 * NUL-terminate and remeber the content, 305 * and advance beyond the separator, if any. 306 */ 307 308 cols[col].width = width; 309 if (maxwidths[col] < width) 310 maxwidths[col] = width; 311 if (*p != '\0') { 312 *p = '\0'; 313 p += wlen; 314 } 315 if ((cols[col].content = strdup(s)) == NULL) 316 err(1, NULL); 317 } 318 if (col == 0) 319 continue; 320 321 /* Found a non-empty line; remember it. */ 322 323 if (entries == maxentry) { 324 if (maxentry > INT_MAX - DEFNUM) 325 errx(1, "too many input lines"); 326 maxentry += DEFNUM; 327 table = ereallocarray(table, maxentry, sizeof(*table)); 328 } 329 table[entries] = ereallocarray(NULL, col + 1, 330 sizeof(*(table[entries]))); 331 table[entries][col].content = NULL; 332 while (col--) 333 table[entries][col] = cols[col]; 334 entries++; 335 } 336 } 337 338 void * 339 ereallocarray(void *ptr, size_t nmemb, size_t size) 340 { 341 if ((ptr = reallocarray(ptr, nmemb, size)) == NULL) 342 err(1, NULL); 343 return ptr; 344 } 345 346 void * 347 ecalloc(size_t nmemb, size_t size) 348 { 349 void *ptr; 350 351 if ((ptr = calloc(nmemb, size)) == NULL) 352 err(1, NULL); 353 return ptr; 354 } 355 356 __dead void 357 usage(void) 358 { 359 (void)fprintf(stderr, 360 "usage: column [-tx] [-c columns] [-s sep] [file ...]\n"); 361 exit(1); 362 } 363