]>
git.cameronkatri.com Git - mandoc.git/blob - mdocterm.c
7a0e1230af49bd0223eb1b85e09061e9f712bb5e
1 /* $Id: mdocterm.c,v 1.24 2009/03/02 12:09:32 kristaps Exp $ */
3 * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
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
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.
19 #include <sys/utsname.h>
36 #define TERMSYM_RBRACK "]"
37 #define TERMSYM_LBRACK "["
38 #define TERMSYM_LARROW "<-"
39 #define TERMSYM_RARROW "->"
40 #define TERMSYM_UARROW "^"
41 #define TERMSYM_DARROW "v"
42 #define TERMSYM_LSQUOTE "`"
43 #define TERMSYM_RSQUOTE "\'"
44 #define TERMSYM_SQUOTE "\'"
45 #define TERMSYM_LDQUOTE "``"
46 #define TERMSYM_RDQUOTE "\'\'"
47 #define TERMSYM_DQUOTE "\""
48 #define TERMSYM_LT "<"
49 #define TERMSYM_GT ">"
50 #define TERMSYM_LE "<="
51 #define TERMSYM_GE ">="
52 #define TERMSYM_EQ "=="
53 #define TERMSYM_NEQ "!="
54 #define TERMSYM_ACUTE "\'"
55 #define TERMSYM_GRAVE "`"
56 #define TERMSYM_PI "pi"
57 #define TERMSYM_PLUSMINUS "+="
58 #define TERMSYM_INF "oo"
59 #define TERMSYM_INF2 "infinity"
60 #define TERMSYM_NAN "NaN"
61 #define TERMSYM_BAR "|"
62 #define TERMSYM_BULLET "o"
65 #define xisspace(x) isspace((int)(x))
67 #define xisspace(x) isspace((x))
76 static void body(struct termp
*,
78 const struct mdoc_meta
*,
79 const struct mdoc_node
*);
80 static void header(struct termp
*,
81 const struct mdoc_meta
*);
82 static void footer(struct termp
*,
83 const struct mdoc_meta
*);
85 static void pword(struct termp
*, const char *, size_t);
86 static void pescape(struct termp
*,
87 const char *, size_t *, size_t);
88 static void pgraph(struct termp
*, char);
89 static void nescape(struct termp
*,
90 const char *, size_t);
91 static void chara(struct termp
*, char);
92 static void stringa(struct termp
*, const char *);
93 static void style(struct termp
*, enum termstyle
);
96 extern size_t strlcat(char *, const char *, size_t);
97 extern size_t strlcpy(char *, const char *, size_t);
102 main(int argc
, char *argv
[])
105 const struct mdoc
*mdoc
;
110 if ( ! mmain_getopt(p
, argc
, argv
, NULL
, NULL
, NULL
, NULL
))
113 if (NULL
== (mdoc
= mmain_mdoc(p
)))
116 termp
.maxrmargin
= 78; /* XXX */
117 termp
.rmargin
= termp
.maxrmargin
;
118 termp
.maxcols
= 1024;
119 termp
.offset
= termp
.col
= 0;
120 termp
.flags
= TERMP_NOSPACE
;
122 if (NULL
== (termp
.buf
= malloc(termp
.maxcols
)))
125 header(&termp
, mdoc_meta(mdoc
));
126 body(&termp
, NULL
, mdoc_meta(mdoc
), mdoc_node(mdoc
));
127 footer(&termp
, mdoc_meta(mdoc
));
137 flushln(struct termp
*p
)
139 size_t i
, j
, vsz
, vis
, maxvis
;
142 * First, establish the maximum columns of "visible" content.
143 * This is usually the difference between the right-margin and
144 * an indentation, but can be, for tagged lists or columns, a
145 * small set of values.
148 assert(p
->offset
< p
->rmargin
);
149 maxvis
= p
->rmargin
- p
->offset
;
153 * If in the standard case (left-justified), then begin with our
154 * indentation, otherwise (columns, etc.) just start spitting
158 if ( ! (p
->flags
& TERMP_NOLPAD
))
160 for (j
= 0; j
< p
->offset
; j
++)
164 * If we're literal, print out verbatim.
166 if (p
->flags
& TERMP_LITERAL
) {
167 for (i
= 0; i
< p
->col
; i
++)
174 for (i
= 0; i
< p
->col
; i
++) {
176 * Count up visible word characters. Control sequences
177 * (starting with the CSI) aren't counted. A space
178 * generates a non-printing word, which is valid (the
179 * space is printed according to regular spacing rules).
183 for (j
= i
, vsz
= 0; j
< p
->col
; j
++) {
184 if (xisspace(p
->buf
[j
]))
186 else if (27 == p
->buf
[j
]) {
187 assert(j
+ 4 <= p
->col
);
194 * If we're breaking normally...
196 * If a word is too long and we're within a line, put it
197 * on the next line. Puke if we're being asked to write
198 * something that will exceed the right margin (i.e.,
199 * from a fresh line).
201 * If we're not breaking...
203 * Don't let the visible size exceed the full right
207 if ( ! (TERMP_NOBREAK
& p
->flags
)) {
208 if (vis
&& vis
+ vsz
> maxvis
) {
210 for (j
= 0; j
< p
->offset
; j
++)
213 } else if (vis
+ vsz
> maxvis
)
214 errx(1, "word breaks right margin");
215 } else if (vis
+ vsz
> p
->maxrmargin
- p
->offset
) {
217 for (j
= 0; j
< p
->rmargin
; j
++)
223 * Write out the word and a trailing space. Omit the
224 * space if we're the last word in the line.
227 for ( ; i
< p
->col
; i
++) {
228 if (xisspace(p
->buf
[i
]))
239 if ((TERMP_NOBREAK
& p
->flags
) && vis
>= maxvis
) {
241 for (i
= 0; i
< p
->rmargin
; i
++)
248 * If we're not to right-marginalise it (newline), then instead
249 * pad to the right margin and stay off.
252 if (p
->flags
& TERMP_NOBREAK
) {
253 for ( ; vis
< maxvis
; vis
++)
263 newln(struct termp
*p
)
267 * A newline only breaks an existing line; it won't assert
270 p
->flags
|= TERMP_NOSPACE
;
272 p
->flags
&= ~TERMP_NOLPAD
;
276 p
->flags
&= ~TERMP_NOLPAD
;
281 vspace(struct termp
*p
)
285 * Asserts a vertical space (a full, empty line-break between
294 stringa(struct termp
*p
, const char *s
)
297 /* XXX - speed up if not passing to chara. */
304 chara(struct termp
*p
, char c
)
308 * Insert a single character into the line-buffer. If the
309 * buffer's space is exceeded, then allocate more space.
311 if (p
->col
+ 1 >= p
->maxcols
) {
312 p
->buf
= realloc(p
->buf
, p
->maxcols
* 2);
317 p
->buf
[(p
->col
)++] = c
;
322 style(struct termp
*p
, enum termstyle esc
)
325 if (p
->col
+ 4 >= p
->maxcols
)
326 errx(1, "line overrun");
328 p
->buf
[(p
->col
)++] = 27;
329 p
->buf
[(p
->col
)++] = '[';
332 p
->buf
[(p
->col
)++] = '0';
335 p
->buf
[(p
->col
)++] = '1';
337 case (STYLE_UNDERLINE
):
338 p
->buf
[(p
->col
)++] = '4';
344 p
->buf
[(p
->col
)++] = 'm';
349 nescape(struct termp
*p
, const char *word
, size_t len
)
355 stringa(p
, TERMSYM_DQUOTE
);
358 if ('r' == word
[0] && 'B' == word
[1])
359 stringa(p
, TERMSYM_RBRACK
);
360 else if ('l' == word
[0] && 'B' == word
[1])
361 stringa(p
, TERMSYM_LBRACK
);
362 else if ('l' == word
[0] && 'q' == word
[1])
363 stringa(p
, TERMSYM_LDQUOTE
);
364 else if ('r' == word
[0] && 'q' == word
[1])
365 stringa(p
, TERMSYM_RDQUOTE
);
366 else if ('o' == word
[0] && 'q' == word
[1])
367 stringa(p
, TERMSYM_LSQUOTE
);
368 else if ('a' == word
[0] && 'q' == word
[1])
369 stringa(p
, TERMSYM_RSQUOTE
);
370 else if ('<' == word
[0] && '-' == word
[1])
371 stringa(p
, TERMSYM_LARROW
);
372 else if ('-' == word
[0] && '>' == word
[1])
373 stringa(p
, TERMSYM_RARROW
);
374 else if ('b' == word
[0] && 'u' == word
[1])
375 stringa(p
, TERMSYM_BULLET
);
376 else if ('<' == word
[0] && '=' == word
[1])
377 stringa(p
, TERMSYM_LE
);
378 else if ('>' == word
[0] && '=' == word
[1])
379 stringa(p
, TERMSYM_GE
);
380 else if ('=' == word
[0] && '=' == word
[1])
381 stringa(p
, TERMSYM_EQ
);
382 else if ('+' == word
[0] && '-' == word
[1])
383 stringa(p
, TERMSYM_PLUSMINUS
);
384 else if ('u' == word
[0] && 'a' == word
[1])
385 stringa(p
, TERMSYM_UARROW
);
386 else if ('d' == word
[0] && 'a' == word
[1])
387 stringa(p
, TERMSYM_DARROW
);
388 else if ('a' == word
[0] && 'a' == word
[1])
389 stringa(p
, TERMSYM_ACUTE
);
390 else if ('g' == word
[0] && 'a' == word
[1])
391 stringa(p
, TERMSYM_GRAVE
);
392 else if ('!' == word
[0] && '=' == word
[1])
393 stringa(p
, TERMSYM_NEQ
);
394 else if ('i' == word
[0] && 'f' == word
[1])
395 stringa(p
, TERMSYM_INF
);
396 else if ('n' == word
[0] && 'a' == word
[1])
397 stringa(p
, TERMSYM_NAN
);
398 else if ('b' == word
[0] && 'a' == word
[1])
399 stringa(p
, TERMSYM_BAR
);
401 /* Deprecated forms. */
402 else if ('B' == word
[0] && 'a' == word
[1])
403 stringa(p
, TERMSYM_BAR
);
404 else if ('I' == word
[0] && 'f' == word
[1])
405 stringa(p
, TERMSYM_INF2
);
406 else if ('G' == word
[0] && 'e' == word
[1])
407 stringa(p
, TERMSYM_GE
);
408 else if ('G' == word
[0] && 't' == word
[1])
409 stringa(p
, TERMSYM_GT
);
410 else if ('L' == word
[0] && 'e' == word
[1])
411 stringa(p
, TERMSYM_LE
);
412 else if ('L' == word
[0] && 'q' == word
[1])
413 stringa(p
, TERMSYM_LDQUOTE
);
414 else if ('L' == word
[0] && 't' == word
[1])
415 stringa(p
, TERMSYM_LT
);
416 else if ('N' == word
[0] && 'a' == word
[1])
417 stringa(p
, TERMSYM_NAN
);
418 else if ('N' == word
[0] && 'e' == word
[1])
419 stringa(p
, TERMSYM_NEQ
);
420 else if ('P' == word
[0] && 'i' == word
[1])
421 stringa(p
, TERMSYM_PI
);
422 else if ('P' == word
[0] && 'm' == word
[1])
423 stringa(p
, TERMSYM_PLUSMINUS
);
424 else if ('R' == word
[0] && 'q' == word
[1])
425 stringa(p
, TERMSYM_RDQUOTE
);
434 pgraph(struct termp
*p
, char byte
)
443 for (i
= 0; i
< INDENT
; i
++)
447 warnx("unknown non-graphing character");
454 pescape(struct termp
*p
, const char *word
, size_t *i
, size_t len
)
462 * Handle an escape sequence. This must manage both groff-style
463 * escapes and mdoc-style escapes.
466 if ('(' == word
[*i
]) {
467 /* Two-character escapes. */
469 assert(*i
+ 1 < len
);
470 nescape(p
, &word
[*i
], 2);
474 } else if ('*' == word
[*i
]) {
480 assert(*i
+ 1 < len
);
481 nescape(p
, &word
[*i
], 2);
487 nescape(p
, &word
[*i
], 1);
490 } else if ('[' != word
[*i
]) {
491 /* One-character escapes. */
516 for (j
= 0; word
[*i
] && ']' != word
[*i
]; (*i
)++, j
++)
519 nescape(p
, &word
[*i
- j
], j
);
524 pword(struct termp
*p
, const char *word
, size_t len
)
529 * Handle pwords, partial words, which may be either a single
530 * word or a phrase that cannot be broken down (such as a
531 * literal string). This handles word styling.
534 if ( ! (p
->flags
& TERMP_NOSPACE
) &&
535 ! (p
->flags
& TERMP_LITERAL
))
538 if ( ! (p
->flags
& TERMP_NONOSPACE
))
539 p
->flags
&= ~TERMP_NOSPACE
;
542 * XXX - if literal and underlining, this will underline the
543 * spaces between literal words.
546 if (p
->flags
& TERMP_BOLD
)
547 style(p
, STYLE_BOLD
);
548 if (p
->flags
& TERMP_UNDERLINE
)
549 style(p
, STYLE_UNDERLINE
);
551 for (i
= 0; i
< len
; i
++) {
552 if ('\\' == word
[i
]) {
553 pescape(p
, word
, &i
, len
);
556 if ( ! isgraph((int)word
[i
])) {
563 if (p
->flags
& TERMP_BOLD
||
564 p
->flags
& TERMP_UNDERLINE
)
565 style(p
, STYLE_CLEAR
);
570 word(struct termp
*p
, const char *word
)
575 * Break apart a word into tokens. If we're a literal word,
576 * then don't. This doesn't handle zero-length words (there
577 * should be none) and makes sure that pword doesn't get spaces
578 * or nil words unless literal.
581 if (p
->flags
& TERMP_LITERAL
) {
582 pword(p
, word
, strlen(word
));
589 if (mdoc_isdelim(word
)) {
590 if ( ! (p
->flags
& TERMP_IGNDELIM
))
591 p
->flags
|= TERMP_NOSPACE
;
592 p
->flags
&= ~TERMP_IGNDELIM
;
596 for (j
= i
= 0; i
< len
; i
++) {
597 if ( ! xisspace(word
[i
])) {
602 /* Escaped spaces don't delimit... */
603 if (i
> 0 && xisspace(word
[i
]) && '\\' == word
[i
- 1]) {
611 pword(p
, &word
[i
- j
], j
);
616 pword(p
, &word
[i
- j
], j
);
622 body(struct termp
*p
, struct termpair
*ppair
,
623 const struct mdoc_meta
*meta
,
624 const struct mdoc_node
*node
)
627 struct termpair pair
;
630 * This is the main function for printing out nodes. It's
631 * constituted of PRE and POST functions, which correspond to
632 * prefix and infix processing.
635 /* Pre-processing. */
640 pair
.offset
= pair
.rmargin
= 0;
644 if (MDOC_TEXT
!= node
->type
) {
645 if (termacts
[node
->tok
].pre
)
646 if ( ! (*termacts
[node
->tok
].pre
)(p
, &pair
, meta
, node
))
648 } else /* MDOC_TEXT == node->type */
649 word(p
, node
->data
.text
.string
);
653 if (TERMPAIR_FLAG
& pair
.type
)
654 p
->flags
|= pair
.flag
;
656 if (dochild
&& node
->child
)
657 body(p
, &pair
, meta
, node
->child
);
659 if (TERMPAIR_FLAG
& pair
.type
)
660 p
->flags
&= ~pair
.flag
;
662 /* Post-processing. */
664 if (MDOC_TEXT
!= node
->type
)
665 if (termacts
[node
->tok
].post
)
666 (*termacts
[node
->tok
].post
)(p
, &pair
, meta
, node
);
671 body(p
, ppair
, meta
, node
->next
);
676 footer(struct termp
*p
, const struct mdoc_meta
*meta
)
681 if (NULL
== (buf
= malloc(p
->rmargin
)))
683 if (NULL
== (os
= malloc(p
->rmargin
)))
686 tm
= localtime(&meta
->date
);
689 if (NULL
== strftime(buf
, p
->rmargin
, "%B %d, %Y", tm
))
691 if (0 == strftime(buf
, p
->rmargin
, "%B %d, %Y", tm
))
695 (void)strlcpy(os
, meta
->os
, p
->rmargin
);
698 * This is /slightly/ different from regular groff output
699 * because we don't have page numbers. Print the following:
706 p
->flags
|= TERMP_NOSPACE
| TERMP_NOBREAK
;
707 p
->rmargin
= p
->maxrmargin
- strlen(buf
);
713 p
->flags
|= TERMP_NOLPAD
| TERMP_NOSPACE
;
714 p
->offset
= p
->rmargin
;
715 p
->rmargin
= p
->maxrmargin
;
716 p
->flags
&= ~TERMP_NOBREAK
;
727 header(struct termp
*p
, const struct mdoc_meta
*meta
)
729 char *buf
, *title
, *bufp
, *vbuf
;
733 p
->rmargin
= p
->maxrmargin
;
736 if (NULL
== (buf
= malloc(p
->rmargin
)))
738 if (NULL
== (title
= malloc(p
->rmargin
)))
740 if (NULL
== (vbuf
= malloc(p
->rmargin
)))
743 if (NULL
== (pp
= mdoc_vol2a(meta
->vol
))) {
744 switch (meta
->msec
) {
750 pp
= mdoc_vol2a(VOL_URM
);
753 pp
= mdoc_vol2a(VOL_SMM
);
762 pp
= mdoc_vol2a(VOL_PRM
);
765 pp
= mdoc_vol2a(VOL_KM
);
774 if (-1 == uname(&uts
))
776 (void)strlcat(vbuf
, uts
.sysname
, p
->rmargin
);
777 (void)strlcat(vbuf
, " ", p
->rmargin
);
778 } else if (NULL
== (pp
= mdoc_msec2a(meta
->msec
)))
779 pp
= mdoc_msec2a(MSEC_local
);
781 (void)strlcat(vbuf
, pp
, p
->rmargin
);
784 * The header is strange. It has three components, which are
785 * really two with the first duplicated. It goes like this:
787 * IDENTIFIER TITLE IDENTIFIER
789 * The IDENTIFIER is NAME(SECTION), which is the command-name
790 * (if given, or "unknown" if not) followed by the manual page
791 * section. These are given in `Dt'. The TITLE is a free-form
792 * string depending on the manual volume. If not specified, it
793 * switches on the manual section.
796 if (mdoc_arch2a(meta
->arch
))
797 (void)snprintf(buf
, p
->rmargin
, "%s (%s)",
798 vbuf
, mdoc_arch2a(meta
->arch
));
800 (void)strlcpy(buf
, vbuf
, p
->rmargin
);
802 pp
= mdoc_msec2a(meta
->msec
);
804 (void)snprintf(title
, p
->rmargin
, "%s(%s)",
805 meta
->title
, pp
? pp
: "");
807 for (bufp
= title
; *bufp
; bufp
++)
808 *bufp
= toupper(*bufp
);
811 p
->rmargin
= (p
->maxrmargin
- strlen(buf
)) / 2;
812 p
->flags
|= TERMP_NOBREAK
| TERMP_NOSPACE
;
817 p
->flags
|= TERMP_NOLPAD
| TERMP_NOSPACE
;
818 p
->offset
= p
->rmargin
;
819 p
->rmargin
= p
->maxrmargin
- strlen(title
);
824 p
->offset
= p
->rmargin
;
825 p
->rmargin
= p
->maxrmargin
;
826 p
->flags
&= ~TERMP_NOBREAK
;
827 p
->flags
|= TERMP_NOLPAD
| TERMP_NOSPACE
;
832 p
->rmargin
= p
->maxrmargin
;
834 p
->flags
&= ~TERMP_NOSPACE
;