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