├── 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 |
--------------------------------------------------------------------------------