]> git.cameronkatri.com Git - mandoc.git/blob - term.c
Cleaned up validation source a bit.
[mandoc.git] / term.c
1 /* $Id: term.c,v 1.7 2009/02/22 14:31:08 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the
7 * above copyright notice and this permission notice appear in all
8 * copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #ifdef __linux__
28 #include <time.h>
29 #endif
30
31 #include "term.h"
32
33 enum termstyle {
34 STYLE_CLEAR,
35 STYLE_BOLD,
36 STYLE_UNDERLINE
37 };
38
39 static void termprint_r(struct termp *,
40 const struct mdoc_meta *,
41 const struct mdoc_node *);
42 static void termprint_header(struct termp *,
43 const struct mdoc_meta *);
44 static void termprint_footer(struct termp *,
45 const struct mdoc_meta *);
46
47 static void pword(struct termp *, const char *, size_t);
48 static void pescape(struct termp *,
49 const char *, size_t *, size_t);
50 static void chara(struct termp *, char);
51 static void style(struct termp *, enum termstyle);
52
53 #ifdef __linux__
54 extern size_t strlcat(char *, const char *, size_t);
55 extern size_t strlcpy(char *, const char *, size_t);
56 #endif
57
58 void
59 flushln(struct termp *p)
60 {
61 size_t i, j, vsz, vis, maxvis;
62
63 /*
64 * First, establish the maximum columns of "visible" content.
65 * This is usually the difference between the right-margin and
66 * an indentation, but can be, for tagged lists or columns, a
67 * small set of values.
68 */
69
70 assert(p->offset < p->rmargin);
71 maxvis = p->rmargin - p->offset;
72 vis = 0;
73
74 /*
75 * If in the standard case (left-justified), then begin with our
76 * indentation, otherwise (columns, etc.) just start spitting
77 * out text.
78 */
79
80 if ( ! (p->flags & TERMP_NOLPAD))
81 /* LINTED */
82 for (j = 0; j < p->offset; j++)
83 putchar(' ');
84
85 for (i = 0; i < p->col; i++) {
86 /*
87 * Count up visible word characters. Control sequences
88 * (starting with the CSI) aren't counted.
89 */
90 assert( ! isspace(p->buf[i]));
91
92 /* LINTED */
93 for (j = i, vsz = 0; j < p->col; j++) {
94 if (isspace(p->buf[j]))
95 break;
96 else if (27 == p->buf[j]) {
97 assert(j + 4 <= p->col);
98 j += 3;
99 } else
100 vsz++;
101 }
102 assert(vsz > 0);
103
104 /*
105 * If a word is too long and we're within a line, put it
106 * on the next line. Puke if we're being asked to write
107 * something that will exceed the right margin (i.e.,
108 * from a fresh line or when we're not allowed to break
109 * the line with TERMP_NOBREAK).
110 */
111
112 if (vis && vis + vsz >= maxvis) {
113 /* FIXME */
114 if (p->flags & TERMP_NOBREAK)
115 errx(1, "word breaks right margin");
116 putchar('\n');
117 for (j = 0; j < p->offset; j++)
118 putchar(' ');
119 vis = 0;
120 } else if (vis + vsz >= maxvis) {
121 /* FIXME */
122 errx(1, "word breaks right margin");
123 }
124
125 /*
126 * Write out the word and a trailing space. Omit the
127 * space if we're the last word in the line.
128 */
129
130 for ( ; i < p->col; i++) {
131 if (isspace(p->buf[i]))
132 break;
133 putchar(p->buf[i]);
134 }
135 vis += vsz;
136 if (i < p->col) {
137 putchar(' ');
138 vis++;
139 }
140 }
141
142 /*
143 * If we're not to right-marginalise it (newline), then instead
144 * pad to the right margin and stay off.
145 */
146
147 if (p->flags & TERMP_NOBREAK) {
148 for ( ; vis <= maxvis; vis++)
149 putchar(' ');
150 } else
151 putchar('\n');
152
153 p->col = 0;
154 }
155
156
157 void
158 newln(struct termp *p)
159 {
160
161 /*
162 * A newline only breaks an existing line; it won't assert
163 * vertical space.
164 */
165 p->flags |= TERMP_NOSPACE;
166 if (0 == p->col)
167 return;
168 flushln(p);
169 }
170
171
172 void
173 vspace(struct termp *p)
174 {
175
176 /*
177 * Asserts a vertical space (a full, empty line-break between
178 * lines).
179 */
180 newln(p);
181 putchar('\n');
182 }
183
184
185 static void
186 chara(struct termp *p, char c)
187 {
188
189 /* TODO: dynamically expand the buffer. */
190 if (p->col + 1 >= p->maxcols)
191 errx(1, "line overrun");
192 p->buf[(p->col)++] = c;
193 }
194
195
196 static void
197 style(struct termp *p, enum termstyle esc)
198 {
199
200 if (p->col + 4 >= p->maxcols)
201 errx(1, "line overrun");
202
203 p->buf[(p->col)++] = 27;
204 p->buf[(p->col)++] = '[';
205 switch (esc) {
206 case (STYLE_CLEAR):
207 p->buf[(p->col)++] = '0';
208 break;
209 case (STYLE_BOLD):
210 p->buf[(p->col)++] = '1';
211 break;
212 case (STYLE_UNDERLINE):
213 p->buf[(p->col)++] = '4';
214 break;
215 default:
216 abort();
217 /* NOTREACHED */
218 }
219 p->buf[(p->col)++] = 'm';
220 }
221
222
223 static void
224 pescape(struct termp *p, const char *word, size_t *i, size_t len)
225 {
226
227 (*i)++;
228 assert(*i < len);
229
230 if ('(' == word[*i]) {
231 /* Two-character escapes. */
232 (*i)++;
233 assert(*i + 1 < len);
234
235 if ('r' == word[*i] && 'B' == word[*i + 1])
236 chara(p, ']');
237 else if ('l' == word[*i] && 'B' == word[*i + 1])
238 chara(p, '[');
239
240 (*i)++;
241 return;
242
243 } else if ('[' != word[*i]) {
244 /* One-character escapes. */
245 switch (word[*i]) {
246 case ('\\'):
247 /* FALLTHROUGH */
248 case ('\''):
249 /* FALLTHROUGH */
250 case ('`'):
251 /* FALLTHROUGH */
252 case ('-'):
253 /* FALLTHROUGH */
254 case ('.'):
255 chara(p, word[*i]);
256 default:
257 break;
258 }
259 return;
260 }
261 /* n-character escapes. */
262 }
263
264
265 static void
266 pword(struct termp *p, const char *word, size_t len)
267 {
268 size_t i;
269
270 assert(len > 0);
271
272 if ( ! (p->flags & TERMP_NOSPACE))
273 chara(p, ' ');
274
275 p->flags &= ~TERMP_NOSPACE;
276
277 if (p->flags & TERMP_BOLD)
278 style(p, STYLE_BOLD);
279 if (p->flags & TERMP_UNDERLINE)
280 style(p, STYLE_UNDERLINE);
281
282 for (i = 0; i < len; i++) {
283 if ('\\' == word[i]) {
284 pescape(p, word, &i, len);
285 continue;
286 }
287 chara(p, word[i]);
288 }
289
290 if (p->flags & TERMP_BOLD ||
291 p->flags & TERMP_UNDERLINE)
292 style(p, STYLE_CLEAR);
293 }
294
295
296 void
297 word(struct termp *p, const char *word)
298 {
299 size_t i, j, len;
300
301 if (mdoc_isdelim(word))
302 p->flags |= TERMP_NOSPACE;
303
304 len = strlen(word);
305 assert(len > 0);
306
307 /* LINTED */
308 for (j = i = 0; i < len; i++) {
309 if ( ! isspace(word[i])) {
310 j++;
311 continue;
312 }
313 if (0 == j)
314 continue;
315 assert(i >= j);
316 pword(p, &word[i - j], j);
317 j = 0;
318 }
319 if (j > 0) {
320 assert(i >= j);
321 pword(p, &word[i - j], j);
322 }
323 }
324
325
326 static void
327 termprint_r(struct termp *p, const struct mdoc_meta *meta,
328 const struct mdoc_node *node)
329 {
330 int dochild;
331
332 /* Pre-processing. */
333
334 dochild = 1;
335
336 if (MDOC_TEXT != node->type) {
337 if (termacts[node->tok].pre)
338 if ( ! (*termacts[node->tok].pre)(p, meta, node))
339 dochild = 0;
340 } else /* MDOC_TEXT == node->type */
341 word(p, node->data.text.string);
342
343 /* Children. */
344
345 if (dochild && node->child)
346 termprint_r(p, meta, node->child);
347
348 /* Post-processing. */
349
350 if (MDOC_TEXT != node->type)
351 if (termacts[node->tok].post)
352 (*termacts[node->tok].post)(p, meta, node);
353
354 /* Siblings. */
355
356 if (node->next)
357 termprint_r(p, meta, node->next);
358 }
359
360
361 static void
362 termprint_footer(struct termp *p, const struct mdoc_meta *meta)
363 {
364 struct tm *tm;
365 char *buf, *os;
366 size_t sz, osz, ssz, i;
367
368 if (NULL == (buf = malloc(p->rmargin)))
369 err(1, "malloc");
370 if (NULL == (os = malloc(p->rmargin)))
371 err(1, "malloc");
372
373 tm = localtime(&meta->date);
374
375 #ifdef __linux__
376 if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
377 #else
378 if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
379 #endif
380 err(1, "strftime");
381
382 osz = strlcpy(os, meta->os, p->rmargin);
383
384 sz = strlen(buf);
385 ssz = sz + osz + 1;
386
387 if (ssz > p->rmargin) {
388 ssz -= p->rmargin;
389 assert(ssz <= osz);
390 os[osz - ssz] = 0;
391 ssz = 1;
392 } else
393 ssz = p->rmargin - ssz + 1;
394
395 printf("\n");
396 printf("%s", os);
397 for (i = 0; i < ssz; i++)
398 printf(" ");
399
400 printf("%s\n", buf);
401 fflush(stdout);
402
403 free(buf);
404 free(os);
405 }
406
407
408 static void
409 termprint_header(struct termp *p, const struct mdoc_meta *meta)
410 {
411 char *buf, *title;
412 const char *pp, *msec;
413 size_t ssz, tsz, ttsz, i;;
414
415 if (NULL == (buf = malloc(p->rmargin)))
416 err(1, "malloc");
417 if (NULL == (title = malloc(p->rmargin)))
418 err(1, "malloc");
419
420 if (NULL == (pp = mdoc_vol2a(meta->vol)))
421 switch (meta->msec) {
422 case (MSEC_1):
423 /* FALLTHROUGH */
424 case (MSEC_6):
425 /* FALLTHROUGH */
426 case (MSEC_7):
427 pp = mdoc_vol2a(VOL_URM);
428 break;
429 case (MSEC_8):
430 pp = mdoc_vol2a(VOL_SMM);
431 break;
432 case (MSEC_2):
433 /* FALLTHROUGH */
434 case (MSEC_3):
435 /* FALLTHROUGH */
436 case (MSEC_4):
437 /* FALLTHROUGH */
438 case (MSEC_5):
439 pp = mdoc_vol2a(VOL_PRM);
440 break;
441 case (MSEC_9):
442 pp = mdoc_vol2a(VOL_KM);
443 break;
444 default:
445 /* FIXME: capitalise. */
446 if (NULL == (pp = mdoc_msec2a(meta->msec)))
447 pp = mdoc_msec2a(MSEC_local);
448 break;
449 }
450 assert(pp);
451
452 tsz = strlcpy(buf, pp, p->rmargin);
453 assert(tsz < p->rmargin);
454
455 if ((pp = mdoc_arch2a(meta->arch))) {
456 tsz = strlcat(buf, " (", p->rmargin);
457 assert(tsz < p->rmargin);
458 tsz = strlcat(buf, pp, p->rmargin);
459 assert(tsz < p->rmargin);
460 tsz = strlcat(buf, ")", p->rmargin);
461 assert(tsz < p->rmargin);
462 }
463
464 ttsz = strlcpy(title, meta->title, p->rmargin);
465
466 if (NULL == (msec = mdoc_msec2a(meta->msec)))
467 msec = "";
468
469 ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2;
470
471 if (ssz > p->rmargin) {
472 if ((ssz -= p->rmargin) % 2)
473 ssz++;
474 ssz /= 2;
475
476 assert(ssz <= ttsz);
477 title[ttsz - ssz] = 0;
478 ssz = 1;
479 } else
480 ssz = ((p->rmargin - ssz) / 2) + 1;
481
482 printf("%s(%s)", title, msec);
483
484 for (i = 0; i < ssz; i++)
485 printf(" ");
486
487 printf("%s", buf);
488
489 for (i = 0; i < ssz; i++)
490 printf(" ");
491
492 printf("%s(%s)\n", title, msec);
493 fflush(stdout);
494
495 free(title);
496 free(buf);
497 }
498
499
500 void
501 termprint(const struct mdoc_node *node,
502 const struct mdoc_meta *meta)
503 {
504 struct termp p;
505
506 p.maxrmargin = 80; /* XXX */
507 p.rmargin = p.maxrmargin;
508 p.maxcols = 1024;
509 p.offset = p.col = 0;
510 p.flags = TERMP_NOSPACE;
511
512 if (NULL == (p.buf = malloc(p.maxcols)))
513 err(1, "malloc");
514
515 termprint_header(&p, meta);
516 termprint_r(&p, meta, node);
517 termprint_footer(&p, meta);
518
519 free(p.buf);
520 }
521
522