├── LICENSE ├── Makefile ├── README.md ├── table.c └── table.h /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bradley Garagan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -std=c11 2 | CFLAGS += -O2 3 | CFLAGS += -funroll-loops 4 | CFLAGS += -flto 5 | CFLAGS += -Wall 6 | CFLAGS += -Wextra 7 | 8 | all: libtable.a 9 | 10 | libtable.a: table.c 11 | $(CC) $(CFLAGS) -c -o $@ $< 12 | 13 | install: 14 | cp libtable.a /usr/local/lib/libtable.a 15 | cp table.h /usr/local/include/table.h 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## libtable 2 | 3 | libtable is a tiny C library for generating 4 | pretty-printed ascii tables. 5 | 6 | ### Installation 7 | 8 | ```sh 9 | git clone https://github.com/marchelzo/libtable.git && \ 10 | cd libtable && \ 11 | make && sudo make install 12 | ``` 13 | 14 | ### Example usage 15 | 16 | ```c 17 | #include 18 | 19 | int main(void) { 20 | struct table t; 21 | 22 | table_init( 23 | &t, 24 | "Name", "%s", 25 | "Age", "%d", 26 | "Score", "%.2f", 27 | NULL 28 | ); 29 | 30 | table_add(&t, "Amet fugiat commodi eligendi possimus harum earum. " 31 | "Sequi quidem ab commodi tempore mollitia provident. " 32 | "Iusto incidunt consequuntur rem eligendi illum. " 33 | "Nisi odit soluta dolorum vero enim neque id. Hic magni? " 34 | "foo bar baz", 36, 3.1); 35 | table_add(&t, "Ametfugiatcommodieligendipossimusharumearum." 36 | "Sequiquidemabcommoditemporemollitiaprovident." 37 | "Iustoinciduntconsequunturremeligendiillum." 38 | "Nisioditsolutadolorumveroenimnequeid.Hicmagni?" 39 | "foobarbaz",36,3.1); 40 | table_add(&t, "Ξεσκεπάζωτὴνψυχοφθόραβδελυγμία", 36, 3.1); 41 | table_add(&t, "Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία", 36, 3.1); 42 | table_add(&t, "Bob", 18, 1.3123); 43 | table_add(&t, "Alice", 20, 6.43); 44 | table_add(&t, "Roger", 18, 12.45); 45 | table_add(&t, "Larry", 59, 12.52); 46 | table_add(&t, "Ё Ђ Ѓ Є Ѕ І Ї Ј Љ", 21, 14.12312312); 47 | 48 | table_print(&t, 60, stdout); 49 | 50 | table_free(&t); 51 | 52 | return 0; 53 | } 54 | ``` 55 | 56 | #### Output 57 | 58 | ``` 59 | +--------------------------------------------------------+ 60 | | Name | Age | Score | 61 | *--------------------------------------------------------* 62 | | Amet fugiat commodi eligendi possimus | 36 | 3.10 | 63 | | harum earum. Sequi quidem ab commodi | | | 64 | | tempore mollitia provident. Iusto | | | 65 | | incidunt consequuntur rem eligendi | | | 66 | | illum. Nisi odit soluta dolorum vero | | | 67 | | enim neque id. Hic magni? foo bar baz | | | 68 | |------------------------------------------|-----|-------| 69 | | Ametfugiatcommodieligendipossimusharume- | 36 | 3.10 | 70 | | arum.Sequiquidemabcommoditemporemolliti- | | | 71 | | aprovident.Iustoinciduntconsequunturrem- | | | 72 | | eligendiillum.Nisioditsolutadolorumvero- | | | 73 | | enimnequeid.Hicmagni?foobarbaz | | | 74 | |------------------------------------------|-----|-------| 75 | | Ξεσκεπάζωτὴνψυχοφθόραβδελυγμία | 36 | 3.10 | 76 | |------------------------------------------|-----|-------| 77 | | Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία | 36 | 3.10 | 78 | |------------------------------------------|-----|-------| 79 | | Bob | 18 | 1.31 | 80 | |------------------------------------------|-----|-------| 81 | | Alice | 20 | 6.43 | 82 | |------------------------------------------|-----|-------| 83 | | Roger | 18 | 12.45 | 84 | |------------------------------------------|-----|-------| 85 | | Larry | 59 | 12.52 | 86 | |------------------------------------------|-----|-------| 87 | | Ё Ђ Ѓ Є Ѕ І Ї Ј Љ | 21 | 14.12 | 88 | +--------------------------------------------------------+ 89 | ``` 90 | -------------------------------------------------------------------------------- /table.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "table.h" 10 | 11 | #define CPSTART(c) (((unsigned char) (c)) >> (CHAR_BIT - 2) != 2) 12 | 13 | 14 | static char const *DELIM = "\x1f"; 15 | static char const CORNER = '+'; 16 | static char const INTERSECT = '*'; 17 | 18 | static inline size_t max(size_t a, size_t b) 19 | { 20 | return a > b ? a : b; 21 | } 22 | 23 | static size_t utf8len(char const *s) 24 | { 25 | size_t len = 0; 26 | char const *c; 27 | 28 | for (c = s; *c; ++c) { 29 | if (CPSTART(*c)) { 30 | len += 1; 31 | } 32 | } 33 | 34 | return len; 35 | } 36 | 37 | static size_t b2cp(char const *s, size_t n) 38 | { 39 | size_t cp, i; 40 | 41 | for (cp = i = 0; s[i] && n > 0; ++i, --n) { 42 | if (CPSTART(s[i])) { 43 | cp += 1; 44 | } 45 | } 46 | 47 | return cp; 48 | } 49 | 50 | static void fputnc(int c, size_t n, FILE *f) 51 | { 52 | while (n --> 0) { 53 | fputc(c, f); 54 | } 55 | } 56 | 57 | static size_t find_break(char const *s, size_t max, bool *hyphen) 58 | { 59 | char *c; 60 | size_t brk, cp; 61 | 62 | if (!*s) { 63 | *hyphen = false; 64 | return 0; 65 | } 66 | 67 | for (c = s, cp = brk = 0; *c && cp < max; ++c) { 68 | brk += 1; 69 | if (CPSTART(*c)) { 70 | cp += 1; 71 | } 72 | } 73 | 74 | while (!CPSTART(*c)) { 75 | c += 1; 76 | brk += 1; 77 | } 78 | 79 | while (*c && c != s && !isspace(*c)) { 80 | c -= 1; 81 | } 82 | 83 | if (c == s) { 84 | *hyphen = true; 85 | while (!CPSTART(s[--brk])); 86 | return brk; 87 | } 88 | 89 | *hyphen = false; 90 | 91 | return c - s; 92 | } 93 | 94 | static void print_row(char * const *data, size_t *max, size_t *remaining, size_t cols, FILE *f) 95 | { 96 | static size_t alloc = 0; 97 | static char const **cells = NULL; 98 | 99 | size_t i, n, pad; 100 | bool hyphen, finished; 101 | 102 | if (cols > alloc) { 103 | char const **tmp; 104 | alloc = cols; 105 | tmp = realloc(cells, alloc * sizeof *cells); 106 | if (tmp == NULL) { 107 | return; 108 | } 109 | 110 | cells = tmp; 111 | 112 | } 113 | 114 | for (i = 0; i < cols; ++i) { 115 | cells[i] = data[i]; 116 | remaining[i] = utf8len(cells[i]); 117 | } 118 | 119 | for (finished = false; !finished;) { 120 | finished = true; 121 | for (i = 0; i < cols; ++i) { 122 | fputs("| ", f); 123 | 124 | n = find_break(cells[i], max[i], &hyphen); 125 | fwrite(cells[i], 1, n, f); 126 | 127 | if (hyphen) { 128 | fputc('-', f); 129 | } else if (isspace(cells[i][n])) { 130 | fputc(' ', f); 131 | remaining[i] -= 1; 132 | cells[i] += 1; 133 | } else { 134 | fputc(' ', f); 135 | } 136 | 137 | remaining[i] -= b2cp(cells[i], n); 138 | 139 | if (remaining[i] != 0) { 140 | finished = false; 141 | } 142 | 143 | pad = max[i] - b2cp(cells[i], n); 144 | fputnc(' ', pad, f); 145 | 146 | cells[i] += n; 147 | 148 | if (i + 1 == cols) { 149 | fputs("|\n", f); 150 | } 151 | } 152 | } 153 | } 154 | 155 | bool table_init(struct table *t, ...) 156 | { 157 | va_list ap; 158 | char *header, *fmt, *tmp, **tmph; 159 | size_t i, fmtlen, totalfmtlen; 160 | 161 | totalfmtlen = 0; 162 | 163 | t->rows = 0; 164 | t->cols = 0; 165 | t->alloc = 0; 166 | t->data = NULL; 167 | t->fmt = NULL; 168 | t->headers = NULL; 169 | 170 | va_start(ap, t); 171 | 172 | for (;; ++t->cols) { 173 | header = va_arg(ap, char *); 174 | if (header == NULL) { 175 | break; 176 | } 177 | fmt = va_arg(ap, char *); 178 | 179 | tmph = realloc(t->headers, (t->cols + 1) * sizeof (char *)); 180 | if (tmph == NULL) { 181 | free(t->headers); 182 | return false; 183 | } 184 | t->headers = tmph; 185 | 186 | t->headers[t->cols] = header; 187 | 188 | fmtlen = strlen(fmt); 189 | tmp = realloc(t->fmt, totalfmtlen + fmtlen + 2); 190 | if (tmp == NULL) { 191 | free(t->fmt); 192 | free(t->headers); 193 | return false; 194 | } 195 | t->fmt = tmp; 196 | strcpy(t->fmt + totalfmtlen, fmt); 197 | totalfmtlen += fmtlen; 198 | strcpy(t->fmt + totalfmtlen, DELIM); 199 | totalfmtlen += 1; 200 | } 201 | 202 | va_end(ap); 203 | 204 | t->max = malloc(t->cols * sizeof *t->max); 205 | if (t->max == NULL) { 206 | free(t->headers); 207 | free(t->fmt); 208 | return false; 209 | } 210 | 211 | for (i = 0; i < t->cols; ++i) { 212 | t->max[i] = utf8len(t->headers[i]); 213 | } 214 | 215 | return true; 216 | } 217 | 218 | bool table_add(struct table *t, ...) 219 | { 220 | va_list ap; 221 | char *field, **row, buffer[512]; 222 | size_t i; 223 | 224 | if (t->rows == t->alloc) { 225 | char ***tmp; 226 | t->alloc = t->alloc * 2 + 4; 227 | tmp = realloc(t->data, t->alloc * sizeof (char **)); 228 | if (tmp == NULL) { 229 | return false; 230 | } 231 | t->data = tmp; 232 | } 233 | 234 | row = (t->data[t->rows++] = malloc(t->cols * sizeof (char *))); 235 | 236 | if (row == NULL) { 237 | t->rows -= 1; 238 | return false; 239 | } 240 | 241 | 242 | 243 | va_start(ap, t); 244 | vsnprintf(buffer, sizeof buffer, t->fmt, ap); 245 | va_end(ap); 246 | 247 | field = strtok(buffer, DELIM); 248 | 249 | for (i = 0; field != NULL; ++i, field = strtok(NULL, DELIM)) { 250 | assert(&row[i] == &t->data[t->rows - 1][i]); 251 | row[i] = malloc(strlen(field) + 1); 252 | if (row[i] == NULL) { 253 | goto err; 254 | } 255 | strcpy(row[i], field); 256 | 257 | t->max[i] = max(t->max[i], utf8len(field)); 258 | } 259 | 260 | return true; 261 | 262 | err: 263 | while (i --> 0) { 264 | free(row[i]); 265 | } 266 | 267 | return false; 268 | } 269 | 270 | bool table_print(struct table const *t, size_t n, FILE *f) 271 | { 272 | size_t i, width, avg, trimthrshld, *max, *remaining; 273 | 274 | if (n < t->cols * 3 + 4) { 275 | /* Not enough space */ 276 | return false; 277 | } 278 | 279 | n -= 2; 280 | 281 | max = malloc(t->cols * sizeof *max); 282 | if (max == NULL) { 283 | return false; 284 | } 285 | 286 | remaining = malloc(t-> cols * sizeof *remaining); 287 | if (remaining == NULL) { 288 | free(max); 289 | return false; 290 | } 291 | 292 | width = t->cols * 3 + 1; 293 | for (i = 0; i < t->cols; ++i) { 294 | max[i] = t->max[i]; 295 | width += t->max[i]; 296 | } 297 | 298 | avg = n / t->cols; 299 | trimthrshld = 0; 300 | 301 | while (width > n) { 302 | bool none = true; 303 | for (i = 0; i < t->cols; ++i) { 304 | if (max[i] + trimthrshld > avg) { 305 | max[i] -= 1; 306 | width -= 1; 307 | none = false; 308 | } 309 | } 310 | 311 | if (none) { 312 | trimthrshld += 1; 313 | } 314 | } 315 | 316 | fputc(CORNER, f); 317 | fputnc('-', width - 2, f); 318 | fputc(CORNER, f); 319 | fputc('\n', f); 320 | 321 | print_row(t->headers, max, remaining, t->cols, f); 322 | 323 | fputc(INTERSECT, f); 324 | fputnc('-', width - 2, f); 325 | fputc(INTERSECT, f); 326 | fputc('\n', f); 327 | 328 | for (i = 0; i < t->rows; ++i) { 329 | print_row(t->data[i], max, remaining, t->cols, f); 330 | 331 | if (i + 1 < t->rows) { 332 | size_t j; 333 | fputc('|', f); 334 | for (j = 0; j < t->cols; ++j) { 335 | fputnc('-', max[j] + 2, f); 336 | fputc('|', f); 337 | } 338 | fputc('\n', f); 339 | } 340 | } 341 | 342 | fputc(CORNER, f); 343 | fputnc('-', width - 2, f); 344 | fputc(CORNER, f); 345 | fputc('\n', f); 346 | 347 | free(remaining); 348 | free(max); 349 | return true; 350 | } 351 | 352 | void table_free(struct table *t) 353 | { 354 | size_t i, j; 355 | 356 | for (i = 0; i < t->rows; ++i) { 357 | for (j = 0; j < t->cols; ++j) { 358 | free(t->data[i][j]); 359 | } 360 | } 361 | 362 | for (i = 0; i < t->rows; ++i) { 363 | free(t->data[i]); 364 | } 365 | 366 | free(t->data); 367 | free(t->headers); 368 | free(t->max); 369 | free(t->fmt); 370 | } 371 | -------------------------------------------------------------------------------- /table.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBTABLE_H_INCLUDED 2 | #define LIBTABLE_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct table { 9 | size_t cols; 10 | size_t rows; 11 | size_t alloc; 12 | size_t *max; 13 | char ***data; 14 | char **headers; 15 | char *fmt; 16 | }; 17 | 18 | bool table_init(struct table *t, ...); 19 | bool table_add(struct table *t, ...); 20 | bool table_print(struct table const *t, size_t maxwidth, FILE *f); 21 | void table_free(struct table *t); 22 | 23 | #endif 24 | --------------------------------------------------------------------------------