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